diff --git a/AquaMai.Mods/Fancy/SetFade.cs b/AquaMai.Mods/Fancy/SetFade.cs index 83d941e0..27c91af3 100644 --- a/AquaMai.Mods/Fancy/SetFade.cs +++ b/AquaMai.Mods/Fancy/SetFade.cs @@ -4,118 +4,248 @@ using Process; using UnityEngine; using UnityEngine.UI; -using System.Reflection; using System; +using System.IO; +using Manager; using MelonLoader; +using MelonLoader.TinyJSON; +using System.Collections.Generic; +using Monitor; +using Mono.Posix; namespace AquaMai.Mods.Fancy; -[ConfigSection( - name: "转场动画", +[ConfigSection(name: "转场动画PLUS", en: "Set Fade Animation", - zh: "修改转场动画为其他变种" -)] - + zh: "修改转场动画为其他变种")] public class SetFade { - [ConfigEntry( - name: "转场类型", - en: "Type: Non-Plus 0, Plus 1. (If SDEZ 1.60 can choose Festa 2)", - zh: "类型: Non-Plus 0, Plus 1. (SDEZ 1.60 限定可选 Festa 2)")] - public static readonly int FadeType = 0; + [ConfigEntry(name: "转场类型", zh: "0:Normal, 1:Plus, 2:Festa(仅限1.60+),5:禁用")] + public static readonly int FadeType = 5; + [ConfigEntry(name: "[仅限1.55+]启用特殊KLD转场", zh: "仅在配置过的歌曲启用KLD转场。")] + public static readonly bool isKLDEnabled = true; + private static readonly string JSONDir = "LocalAssets"; + private static readonly string JSONFileName = "CommonFadeList.json"; - private static bool isInitialized = false; private static bool isResourcePatchEnabled = false; + private static bool _isInitialized = false; private static Sprite[] subBGs = new Sprite[3]; + private static List cachedEntries = new List(); + private static int _kldRemainingCharges = 0; + internal static CommonFadeEntry _activeKldConfig = null; - [HarmonyPrepare] - public static bool SetFade_Prepare() + // --- TinyJson 数据模型 --- + // 注意:字段名必须与 JSON 中的 Key 完全一致,且必须为 public + public class CommonFadeEntry { - SetFade_Initialize(); - if (!isInitialized) - MelonLogger.Msg("[SetFade] Initialization failed, this patch will not be applied."); - return isInitialized; + public int ID; + public int isBlack; + public int Type; + public int FadeType; } - private static void SetFade_Initialize() + [HarmonyPrepare] + public static bool Prepare() { - bool areSubBGsValid; - bool isFadeTypeValid; + if (_isInitialized) return true; + subBGs[0] = Resources.Load("Process/ChangeScreen/Sprites/Sub_01"); + subBGs[1] = Resources.Load("Process/ChangeScreen/Sprites/Sub_02"); + subBGs[2] = (GameInfo.GameVersion >= 26000) ? Resources.Load("Process/ChangeScreen/Sprites/Sub_03") : subBGs[0]; - if (GameInfo.GameVersion != 26000) - { - subBGs[0] = Resources.Load("Process/ChangeScreen/Sprites/Sub_01"); - subBGs[1] = Resources.Load("Process/ChangeScreen/Sprites/Sub_02"); - areSubBGsValid = subBGs[0] != null && subBGs[1] != null; - isFadeTypeValid = FadeType == 0 || FadeType == 1; - } - else + LoadJsonManual(); + + _isInitialized = true; + return true; + } + + // --- 1. 核心解析逻辑:使用 TinyJson --- + private static void LoadJsonManual() + { + try { - subBGs[0] = Resources.Load("Process/ChangeScreen/Sprites/Sub_01"); - subBGs[1] = Resources.Load("Process/ChangeScreen/Sprites/Sub_02"); - subBGs[2] = Resources.Load("Process/ChangeScreen/Sprites/Sub_03"); - areSubBGsValid = subBGs[0] != null && subBGs[1] != null && subBGs[2] != null; - isFadeTypeValid = FadeType == 0 || FadeType == 1 || FadeType == 2; - } + string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, JSONDir, JSONFileName); + if (!File.Exists(path)) + { + MelonLogger.Warning($"[SetFade] 配置文件未找到: {path}"); + return; + } + + string jsonContent = File.ReadAllText(path); + cachedEntries.Clear(); - if (!areSubBGsValid) - MelonLogger.Msg($"[SwitchFade] Couldn't find SubBG sprites."); + // --- 修复 Line 80 --- + // 错误写法: var data = Process.TinyJson.FromJson>(jsonContent); + // 正确写法 (使用 MelonLoader.TinyJSON): - if (!isFadeTypeValid) - MelonLogger.Msg($"[SwitchFade] Invalid FadeType."); + var variant = JSON.Load(jsonContent); // 1. 先载入为 Variant 对象 + if (variant != null) + { + var data = variant.Make>(); // 2. 再转换为具体的 List - isInitialized = areSubBGsValid && isFadeTypeValid; + if (data != null) + { + cachedEntries = data; + MelonLogger.Msg($"[SetFade] 成功通过 MelonLoader.TinyJSON 载入 {cachedEntries.Count} 条配置。"); + } + } + } + catch (Exception e) + { + MelonLogger.Error($"[SetFade] JSON 解析失败! 请检查格式。错误: {e.Message}"); + } } + // --- 2. 选曲监听与充能 --- + [HarmonyPostfix] + [HarmonyPatch(typeof(MusicSelectMonitor), "UpdateRivalScore")] + [HarmonyPatch(typeof(MusicSelectMonitor), "SetRivalScore")] + public static void OnMusicSelectionChanged(MusicSelectProcess ____musicSelect) + { + if (!isKLDEnabled || ____musicSelect == null) return; + try + { + var musicData = ____musicSelect.GetMusic(0)?.MusicData; + if (musicData != null) + { + var matched = cachedEntries.Find(e => e.ID == musicData.name.id); + if (matched != null) + { + if (_activeKldConfig != matched) + { + _activeKldConfig = matched; + _kldRemainingCharges = 3; + MelonLogger.Msg($"[SetFade] 目标锁定:ID {matched.ID},KLD 已充能 (3次)"); + } + } + else + { + _activeKldConfig = null; + _kldRemainingCharges = 0; + } + } + } + catch { } + } - // 在显示转场前启用patch + // --- 3. 资源拦截与重定向 --- [HarmonyPrefix] [HarmonyPatch(typeof(FadeProcess), "OnStart")] - public static void FadeProcessOnStartPreFix() { isResourcePatchEnabled = true; } - [HarmonyPrefix] [HarmonyPatch(typeof(AdvertiseProcess), "InitFade")] - public static void AdvertiseProcessInitFadePreFix() { isResourcePatchEnabled = true; } - [HarmonyPrefix] [HarmonyPatch(typeof(NextTrackProcess), "OnStart")] - public static void NextTrackProcessOnStartPreFix() { isResourcePatchEnabled = true; } + public static void StartFadePrefix() + { + isResourcePatchEnabled = (_kldRemainingCharges > 0 && _activeKldConfig != null); + } - // 在显示转场后禁用patch + [HarmonyPrefix] + [HarmonyPatch(typeof(Resources), "Load", new[] { typeof(string), typeof(global::System.Type) })] + public static bool ResourcesLoadPrefix(ref string path, global::System.Type systemTypeInstance, ref UnityEngine.Object __result) + { + if (!isResourcePatchEnabled) + { + if (FadeType >= 0 && FadeType <= 2) + { + string targetPath = $"Process/ChangeScreen/Prefabs/ChangeScreen_0{FadeType + 1}"; + if (path.StartsWith("Process/ChangeScreen/Prefabs/ChangeScreen_0") && path != targetPath) + { + if (GameInfo.GameVersion < 26000 && FadeType == 2) return true; + __result = Resources.Load(targetPath, systemTypeInstance); + return false; + } + } + return true; + } + + if (path.StartsWith("Process/ChangeScreen/Prefabs/ChangeScreen_0")) + { + __result = Resources.Load("Process/Kaleidxscope/Prefab/UI_KLD_ChangeScreen", systemTypeInstance); + return false; + } + if (path.StartsWith("Process/ChangeScreen/Prefabs/Sub_ChangeScreen")) + { + __result = Resources.Load("Process/Kaleidxscope/Prefab/UI_KLD_Sub_ChangeScreen", systemTypeInstance); + return false; + } + return true; + } + + // --- 4. 动画驱动 --- [HarmonyPostfix] [HarmonyPatch(typeof(FadeProcess), "OnStart")] - public static void FadeProcessOnStartPostFix(GameObject[] ___fadeObject) { ReplaceSubBG(___fadeObject); } - [HarmonyPostfix] [HarmonyPatch(typeof(AdvertiseProcess), "InitFade")] - public static void AdvertiseProcessInitFadePostFix(GameObject[] ___fadeObject) { ReplaceSubBG(___fadeObject); } - [HarmonyPostfix] [HarmonyPatch(typeof(NextTrackProcess), "OnStart")] - public static void NextTrackProcessOnStartPostFix(GameObject[] ___fadeObject) { ReplaceSubBG(___fadeObject); } - - - private static void ReplaceSubBG(GameObject[] fadeObjects) + public static void GlobalPostfix(GameObject[] ___fadeObject) { + if (isResourcePatchEnabled && _activeKldConfig != null) + { + _kldRemainingCharges--; + if (___fadeObject != null) + { + foreach (var monitor in ___fadeObject) + DriveKLDAnimation(monitor, _activeKldConfig); + } + } + else if (___fadeObject != null) + { + foreach (var monitor in ___fadeObject) + ReplaceSubBG(monitor); + } isResourcePatchEnabled = false; - foreach (var monitor in fadeObjects) + if (_kldRemainingCharges <= 0) _activeKldConfig = null; + } + + private static void ReplaceSubBG(GameObject monitor) + { + if (FadeType < 0 || FadeType >= subBGs.Length) return; + try { - var subBG = monitor.transform.Find("Canvas/Sub/Sub_ChangeScreen(Clone)/Sub_BG").GetComponent(); - subBG.sprite = subBGs[FadeType]; + var subBG = monitor.transform.Find("Canvas/Sub/Sub_ChangeScreen(Clone)/Sub_BG")?.GetComponent(); + if (subBG != null) subBG.sprite = subBGs[FadeType]; } + catch { } } - [HarmonyPrefix] - [HarmonyPatch(typeof(Resources), "Load", new[] { typeof(string), typeof(Type) })] - public static bool ResourcesLoadPrefix(ref string path, Type systemTypeInstance, ref UnityEngine.Object __result) + private static void DriveKLDAnimation(GameObject monitor, CommonFadeEntry cfg) { - if (isResourcePatchEnabled) + try { - if (path.StartsWith("Process/ChangeScreen/Prefabs/ChangeScreen_0") && - path != $"Process/ChangeScreen/Prefabs/ChangeScreen_0{FadeType + 1}") // 避免无限递归 + var main = monitor.transform.Find("Canvas/Main/UI_KLD_ChangeScreen(Clone)"); + var sub = monitor.transform.Find("Canvas/Sub/UI_KLD_Sub_ChangeScreen(Clone)"); + + string animName = cfg.FadeType switch + { + 1 => "In", + 2 => "Out_02", + 3 => "Out_03", + _ => "In" + }; + + if (main != null) { - __result = Resources.Load($"Process/ChangeScreen/Prefabs/ChangeScreen_0{FadeType + 1}", systemTypeInstance); - return false; + var ctrl = main.GetComponent(); + if (ctrl != null) + { + ctrl.SetBackGroundType(cfg.isBlack != 0 ? KaleidxScopeFadeController.BackGroundType.Black : KaleidxScopeFadeController.BackGroundType.Normal); + if (cfg.Type == 10) ctrl.SetSpriteType((KaleidxScopeFadeController.SpriteType)7); + else ctrl.SetSpriteType((KaleidxScopeFadeController.SpriteType)cfg.Type); + if (Enum.TryParse(animName, out var state)) + ctrl.PlayAnimation(state); + } + } + if (sub != null) + { + var sCtrl = sub.GetComponent(); + if (sCtrl != null) + { + sCtrl.SetBackGroundType(cfg.isBlack != 0 ? KaleidxScopeSubFadeController.BackGroundType.Black : KaleidxScopeSubFadeController.BackGroundType.Normal); + if (cfg.Type == 10) sCtrl.SetSpriteType((KaleidxScopeSubFadeController.SpriteType)7); + else sCtrl.SetSpriteType((KaleidxScopeSubFadeController.SpriteType)cfg.Type); + sCtrl.PlayAnimation(KaleidxScopeSubFadeController.AnimState.In); + } } } - return true; + catch { } } } diff --git a/AquaMai.Mods/Fancy/SetTrackStart.cs b/AquaMai.Mods/Fancy/SetTrackStart.cs new file mode 100644 index 00000000..da9ca636 --- /dev/null +++ b/AquaMai.Mods/Fancy/SetTrackStart.cs @@ -0,0 +1,145 @@ +using HarmonyLib; +using AquaMai.Config.Attributes; +using Process; +using Manager; +using MAI2.Util; +using Monitor; +using System.Reflection; +using UnityEngine; +using Mai2.Mai2Cue; +using MelonLoader; + +namespace AquaMai.Mods.Fancy; + +[ConfigSection(name: "开场动画PLUS(需配合转场动画使用)", + en: "Set Track Start Animation", + zh: "同步修改开场动画为其他变种")] +public class SetTrackStart +{ + private static SetFade.CommonFadeEntry GetActiveConfig() => + typeof(SetFade).GetField("_activeKldConfig", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as SetFade.CommonFadeEntry; + + private static bool _isModActive = false; + private static float _timer = 0f; + private const float MAX_ANIM_TIME = 4.5f; + + // --- 修改:仅禁用 Active,不销毁对象,防止动画错位 --- + private static void DisableSpecificUI(TrackStartMonitor monitor) + { + if (monitor == null) return; + + // 向上找到当前屏幕对应的根节点 TrackStartProcess(Clone) + Transform root = monitor.transform; + while (root.parent != null && !root.name.Contains("TrackStartProcess")) + root = root.parent; + + // 目标 1: LifeGuage + Transform lifeGuage = root.Find("Canvas/Main/Null_UI_Kaleid_TS/UI_Kaleid_TS/UI/LifeGuage"); + if (lifeGuage != null) + { + lifeGuage.gameObject.SetActive(false); + MelonLogger.Msg("[V52] 已禁用 LifeGuage (SetActive: false)"); + } + + // 目标 2: TrackStart_head + Transform head = root.Find("Canvas/Main/Null_UI_Kaleid_TS/UI_Kaleid_TS/UI/TrackStart_head"); + if (head != null) + { + head.gameObject.SetActive(false); + MelonLogger.Msg("[V52] 已禁用 TrackStart_head (SetActive: false)"); + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart", new[] { typeof(TrackStartMonitor.TrackStartType) })] + public static bool SetTrackStartPrefix(TrackStartMonitor __instance, TrackStartMonitor.TrackStartType type) + { + if (GetActiveConfig() == null) return true; + + if (type == TrackStartMonitor.TrackStartType.Normal || type == TrackStartMonitor.TrackStartType.Versus) + { + var ctrlField = typeof(TrackStartMonitor).GetField("kaleidxScopeTrackStartController", BindingFlags.NonPublic | BindingFlags.Instance); + var ctrl = ctrlField?.GetValue(__instance) as KaleidxScopeTrackStartController; + + if (ctrl != null) + { + _isModActive = true; + _timer = 0f; + + // 初始隔离原版 UI + string[] targetNames = { "_normalObject", "_versusObject", "_objNormal", "_objVersus", "_backGround" }; + foreach (var name in targetNames) + { + var f = typeof(TrackStartMonitor).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance); + (f?.GetValue(__instance) as GameObject)?.SetActive(false); + } + + (typeof(TrackStartMonitor).GetField("_kaleidxScopeBackground", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(__instance) as GameObject)?.SetActive(true); + ctrl.gameObject.SetActive(true); + + int gateId = 1; + var config = GetActiveConfig(); + var gField = config.GetType().GetField("Type", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (gField != null) gateId = System.Convert.ToInt32(gField.GetValue(config)); + + ctrl.PlayAnimation((KaleidxScopeTrackStartController.AnimState)(gateId % 11)); + ctrl.SetTrackNum(GameManager.MusicTrackNumber); + + // 内部数值依然设为0 + ctrl.SetLife(0); + + // --- 修改:调用禁用函数而非删除函数 --- + DisableSpecificUI(__instance); + + SoundManager.PlaySE(Cue.SE_TRACK_START_KALEID, __instance.MonitorIndex); + return false; + } + } + return true; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(TrackStartMonitor), "IsEnd")] + public static bool IsEndPrefix(TrackStartMonitor __instance, ref bool __result) + { + if (!_isModActive) return true; + _timer += Time.deltaTime; + + var ctrlField = typeof(TrackStartMonitor).GetField("kaleidxScopeTrackStartController", BindingFlags.NonPublic | BindingFlags.Instance); + var ctrl = ctrlField?.GetValue(__instance) as KaleidxScopeTrackStartController; + + if (ctrl != null) + { + if (ctrl.PlayEnded() || _timer >= MAX_ANIM_TIME) + { + __result = true; + } + else + { + __result = false; + } + return false; + } + return true; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(TrackStartProcess), "OnUpdate")] + public static bool ProcessUpdatePrefix(TrackStartProcess __instance) + { + if (!_isModActive) return true; + + var stateField = typeof(TrackStartProcess).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance); + var currentState = (TrackStartProcess.TrackStartSequence)stateField.GetValue(__instance); + + if (currentState == TrackStartProcess.TrackStartSequence.DispEnd) + { + // 这里保持逻辑,动画结束后会自然销毁或释放进程 + } + return true; + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(TrackStartProcess), "OnRelease")] + public static void OnReleasePostfix() => _isModActive = false; +}