Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
606e372
加入力大砖飞模组
Haisairova-Official Dec 21, 2025
e514c7b
[+] 修改力大砖飞警告说明
Haisairova-Official Dec 21, 2025
5acfb37
Update AquaMai.Mods/Fancy/RsOverride.cs
Haisairova-Official Dec 21, 2025
9af16af
Update AquaMai.Mods/Fancy/RsOverride.cs
Haisairova-Official Dec 21, 2025
a0f4e65
Update AquaMai.Mods/Fancy/RsOverride.cs
Haisairova-Official Dec 21, 2025
7b69c29
Update AquaMai.Mods/Fancy/RsOverride.cs
Haisairova-Official Dec 21, 2025
692d10b
Apply suggestion from @clansty
clansty Dec 21, 2025
17c953e
[F] 修改目录避免模组冲突
Haisairova-Official Dec 21, 2025
f2917f5
Merge branch 'MuNET-OSS:main' into main
Haisairova-Official Dec 23, 2025
ec12ce6
Merge branch 'MuNET-OSS:main' into main
Haisairova-Official Jan 5, 2026
80e835c
[+] 对转场动画功能进行了史诗级加钱。
Haisairova-Official Jan 5, 2026
ebda3da
Update KLD transition config entry description
Haisairova-Official Jan 5, 2026
0ca1e8f
[F] 修复部分曲目无fade out动画的bug
Haisairova-Official Jan 5, 2026
b32ca44
Update AquaMai.Mods/Fancy/SetFade.cs
Haisairova-Official Jan 6, 2026
58b9fe8
Update AquaMai.Mods/Fancy/SetFade.cs
Haisairova-Official Jan 6, 2026
1985afb
[F] 修改JSON读取逻辑,使用Melonloader.TinyJSON。添加默认不修改转场的选项5(其实所有无效数值都可以)
Haisairova-Official Jan 6, 2026
055469f
[+,F] 追加SetTrackStart模组
Haisairova-Official Jan 8, 2026
1f43eac
[+,F] 追加SetTrackStart模组
Haisairova-Official Jan 8, 2026
a76e507
Merge branch 'main' into main
Haisairova-Official Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 197 additions & 67 deletions AquaMai.Mods/Fancy/SetFade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CommonFadeEntry> cachedEntries = new List<CommonFadeEntry>();

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<Sprite>("Process/ChangeScreen/Sprites/Sub_01");
subBGs[1] = Resources.Load<Sprite>("Process/ChangeScreen/Sprites/Sub_02");
subBGs[2] = (GameInfo.GameVersion >= 26000) ? Resources.Load<Sprite>("Process/ChangeScreen/Sprites/Sub_03") : subBGs[0];
Comment on lines +53 to +55

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

新的 Prepare 方法直接加载了 SubBG 精灵,但没有像旧的 SetFade_Initialize 方法那样检查加载结果是否为 null。如果 Resources.Load 由于某些原因(例如资源路径错误或资源损坏)失败并返回 null,那么在后续调用 ReplaceSubBG 时可能会导致 NullReferenceException

建议恢复对加载资源的空值检查,并在加载失败时记录错误日志,以提高模组的健壮性。

        subBGs[0] = Resources.Load<Sprite>("Process/ChangeScreen/Sprites/Sub_01");
        subBGs[1] = Resources.Load<Sprite>("Process/ChangeScreen/Sprites/Sub_02");
        subBGs[2] = (GameInfo.GameVersion >= 26000) ? Resources.Load<Sprite>("Process/ChangeScreen/Sprites/Sub_03") : subBGs[0];
        if (subBGs[0] == null || subBGs[1] == null || (GameInfo.GameVersion >= 26000 && subBGs[2] == null))
        {
            MelonLogger.Error("[SetFade] Failed to load one or more SubBG sprites. The mod may not function correctly.");
        }


if (GameInfo.GameVersion != 26000)
{
subBGs[0] = Resources.Load<Sprite>("Process/ChangeScreen/Sprites/Sub_01");
subBGs[1] = Resources.Load<Sprite>("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<Sprite>("Process/ChangeScreen/Sprites/Sub_01");
subBGs[1] = Resources.Load<Sprite>("Process/ChangeScreen/Sprites/Sub_02");
subBGs[2] = Resources.Load<Sprite>("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<List<CommonFadeEntry>>(jsonContent);
// 正确写法 (使用 MelonLoader.TinyJSON):

if (!isFadeTypeValid)
MelonLogger.Msg($"[SwitchFade] Invalid FadeType.");
var variant = JSON.Load(jsonContent); // 1. 先载入为 Variant 对象
if (variant != null)
{
var data = variant.Make<List<CommonFadeEntry>>(); // 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<Image>();
subBG.sprite = subBGs[FadeType];
var subBG = monitor.transform.Find("Canvas/Sub/Sub_ChangeScreen(Clone)/Sub_BG")?.GetComponent<Image>();
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<KaleidxScopeFadeController>();
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<KaleidxScopeFadeController.AnimState>(animName, out var state))
ctrl.PlayAnimation(state);
}
}
if (sub != null)
{
var sCtrl = sub.GetComponent<KaleidxScopeSubFadeController>();
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 { }
}
}
Loading