[+] Slide code support & split multiple patches (#77)

* 功能拆分

将不同的功能分拆到不同文件

* Slide code notation support

This is part of Maimai DX 2077 patch set.
New MA2 commands: NMSSS, BRSSS, EXSSS, BXSSS, CNSSS
This commit is contained in:
Minepig
2024-10-25 20:42:08 +08:00
committed by GitHub
parent 98213cff67
commit d0bb3cc75c
21 changed files with 2078 additions and 111 deletions

View File

@@ -10,13 +10,18 @@ using UnityEngine;
namespace AquaMai.UX;
public class CustomNoteSkin
public class CustomSkins
{
private static readonly List<string> ImageExts = [".png", ".jpg", ".jpeg"];
private static readonly List<string> SlideFanFields = ["_normalSlideFan", "_eachSlideFan", "_breakSlideFan", "_breakSlideFanEff"];
private static readonly List<string> CustomTrackStartFields = ["_musicBase", "_musicTab", "_musicLvBase", "_musicLvText"];
private static Sprite customOutline;
private static Sprite[,] customSlideFan = new Sprite[4, 11];
public static readonly Sprite[,] CustomJudge = new Sprite[2, ((int)NoteJudge.ETiming.End + 1)];
public static readonly Sprite[,,,] CustomJudgeSlide = new Sprite[2, 3, 2, ((int)NoteJudge.ETiming.End + 1)];
public static readonly Texture2D[] CustomTrackStart = new Texture2D[4];
private static bool LoadIntoGameNoteImageContainer(string fieldName, int? idx1, int? idx2, Texture2D texture)
{
@@ -105,8 +110,17 @@ public class CustomNoteSkin
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);
int? idx3 = (args.Length < 4) ? null : (int.TryParse(args[3], out temp) ? temp : null);
Traverse traverse;
if (CustomTrackStartFields.Contains(fieldName))
{
var i = CustomTrackStartFields.IndexOf(fieldName);
CustomTrackStart[i] = texture;
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
continue;
}
if (fieldName == "_outline")
{
@@ -115,6 +129,59 @@ public class CustomNoteSkin
continue;
}
if (fieldName == "_judgeNormal" || fieldName == "_judgeBreak")
{
if (!idx1.HasValue)
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
continue;
}
var i = (fieldName == "_judgeBreak") ? 1 : 0;
CustomJudge[i, idx1.Value] = 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 (fieldName == "_judgeSlideNormal" || fieldName == "_judgeSlideBreak")
{
if (!idx1.HasValue || !idx2.HasValue || !idx3.HasValue)
{
MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs 3 indices");
continue;
}
var i = (fieldName == "_judgeSlideBreak") ? 1 : 0;
Vector2 pivot;
switch (idx1.Value)
{
case 0 when idx2.Value == 0:
pivot = new Vector2(0f, 0.5f);
break;
case 0 when idx2.Value == 1:
pivot = new Vector2(1f, 0.5f);
break;
case 1 when idx2.Value == 0:
pivot = new Vector2(0f, 0.3f);
break;
case 1 when idx2.Value == 1:
pivot = new Vector2(1f, 0.3f);
break;
case 2 when idx2.Value == 0:
pivot = new Vector2(0.5f, 0.8f);
break;
case 2 when idx2.Value == 1:
pivot = new Vector2(0.5f, 0.2f);
break;
default:
pivot = new Vector2(0.5f, 0.5f);
break;
}
CustomJudgeSlide[i, idx1.Value, idx2.Value, idx3.Value] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f);
MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
continue;
}
if (SlideFanFields.Contains(fieldName))
{
if (!idx1.HasValue)

View File

@@ -0,0 +1,66 @@
using System.Collections.Generic;
using HarmonyLib;
using Monitor;
using UI;
using UnityEngine;
using UnityEngine.UI;
namespace AquaMai.UX;
public class CustomTrackStartDiff
{
// 自定义在歌曲开始界面上显示的难度 (并不是真的自定义难度)
// 需要启用自定义皮肤功能
// 会加载四个图片资源: musicBase, musicTab, musicLvBase, musicLvText
[HarmonyPostfix]
[HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart")]
private static void DisableTabs(
MultipleImage ____musicBaseImage,
MultipleImage ____musicTabImage,
SpriteCounter ____difficultySingle,
SpriteCounter ____difficultyDouble,
Image ____levelTextImage,
List<ResultMonitor.SpriteSheet> ____musicLevelSpriteSheets,
TimelineRoot ____musicDetail
)
{
var texture = CustomSkins.CustomTrackStart[0];
if (texture != null)
{
____musicBaseImage.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f);
____musicBaseImage.ChangeSprite(6);
}
texture = CustomSkins.CustomTrackStart[1];
if (texture != null)
{
____musicTabImage.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f);
____musicTabImage.ChangeSprite(6);
}
texture = CustomSkins.CustomTrackStart[2];
if (texture != null)
{
var lvBase = Traverse.Create(____musicDetail).Field<MultipleImage>("_lv_Base").Value;
lvBase.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f);
lvBase.ChangeSprite(6);
}
texture = CustomSkins.CustomTrackStart[3];
if (texture != null)
{
var original = ____musicLevelSpriteSheets[0].Sheet;
var sheet = new Sprite[original.Length];
for (var i = 0; i < original.Length; i++)
{
var sprite = original[i];
sheet[i] = Sprite.Create(texture, sprite.textureRect, new Vector2(0.5f, 0.5f), 100f);
}
____difficultySingle.SetSpriteSheet(sheet);
____difficultyDouble.SetSpriteSheet(sheet);
____levelTextImage.sprite = sheet[14];
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using HarmonyLib;
using Monitor;
using UI;
using UnityEngine;
using UnityEngine.UI;
namespace AquaMai.UX;
public class DisableTrackStartTabs
{
// 在歌曲开始界面, 把 TRACK X 字样, DX/标准谱面的显示框, 以及画面下方的滴蜡熊隐藏掉, 让他看起来不那么 sinmai, 更像是 majdata
[HarmonyPostfix]
[HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart")]
private static void DisableTabs(
SpriteCounter ____trackNumber, SpriteCounter ____bossTrackNumber, SpriteCounter ____utageTrackNumber,
MultipleImage ____musicTabImage, GameObject[] ____musicTabObj, GameObject ____derakkumaRoot
)
{
____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);
____derakkumaRoot.SetActive(false);
}
}

View File

@@ -0,0 +1,75 @@
using HarmonyLib;
using Manager;
using Monitor;
using UnityEngine;
namespace AquaMai.UX;
public class JudgeDisplay4B
{
// 精确到子判定的自定义判定显示, 需要启用自定义皮肤功能 (理论上不启用自定义皮肤不会崩游戏, 只不过此时这个功能显然不会生效)
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideJudge), "Initialize")]
private static void SlideJudgeDisplay4B(
SpriteRenderer ___SpriteRenderAdd, SpriteRenderer ___SpriteRender,
SlideJudge.SlideJudgeType ____judgeType, SlideJudge.SlideAngle ____angle,
NoteJudge.ETiming judge, float msec, bool isBreak
)
{
var i = isBreak ? 1 : 0;
Sprite sprite = CustomSkins.CustomJudgeSlide[i, (int)____judgeType, (int)____angle, (int)judge];
if (sprite != null) {
___SpriteRender.sprite = sprite;
}
if (isBreak && judge == NoteJudge.ETiming.Critical)
{
sprite = CustomSkins.CustomJudgeSlide[i, (int)____judgeType, (int)____angle, (int) NoteJudge.ETiming.End];
if (sprite != null)
{
___SpriteRenderAdd.sprite = sprite;
}
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(JudgeGrade), "Initialize")]
private static void JudgeGradeDisplay4B(
SpriteRenderer ___SpriteRender,
NoteJudge.ETiming judge, float msec, NoteJudge.EJudgeType type
)
{
var i = (type == NoteJudge.EJudgeType.Break) ? 1 : 0;
Sprite sprite = CustomSkins.CustomJudge[i, (int)judge];
if (sprite != null) {
___SpriteRender.sprite = sprite;
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(JudgeGrade), "InitializeBreak")]
private static void JudgeGradeBreakDisplay4B(
SpriteRenderer ___SpriteRenderAdd,
NoteJudge.ETiming judge, float msec, NoteJudge.EJudgeType type
)
{
if (judge == NoteJudge.ETiming.Critical)
{
var sprite = CustomSkins.CustomJudge[1, (int) NoteJudge.ETiming.End];
if (sprite != null)
{
___SpriteRenderAdd.sprite = sprite;
}
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(JudgeGrade), "InitializeBreak")]
private static void InitializeBreakFix(ref NoteJudge.EJudgeType type)
{
type = NoteJudge.EJudgeType.Break;
}
}

