From c648493a9efd9d6f33ba93bf98b3c47a19cbae6d Mon Sep 17 00:00:00 2001 From: Minepig <30530115+Minepig@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:47:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E7=9A=AE=E8=82=A4?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AquaMai/AquaMai.csproj | 5 + AquaMai/CustomSkin/CustomNoteSkin.cs | 314 +++++++++++++++++++++++++++ AquaMai/Main.cs | 9 + 3 files changed, 328 insertions(+) create mode 100644 AquaMai/CustomSkin/CustomNoteSkin.cs diff --git a/AquaMai/AquaMai.csproj b/AquaMai/AquaMai.csproj index a83253de..3adf00f5 100644 --- a/AquaMai/AquaMai.csproj +++ b/AquaMai/AquaMai.csproj @@ -292,12 +292,14 @@ DEBUG + + @@ -311,6 +313,9 @@ DEBUG + + + True True diff --git a/AquaMai/CustomSkin/CustomNoteSkin.cs b/AquaMai/CustomSkin/CustomNoteSkin.cs new file mode 100644 index 00000000..24b0b891 --- /dev/null +++ b/AquaMai/CustomSkin/CustomNoteSkin.cs @@ -0,0 +1,314 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using MelonLoader; +using Monitor; +using Monitor.Game; +using Process; +using UnityEngine; + +namespace AquaMai.CustomSkin; + +public class CustomNoteSkin +{ + private static readonly List ImageExts = [".jpg", ".png", ".jpeg"]; + private static readonly List SlideFanFields = ["_normalSlideFan", "_eachSlideFan", "_breakSlideFan", "_breakSlideFanEff"]; + + private static Sprite customOutline; + private static Sprite[,] customSlideFan = new Sprite[4, 11]; + + private static bool LoadIntoGameNoteImageContainer(string fieldName, int? idx1, int? idx2, Texture2D texture) + { + // 先确定确实有这个 Field, 如果没有的话可以直接跳过这个文件 + var fieldTraverse = Traverse.Create(typeof(GameNoteImageContainer)).Field(fieldName); + if (!fieldTraverse.FieldExists()) + { + MelonLogger.Msg($"[CustomNoteSkin] Cannot found field {fieldName}"); + return false; + } + + var fieldType = fieldTraverse.GetValueType(); + if (!idx1.HasValue) + { + // 目标 Field 应当是单个 Sprite + if (fieldType != typeof(Sprite)) + { + MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite"); + return false; + } + var target = fieldTraverse.GetValue(); + var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height); + var custom = Sprite.Create( + texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, + 0, SpriteMeshType.Tight, target.border + ); + fieldTraverse.SetValue(custom); + } + else if (!idx2.HasValue) + { + // 目标 Field 是一维数组 + if (fieldType != typeof(Sprite[])) + { + MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite[]"); + return false; + } + var targetArray = fieldTraverse.GetValue(); + var target = targetArray[idx1.Value]; + var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height); + var custom = Sprite.Create( + texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, + 0, SpriteMeshType.Tight, target.border + ); + targetArray[idx1.Value] = custom; + } + else + { + // 目标 Field 是二维数组 + if (fieldType != typeof(Sprite[,])) + { + MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite[,]"); + return false; + } + var targetArray = fieldTraverse.GetValue(); + var target = targetArray[idx1.Value, idx2.Value]; + var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height); + var custom = Sprite.Create( + texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, + 0, SpriteMeshType.Tight, target.border + ); + targetArray[idx1.Value, idx2.Value] = custom; + } + + return true; + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(GameNotePrefabContainer), "Initialize")] + private static void LoadNoteSkin() + { + if (!Directory.Exists(Path.Combine(Environment.CurrentDirectory, "Skins"))) return; + + foreach (var laFile in Directory.EnumerateFiles(Path.Combine(Environment.CurrentDirectory, "Skins"))) + { + if (!ImageExts.Contains(Path.GetExtension(laFile).ToLowerInvariant())) continue; + var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); + texture.LoadImage(File.ReadAllBytes(laFile)); + + var name = Path.GetFileNameWithoutExtension(laFile); + var args = name.Split('_'); + // 文件名的格式是 XXXXXXXX_A_B 表示 GameNoteImageContainer._XXXXXXXX[A, B] + // 视具体情况, A, B 可能不存在 + var fieldName = '_' + args[0]; + int? idx1 = (args.Length < 2)? null : (int.TryParse(args[1], out var temp) ? temp : null); + int? idx2 = (args.Length < 3)? null : (int.TryParse(args[2], out temp) ? temp : null); + + Traverse traverse; + + if (fieldName == "_outline") + { + customOutline = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 1f); + MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); + continue; + } + + if (SlideFanFields.Contains(fieldName)) + { + if (!idx1.HasValue) + { + MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); + continue; + } + var i = SlideFanFields.IndexOf(fieldName); + customSlideFan[i, idx1.Value] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(1f, 0.5f), 1f); + MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); + continue; + } + + if (fieldName == "_touchJust") + { + traverse = Traverse.Create(GameNotePrefabContainer.TouchTapB); + var noticeObject = traverse.Field("NoticeObject").Value; + var target = noticeObject.GetComponent(); + var pivot = new Vector2( + target.sprite.pivot.x / target.sprite.rect.width, + target.sprite.pivot.y / target.sprite.rect.height + ); + var custom = Sprite.Create( + texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, + 0, SpriteMeshType.Tight, target.sprite.border + ); + target.sprite = custom; + + traverse = Traverse.Create(GameNotePrefabContainer.TouchTapC); + noticeObject = traverse.Field("NoticeObject").Value; + noticeObject.GetComponent().sprite = custom; + MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); + continue; + } + + if (fieldName == "_touchHold") + { + if (!idx1.HasValue) + { + MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); + continue; + } + traverse = Traverse.Create(GameNotePrefabContainer.TouchHoldC); + var target = traverse.Field("ColorsObject").Value; + var renderer = target[idx1.Value]; + var pivot = new Vector2( + renderer.sprite.pivot.x / renderer.sprite.rect.width, + renderer.sprite.pivot.y / renderer.sprite.rect.height + ); + var custom = Sprite.Create( + texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, + 0, SpriteMeshType.Tight, renderer.sprite.border + ); + renderer.sprite = custom; + MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); + continue; + } + + if (fieldName == "_normalTouchBorder") + { + if (!idx1.HasValue) + { + MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); + continue; + } + traverse = Traverse.Create(GameNotePrefabContainer.TouchReserve); + var target = traverse.Field("_reserveSingleSprite").Value; + var targetSprite = target[idx1.Value - 2]; + var pivot = new Vector2( + targetSprite.pivot.x / targetSprite.rect.width, + targetSprite.pivot.y / targetSprite.rect.height + ); + target[idx1.Value - 2] = Sprite.Create( + texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, + 0, SpriteMeshType.Tight, targetSprite.border + ); + MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); + continue; + } + + if (fieldName == "_eachTouchBorder") + { + if (!idx1.HasValue) + { + MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); + continue; + } + traverse = Traverse.Create(GameNotePrefabContainer.TouchReserve); + var target = traverse.Field("_reserveEachSprite").Value; + var targetSprite = target[idx1.Value - 2]; + var pivot = new Vector2( + targetSprite.pivot.x / targetSprite.rect.width, + targetSprite.pivot.y / targetSprite.rect.height + ); + target[idx1.Value - 2] = Sprite.Create( + texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, + 0, SpriteMeshType.Tight, targetSprite.border + ); + MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); + continue; + } + + if (LoadIntoGameNoteImageContainer(fieldName, idx1, idx2, texture)) + { + MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); + } + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(GameCtrl), "Initialize")] + private static void ChangeOutlineTexture(GameObject ____guideEndPointObj) + { + if (____guideEndPointObj != null && customOutline != null) + { + ____guideEndPointObj.GetComponent().sprite = customOutline; + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(SlideFan), "Initialize")] + private static void ChangeFanTexture( + SpriteRenderer[] ____spriteLines, SpriteRenderer[] ____effectSprites, bool ___BreakFlag, bool ___EachFlag + ) + { + Vector3 position; + Sprite sprite; + if (___BreakFlag) + { + for (var i = 0; i < 11; i++) + { + sprite = customSlideFan[2, i]; + if (sprite != null) + { + ____spriteLines[2 * i].sprite = sprite; + position = ____spriteLines[2 * i].transform.localPosition; + ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z); + ____spriteLines[2 * i].color = Color.white; + + ____spriteLines[2 * i + 1].sprite = sprite; + position = ____spriteLines[2 * i + 1].transform.localPosition; + ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z); + ____spriteLines[2 * i + 1].color = Color.white; + } + sprite = customSlideFan[3, i]; + if (sprite != null) + { + ____effectSprites[2 * i].sprite = sprite; + position = ____effectSprites[2 * i].transform.localPosition; + ____effectSprites[2 * i].transform.localPosition = new Vector3(0, position.y, position.z); + ____effectSprites[2 * i].color = Color.white; + + ____effectSprites[2 * i + 1].sprite = sprite; + position = ____effectSprites[2 * i + 1].transform.localPosition; + ____effectSprites[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z); + ____effectSprites[2 * i + 1].color = Color.white; + } + } + } + else if (___EachFlag) + { + for (var i = 0; i < 11; i++) + { + sprite = customSlideFan[1, i]; + if (sprite != null) + { + ____spriteLines[2 * i].sprite = sprite; + position = ____spriteLines[2 * i].transform.localPosition; + ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z); + ____spriteLines[2 * i].color = Color.white; + + ____spriteLines[2 * i + 1].sprite = sprite; + position = ____spriteLines[2 * i + 1].transform.localPosition; + ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z); + ____spriteLines[2 * i + 1].color = Color.white; + } + } + } + else + { + for (var i = 0; i < 11; i++) + { + sprite = customSlideFan[0, i]; + if (sprite != null) + { + ____spriteLines[2 * i].sprite = sprite; + position = ____spriteLines[2 * i].transform.localPosition; + ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z); + ____spriteLines[2 * i].color = Color.white; + + ____spriteLines[2 * i + 1].sprite = sprite; + position = ____spriteLines[2 * i + 1].transform.localPosition; + ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z); + ____spriteLines[2 * i + 1].color = Color.white; + } + } + } + } +} \ No newline at end of file diff --git a/AquaMai/Main.cs b/AquaMai/Main.cs index 7d8619b9..97910f59 100644 --- a/AquaMai/Main.cs +++ b/AquaMai/Main.cs @@ -4,8 +4,10 @@ using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; +using AquaMai.CustomSkin; using AquaMai.Fix; using AquaMai.Helpers; +using AquaMai.RenderTweak; using AquaMai.Resources; using AquaMai.Utils; using AquaMai.UX; @@ -164,6 +166,13 @@ namespace AquaMai Patch(typeof(RunCommandOnEvents)); // Utils Patch(typeof(JudgeAdjust)); + + // My Patches + Patch(typeof(CustomNoteSkin)); + Patch(typeof(SlideAutoPlayTweak)); + Patch(typeof(TrackStartProcessTweak)); + Patch(typeof(SlideJudgeTweak)); + Patch(typeof(FixConnSlide)); # if DEBUG Patch(typeof(LogNetworkErrors)); # endif