diff --git a/AquaMai/AquaMai.csproj b/AquaMai/AquaMai.csproj index 9965e89f..74d23044 100644 --- a/AquaMai/AquaMai.csproj +++ b/AquaMai/AquaMai.csproj @@ -292,12 +292,14 @@ DEBUG + + @@ -312,6 +314,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/Fix/FixConnSlide.cs b/AquaMai/Fix/FixConnSlide.cs new file mode 100644 index 00000000..138716ad --- /dev/null +++ b/AquaMai/Fix/FixConnSlide.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using Manager; +using MelonLoader; +using Monitor; + +namespace AquaMai.Fix; + +public class FixConnSlide +{ + /* 这个 Patch 用于修复以下 bug: + * 非 ConnSlide 被错误解析为 ConnSlide (Fes 首日刹那旅程爆机 bug) + * 原 method 逻辑如下: + * + * if (this.IsSlideAll(noteData1.type) && (index1 + 1 < this._note._noteData.Count ? 1 : 0) != 0) + * { + * int targetNote = noteData1.slideData.targetNote; + * if (noteData1.slideData != null) + * targetNote = noteData1.slideData.targetNote; + * for (int index3 = index1; index3 < this._note._noteData.Count; ++index3) + * { + * NoteData noteData3 = this._note._noteData[index3]; + * if (this.IsSlideAll(noteData3.type) && noteData3.time == noteData1.end && noteData3.startButtonPos == targetNote && noteData3.parent == null) + * { + * noteData3.parent = noteData1.parent; + * noteData1.child.Add(noteData3); + * noteData3.isUsed = true; + * noteData3.isJudged = true; + * break; + * } + * } + * } + * + * 修复 bug 需要把第二次调用 this.IsSlideAll() 更改为 this.IsConnectNote(), 这里使用 Transpiler 解决 + */ + [HarmonyTranspiler] + [HarmonyPatch(typeof(NotesReader), "calcSlide")] + private static IEnumerable Fix(IEnumerable instructions) + { + List instList = new List(instructions); + bool found = false; + MethodInfo methodIsSlideAll = AccessTools.Method(typeof(NotesReader), "IsSlideAll"); + MethodInfo methodIsConnectNote = AccessTools.Method(typeof(NotesReader), "IsConnectNote"); + + for (int i = 0; i < instList.Count; i++) + { + CodeInstruction inst = instList[i]; + if (!found && inst.Calls(methodIsSlideAll)) + { + found = true; + continue; + } + + if (found && inst.Calls(methodIsSlideAll)) + { + inst.operand = methodIsConnectNote; + // MelonLogger.Msg($"[FixConnSlide] Successfully patched NotesReader::calcSlide"); + break; + } + } + return instList; + } +} \ 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.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 @@ public override void OnInitializeMelon() 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 diff --git a/AquaMai/RenderTweak/SlideAutoPlayTweak.cs b/AquaMai/RenderTweak/SlideAutoPlayTweak.cs new file mode 100644 index 00000000..17017b6f --- /dev/null +++ b/AquaMai/RenderTweak/SlideAutoPlayTweak.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using HarmonyLib; +using Manager; +using Monitor; + +namespace AquaMai.RenderTweak; + +public class SlideAutoPlayTweak +{ + /* 这个 Patch 用于修复以下 bug: + * SlideFan 在 AutoPlay 时, 只有第一个箭头会消失 + * 原 method 逻辑如下: + * + * if (this.IsNoteCheckTimeStartIgnoreJudgeWait()) + * { + * // do something ... + * if (!GameManager.IsAutoPlay()) + * { + * // do something ... + * for (int index = 0; index < this._arrowPrefubs.Length && (double) index < (double) num2 * 11.0; ++index) + * { + * // do something about displaying arrows ... + * } + * } + * else + * { + * float num4 = (currentMsec - this.StarLaunchMsec) / (this.StarArriveMsec - this.StarLaunchMsec - this.lastWaitTime); + * for (int index = 0; index < this._arrowPrefubs.Length && (double) index < (double) num4 * 1.0; ++index) + * { + * // do something about displaying arrows ... + * } + * if ((double) num4 > 1.0) + * num1 = 3; + * } + * // do something ... + * } + * + * 导致这个 bug 的原因是 else 分支的 for 循环终止条件写错了, 应该是 11.0 (因为有 11 个箭头), SBGA 写成了 1.0 + * 这个 method 中一共只有 5 处 ldc.r4 的 IL Code, 依次为 10.0, 11.0, 1.0, 1.0, 0.0 + * 修复 bug 需要把第三处的 1.0 更改为 11.0, 这里使用 Transpiler 解决 + */ + [HarmonyTranspiler] + [HarmonyPatch(typeof(SlideFan), "NoteCheck")] + private static IEnumerable FixFanAutoPlayArrow(IEnumerable instructions) + { + List instList = new List(instructions); + bool found = false; + for (int i = 0; i < instList.Count; i++) + { + CodeInstruction inst = instList[i]; + if (inst.LoadsConstant(11.0)) + { + found = true; + } + + if (found && inst.LoadsConstant(1.0)) + { + inst.operand = 11.0f; + break; + } + } + return instList; + } + + /* 这个 Patch 让 Slide 在 AutoPlay 的时候, 每个区仍然会分按下和松开两段进行推进 (加上 this._hitIn 的变化) + * 原 method 逻辑如下: + * + * if (!GameManager.IsAutoPlay()) + * { + * // do somethings ... + * } + * else + * { + * float num1 = (currentMsec - this.StarLaunchMsec) / (this.StarArriveMsec - this.StarLaunchMsec - this.lastWaitTime); + * this._hitIndex = (int) ((double) this._hitAreaList.Count * (double) num1); + * if (this._hitIndex >= this._hitAreaList.Count) + * this._hitIndex = this._hitAreaList.Count - 1; + * if (this._hitIndex < 0) + * this._hitIndex = 0; + * int num2 = (int) ((double) this._dispLaneNum * this.GetDeleteArrowDistance()); + * // do somethings ... + * } + * + * 现在要在 this.GetDeleteArrowDistance() 之前插入 + * this._hitIn = ((float)this._hitAreaList.Count * num1 > (float)this._hitIndex + 0.5f); + * 这段代码, 可以采用 Prefix, GetDeleteArrowDistance() 只在两个地方调用过, 另一处就在上面的 if 分支中 (即非 AutoPlay 情况) + */ + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideRoot), "GetDeleteArrowDistance")] + private static void FixSlideAutoPlayArrow( + SlideRoot __instance, ref bool ____hitIn, int ____hitIndex, List ____hitAreaList, + float ___StarLaunchMsec, float ___StarArriveMsec, float ___lastWaitTime + ) + { + if (GameManager.IsAutoPlay()) + { + float prop = (NotesManager.GetCurrentMsec() - ___StarLaunchMsec) / (___StarArriveMsec - ___StarLaunchMsec - ___lastWaitTime); + ____hitIn = ____hitAreaList.Count * prop > ____hitIndex + 0.5f; + } + } + +} \ No newline at end of file diff --git a/AquaMai/RenderTweak/SlideJudgeTweak.cs b/AquaMai/RenderTweak/SlideJudgeTweak.cs new file mode 100644 index 00000000..25377a11 --- /dev/null +++ b/AquaMai/RenderTweak/SlideJudgeTweak.cs @@ -0,0 +1,89 @@ +using System; +using HarmonyLib; +using Manager; +using Monitor; +using Process; +using UnityEngine; + +namespace AquaMai.RenderTweak; + +public class SlideJudgeTweak +{ + /* + * 这个 Patch 让 BreakSlide 的 Critical 判定也可以像 BreakTap 一样闪烁 + */ + [HarmonyPostfix] + [HarmonyPatch(typeof(SlideJudge), "UpdateBreakEffectAdd")] + private static void FixBreakSlideJudgeBlink( + SpriteRenderer ___SpriteRenderAdd, SpriteRenderer ___SpriteRender, + SlideJudge.SlideJudgeType ____judgeType, SlideJudge.SlideAngle ____angle + ) + { + if (!___SpriteRenderAdd.gameObject.activeSelf) return; + float num = ___SpriteRenderAdd.color.r; + ___SpriteRenderAdd.color = new Color(num, num, num, 0.3f); + if (num > 0.9f) + { + ___SpriteRender.sprite = GameNoteImageContainer.JudgeSlideCriticalBreak[(int) ____judgeType, (int) ____angle]; + } + else if (num < 0.1f) + { + ___SpriteRender.sprite = GameNoteImageContainer.JudgeSlideCritical[(int) ____judgeType, (int) ____angle]; + } + } + + /* + * 这个 Patch 让圆弧形的 Slide 的判定显示与判定线精确对齐 (原本会有一点歪), 就像 majdata 里那样 + */ + [HarmonyPostfix] + [HarmonyPatch(typeof(SlideRoot), "Initialize")] + private static void FixCircleSlideJudgePosition( + SlideRoot __instance, SlideType ___EndSlideType, SlideJudge ___JudgeObj + ) + { + if (null != ___JudgeObj) + { + float z = ___JudgeObj.transform.localPosition.z; + if (___EndSlideType == SlideType.Slide_Circle_L) + { + float angle = -45.0f - 45.0f * __instance.EndButtonId; + double angleRad = Math.PI / 180.0 * (angle + 90 + 22.5 + 2.6415); + ___JudgeObj.transform.localPosition = new Vector3(480f * (float)Math.Cos(angleRad), 480f * (float)Math.Sin(angleRad), z); + ___JudgeObj.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, angle); + } + else if (___EndSlideType == SlideType.Slide_Circle_R) + { + float angle = -45.0f * __instance.EndButtonId; + double angleRad = Math.PI / 180.0 * (angle + 90 - 22.5 - 2.6415); + ___JudgeObj.transform.localPosition = new Vector3(480f * (float)Math.Cos(angleRad), 480f * (float)Math.Sin(angleRad), z); + ___JudgeObj.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, angle); + } + } + } + + /* + * 这个 Patch 让 Wifi Slide 的判定显示有上下的区别 (原本所有 Wifi 的判定显示都是朝向圆心的), 就像 majdata 里那样 + * 这个 bug 产生的原因是 SBGA 忘记给 Wifi 的 EndButtonId 赋值了 + * 不过需要注意的是, 考虑到圆弧形 Slide 的判定显示就是永远朝向圆心的, 我个人会觉得这个 Patch 关掉更好看一点 + * 所以这里把 Patch 注释掉了 + */ + // [HarmonyPostfix] + // [HarmonyPatch(typeof(SlideFan), "Initialize")] + private static void FixFanJudgeFilp( + int[] ___GoalButtonId, SlideJudge ___JudgeObj + ) + { + if (null != ___JudgeObj) + { + if (2 <= ___GoalButtonId[1] && ___GoalButtonId[1] <= 5) + { + ___JudgeObj.Flip(false); + ___JudgeObj.transform.Rotate(0.0f, 0.0f, 180f); + } + else + { + ___JudgeObj.Flip(true); + } + } + } +} \ No newline at end of file diff --git a/AquaMai/RenderTweak/TrackStartProcessTweak.cs b/AquaMai/RenderTweak/TrackStartProcessTweak.cs new file mode 100644 index 00000000..d547f595 --- /dev/null +++ b/AquaMai/RenderTweak/TrackStartProcessTweak.cs @@ -0,0 +1,83 @@ +using HarmonyLib; +using Monitor; +using Process; +using UI; +using UnityEngine; + +namespace AquaMai.RenderTweak; + +public class TrackStartProcessTweak +{ + // 总之这个 Patch 没啥用, 是我个人用 sinmai 录谱面确认时用得到, 顺手也写进来了 + // 具体而言就是推迟了歌曲开始界面的动画便于后期剪辑 + // 然后把“TRACK X”字样和 DX/标准谱面的显示框隐藏掉, 让他看起来不那么 sinmai, 更像是 majdata + + [HarmonyPrefix] + [HarmonyPatch(typeof(TrackStartProcess), "OnUpdate")] + private static bool DelayAnimation( + TrackStartProcess.TrackStartSequence ____state, + ref float ____timeCounter, + ProcessDataContainer ___container + ) + { + if (____state == TrackStartProcess.TrackStartSequence.Wait) + { + // 将开始动画(就是“噔噔, 噔 噔噔”)推迟 + float temp = ____timeCounter + Time.deltaTime; + if (____timeCounter < 1.0f && temp >= 1.0f) + { + // 这是用来让转场动画继续播放的, 原本就是这个时候 notify 的同时开始播放开始动画 + // 现在把开始动画往后延 + ___container.processManager.NotificationFadeIn(); + } + ____timeCounter = temp; + if (____timeCounter >= 3.0f) + { + return true; + // 原 method 的逻辑是这样 + // case TrackStartProcess.TrackStartSequence.Wait: + // this._timeCounter += Time.deltaTime; + // if ((double) this._timeCounter >= 1.0) + // { + // this._timeCounter = 0.0f; + // this._state = TrackStartProcess.TrackStartSequence.Disp; + // /* 一些开始播放开始动画的代码 */ + // this.container.processManager.NotificationFadeIn(); + // break; + // } + // break; + // 所以只要在 prefix 里面等到 timeCounter 达到我们想要的值以后再执行原 method 就好 + // 这里有个细节: NotificationFadeIn() 会被执行两遍, 这其实不好, 是个潜在 bug + // 不过由于此处把开始动画往后推了 2s, 转场动画已经结束把 Process 释放掉了, 所以第二遍会找不到 Process 就没效果 + } + return false; + } + else if (____state == TrackStartProcess.TrackStartSequence.DispEnd) + { + // 将开始动画结束以后的转场动画推迟 + ____timeCounter += Time.deltaTime; // timeCounter 会在先前由原本的 method 归零 + if (____timeCounter >= 1.0f) + { + return true; + } + return false; + } + return true; + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart")] + private static void DisableTabs( + SpriteCounter ____trackNumber, SpriteCounter ____bossTrackNumber, SpriteCounter ____utageTrackNumber, + MultipleImage ____musicTabImage, GameObject[] ____musicTabObj + ) + { + ____trackNumber.transform.parent.gameObject.SetActive(false); + ____bossTrackNumber.transform.parent.gameObject.SetActive(false); + ____utageTrackNumber.transform.parent.gameObject.SetActive(false); + ____musicTabImage.gameObject.SetActive(false); + ____musicTabObj[0].gameObject.SetActive(false); + ____musicTabObj[1].gameObject.SetActive(false); + ____musicTabObj[2].gameObject.SetActive(false); + } +} \ No newline at end of file diff --git a/AquaMai/UX/DemoMaster.cs b/AquaMai/UX/DemoMaster.cs index 052d0fcc..e3dd5090 100644 --- a/AquaMai/UX/DemoMaster.cs +++ b/AquaMai/UX/DemoMaster.cs @@ -16,6 +16,7 @@ public static void AdvDemoProcessPostStart() { var userOption = Singleton.Instance.GetGameScore(i).UserOption; userOption.NoteSpeed = OptionNotespeedID.Speed6_5; + userOption.TouchSpeed = OptionTouchspeedID.Speed7_0; } }