View File

@@ -0,0 +1,25 @@
using HarmonyLib;
using Manager;
namespace AquaMai.UX;
public class RealisticRandomJudge
{
// 让 AutoPlay 的随机判定模式真的会随机产生所有的判定 (精确到子判定)
// 原本的随机判定只会等概率产生 Critical, LateGreat1st, LateGood, Miss(TooLate)
// 这里改成三角分布产生从 Miss(TooFast) ~ Critical ~ Miss(TooLate) 的所有 15 种判定结果
// 当然, 此处并不会考虑原本那个 Note 是不是真的有对应的判定 (比如 Slide 实际上不应该有小 p 之类的)
[HarmonyPostfix]
[HarmonyPatch(typeof(GameManager), "AutoJudge")]
private static NoteJudge.ETiming RealAutoJudgeRandom(NoteJudge.ETiming retval)
{
if (GameManager.AutoPlay == GameManager.AutoPlayMode.Random)
{
var x = UnityEngine.Random.Range(0, 8);
x += UnityEngine.Random.Range(0, 8);
return (NoteJudge.ETiming) x;
}
return retval;
}
}

View File

@@ -1,8 +1,10 @@
using HarmonyLib;
using System.Collections.Generic;
using HarmonyLib;
using Monitor;
using Process;
using UI;
using UnityEngine;
using UnityEngine.UI;
namespace AquaMai.UX;
@@ -10,7 +12,6 @@ public class TrackStartProcessTweak
{
// 总之这个 Patch 没啥用, 是我个人用 sinmai 录谱面确认时用得到, 顺手也写进来了
// 具体而言就是推迟了歌曲开始界面的动画便于后期剪辑
// 然后把“TRACK X”字样和 DX/标准谱面的显示框隐藏掉, 让他看起来不那么 sinmai, 更像是 majdata
[HarmonyPrefix]
[HarmonyPatch(typeof(TrackStartProcess), "OnUpdate")]
@@ -65,19 +66,6 @@ public class TrackStartProcessTweak
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);
}
}