-
Notifications
You must be signed in to change notification settings - Fork 52
[+] 高仿旧框下一曲目随机图片提示功能 #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[+] 高仿旧框下一曲目随机图片提示功能 #111
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,257 @@ | ||
| using AquaMai.Config.Attributes; | ||
| using AquaMai.Core.Attributes; | ||
| using AquaMai.Core.Helpers; | ||
| using HarmonyLib; | ||
| using MAI2.Util; | ||
| using Manager; | ||
| using MelonLoader; | ||
| using Monitor; | ||
| using Process; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using UnityEngine; | ||
| using UnityEngine.UI; | ||
|
|
||
| namespace AquaMai.Mods.Fancy; | ||
|
|
||
| [ConfigSection( | ||
| "仿旧框下一曲目随机图像", | ||
| zh: "模仿旧框 FiNALE 在下一首曲目前显示随机提示图", | ||
| en: "Shows random tips image before next track, just like the good ol' FiNALE")] | ||
| [EnableGameVersion(22000)] | ||
| public class NextTrackTips | ||
| { | ||
| [ConfigEntry( | ||
| zh: "随机提示图目录,图像格式为 png", | ||
| en: "Tips image directory, only png images are supported")] | ||
| private static readonly string TipsDirectory = "LocalAssets/Tips"; | ||
|
|
||
| private static readonly List<Sprite> _nextTrackSprites = []; | ||
|
|
||
| private static bool _timeCounterChanged = false; | ||
|
|
||
| private static readonly CommonWindow[] _hackyWindows = new CommonWindow[2]; | ||
| private static IMessageMonitor[] _genericMonitorRefs = new IMessageMonitor[2]; | ||
|
|
||
| [HarmonyPrepare] | ||
| public static bool Initialize() | ||
| { | ||
| var resolvedDir = FileSystem.ResolvePath(TipsDirectory); | ||
| if (!Directory.Exists(resolvedDir)) | ||
| { | ||
| MelonLogger.Error($"[NextTrackTips] Tips directory does not exist: {resolvedDir}"); | ||
| return false; | ||
| } | ||
|
|
||
| var tipImgs = Directory.GetFiles(resolvedDir, "*.png", SearchOption.TopDirectoryOnly); | ||
|
|
||
| foreach (var tipImgPath in tipImgs) | ||
| { | ||
| try | ||
| { | ||
| var tex = new Texture2D(1, 1, TextureFormat.RGBA32, false); | ||
| tex.LoadImage(File.ReadAllBytes(tipImgPath)); | ||
| _nextTrackSprites.Add(Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f))); | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| MelonLogger.Warning($"[NextTrackTips] Failed to load image {tipImgPath}: {e}"); | ||
| } | ||
| } | ||
|
|
||
| if (_nextTrackSprites.Count < 1) | ||
| { | ||
| MelonLogger.Error($"[NextTrackTips] Tips directory seems empty or cannot load all images: {resolvedDir}"); | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(GenericProcess), "OnStart")] | ||
| public static void GenericProcess_OnStart_Postfix(GenericMonitor[] ____monitors) | ||
| { | ||
| _genericMonitorRefs = ____monitors; | ||
| } | ||
|
|
||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(GenericProcess), "OnRelease")] | ||
| public static void GenericProcess_OnRelease_Postfix() | ||
| { | ||
| _genericMonitorRefs = new IMessageMonitor[2]; | ||
| } | ||
|
|
||
| private static CommonWindow InitializeCommonWindowObject(CommonWindow prefab, Transform parent, int monitorIndex) | ||
| { | ||
| var window = UnityEngine.Object.Instantiate(prefab, parent); | ||
|
|
||
| window.Prepare( | ||
| _genericMonitorRefs[monitorIndex], | ||
| DB.WindowMessageID.NextTrackTips01, | ||
| DB.WindowPositionID.Middle, | ||
| Vector3.zero, | ||
| new WindowParam | ||
| { | ||
| changeSize = true, | ||
| sizeID = DB.WindowSizeID.LargeHorizontal, | ||
| hideTitle = true, | ||
| replaceText = true, | ||
| text = "", | ||
| directSprite = true, | ||
| sprite = _nextTrackSprites[UnityEngine.Random.Range(0, _nextTrackSprites.Count)] | ||
| } | ||
| ); | ||
|
|
||
| // Some hacks to force the layout and "fix" spacing | ||
| var winLayout = window.transform.Find("IMG_Window").gameObject.GetComponent<HorizontalLayoutGroup>(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| winLayout.spacing = 0.0f; | ||
| winLayout.padding = new RectOffset(40, 40, 40, 40); | ||
|
|
||
| return window; | ||
| } | ||
|
|
||
| #region NextTrackProcess Patch | ||
| private static bool CheckNextTrackProcess(NextTrackProcess.NextTrackMode mode) | ||
| { | ||
| return mode != NextTrackProcess.NextTrackMode.FreedomTimeup && mode != NextTrackProcess.NextTrackMode.NeedAwake && mode != NextTrackProcess.NextTrackMode.GotoEnd; | ||
| } | ||
|
|
||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(NextTrackProcess), "ProcessingProcess")] | ||
| public static void ProcessingProcess_Postfix(NextTrackProcess.NextTrackMode ____mode, ref float ____timeCounter) | ||
| { | ||
| if (CheckNextTrackProcess(____mode) && !_timeCounterChanged) | ||
| { | ||
| ____timeCounter = 5f; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| _timeCounterChanged = true; | ||
| } | ||
| } | ||
|
|
||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(NextTrackProcess), "OnStart")] | ||
| public static void OnStart_Postfix(NextTrackMonitor[] ____monitors, NextTrackProcess.NextTrackMode ____mode) | ||
| { | ||
| if (!CheckNextTrackProcess(____mode)) | ||
| return; | ||
|
|
||
| var commonWindowPref = Resources.Load<GameObject>("Process/Generic/GenericProcess").transform.Find("Canvas/Main/MessageRoot/HorizontalSplitWindow").gameObject.GetComponent<CommonWindow>(); | ||
WUGqnwvMQPzl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| for (int i = 0; i < ____monitors.Length; ++i) | ||
| { | ||
| var currUser = Singleton<UserDataManager>.Instance.GetUserData(i); | ||
| if (currUser == null || !currUser.IsActiveUser) | ||
| continue; | ||
|
|
||
| var mainCanvas = ____monitors[i].transform.Find("Canvas/Main"); | ||
| _hackyWindows[i] = InitializeCommonWindowObject(commonWindowPref, mainCanvas.transform, i); | ||
|
|
||
| // Play the sound effects and voice line | ||
| SoundManager.PlaySE(Mai2.Mai2Cue.Cue.JINGLE_NEXT_TRACK, i); | ||
|
|
||
| Mai2.Voice_Partner_000001.Cue nextTrackVoice = UnityEngine.Random.Range(0, 2) == 0 ? Mai2.Voice_Partner_000001.Cue.VO_000151 : Mai2.Voice_Partner_000001.Cue.VO_000152; | ||
| if (GameManager.MusicTrackNumber + 1U == GameManager.GetMaxTrackCount()) | ||
| nextTrackVoice = Mai2.Voice_Partner_000001.Cue.VO_000153; | ||
| SoundManager.PlayPartnerVoice(nextTrackVoice, i); | ||
| } | ||
| } | ||
|
|
||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(NextTrackProcess), "OnLateUpdate")] | ||
| public static void OnLateUpdate_Postfix(NextTrackMonitor[] ____monitors) | ||
| { | ||
| for (int i = 0; i < ____monitors.Length; ++i) | ||
| _hackyWindows[i]?.UpdateView(GameManager.GetGameMSecAdd()); | ||
| } | ||
|
|
||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(NextTrackProcess), "StartFadeIn")] | ||
| public static void StartFadeIn_Postfix(NextTrackMonitor[] ____monitors) | ||
| { | ||
| for (int i = 0; i < ____monitors.Length; ++i) | ||
| _hackyWindows[i]?.Close(); | ||
| } | ||
|
|
||
| [HarmonyPrefix] | ||
| [HarmonyPatch(typeof(NextTrackProcess), "OnRelease")] | ||
| public static void OnRelease_Prefix(NextTrackMonitor[] ____monitors) | ||
| { | ||
| for (int i = 0; i < ____monitors.Length; ++i) | ||
| { | ||
| if (_hackyWindows[i] != null) | ||
| { | ||
| UnityEngine.Object.Destroy(_hackyWindows[i]); | ||
| _hackyWindows[i] = null; | ||
| } | ||
| } | ||
|
|
||
| _timeCounterChanged = false; | ||
| } | ||
| #endregion | ||
|
|
||
| #region KaleidxScopeFadeProcess Patch | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 用于 patch 可以将其重构为共享的辅助方法以提高可维护性。例如,你可以创建如下的辅助方法:
然后 patch 方法只需调用这些辅助方法。主要的区别在于监视器/控制器列表的来源,可以将其作为参数传递。 |
||
| [EnableGameVersion(25000, noWarn: true)] | ||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(KaleidxScopeFadeProcess), "OnStart")] | ||
| public static void KS_OnStart_Postfix(ProcessBase ___toProcess, List<KaleidxScopeFadeController> ___mainControllerList) | ||
| { | ||
| if (___toProcess.GetType() != typeof(MusicSelectProcess) || GameManager.MusicTrackNumber < 2) // WTF SBGA??? | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return; | ||
|
|
||
| var commonWindowPref = Resources.Load<GameObject>("Process/Generic/GenericProcess").transform.Find("Canvas/Main/MessageRoot/HorizontalSplitWindow").gameObject.GetComponent<CommonWindow>(); | ||
|
|
||
| // WTF SBGA? | ||
| for (int i = 0; i < ___mainControllerList.Count; ++i) | ||
| { | ||
| var currUser = Singleton<UserDataManager>.Instance.GetUserData(i); | ||
| if (currUser == null || !currUser.IsActiveUser) | ||
| continue; | ||
|
|
||
| _hackyWindows[i] = InitializeCommonWindowObject(commonWindowPref, ___mainControllerList[i].transform, i); | ||
|
|
||
| // Play the sound effects and voice line | ||
| SoundManager.PlaySE(Mai2.Mai2Cue.Cue.JINGLE_NEXT_TRACK, i); | ||
|
|
||
| Mai2.Voice_Partner_000001.Cue nextTrackVoice = UnityEngine.Random.Range(0, 2) == 0 ? Mai2.Voice_Partner_000001.Cue.VO_000151 : Mai2.Voice_Partner_000001.Cue.VO_000152; | ||
| // WTF SBGA?? | ||
| if (GameManager.MusicTrackNumber == GameManager.GetMaxTrackCount()) | ||
| nextTrackVoice = Mai2.Voice_Partner_000001.Cue.VO_000153; | ||
| SoundManager.PlayPartnerVoice(nextTrackVoice, i); | ||
| } | ||
| } | ||
|
|
||
| [EnableGameVersion(25000, noWarn: true)] | ||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(KaleidxScopeFadeProcess), "OnLateUpdate")] | ||
| public static void KS_OnLateUpdate_Postfix(List<KaleidxScopeFadeController> ___mainControllerList, KaleidxScopeFadeState ___stateMachine) | ||
| { | ||
| for (int i = 0; i < ___mainControllerList.Count; ++i) | ||
| _hackyWindows[i]?.UpdateView(GameManager.GetGameMSecAdd()); | ||
| } | ||
|
|
||
| [EnableGameVersion(25000, noWarn: true)] | ||
| [HarmonyPostfix] | ||
| [HarmonyPatch(typeof(KaleidxScopeFadeProcess), "StartFadeIn")] | ||
| public static void KS_StartFadeIn_Postfix(List<KaleidxScopeFadeController> ___mainControllerList) | ||
| { | ||
| for (int i = 0; i < ___mainControllerList.Count; ++i) | ||
| _hackyWindows[i]?.Close(); | ||
| } | ||
|
|
||
| [EnableGameVersion(25000, noWarn: true)] | ||
| [HarmonyPrefix] | ||
| [HarmonyPatch(typeof(KaleidxScopeFadeProcess), "OnRelease")] | ||
| public static void KS_OnRelease_Prefix(List<KaleidxScopeFadeController> ___mainControllerList) | ||
| { | ||
| for (int i = 0; i < ___mainControllerList.Count; ++i) | ||
| { | ||
| if (_hackyWindows[i] != null) | ||
| { | ||
| UnityEngine.Object.Destroy(_hackyWindows[i]); | ||
| _hackyWindows[i] = null; | ||
| } | ||
| } | ||
| } | ||
| #endregion | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
数组
_hackyWindows和_genericMonitorRefs被初始化为硬编码的大小 2。虽然游戏通常可能在 2 个监视器上运行,但依赖这一点会使代码变得脆弱。如果监视器的数量发生变化,可能会导致IndexOutOfRangeException,因为循环是基于____monitors.Length进行迭代的。更健壮的做法是使用
List<T>,或者在首次需要时(例如在OnStart方法中)动态确定数组大小。例如,对于
_hackyWindows,可以在OnStart_Postfix中这样做:_hackyWindows = new CommonWindow[____monitors.Length];然后继续执行循环。
对于
_genericMonitorRefs,它在GenericProcess_OnStart_Postfix中被重新赋值,但在GenericProcess_OnRelease_Postfix中被重置为一个固定大小的数组。更好的做法是在释放时将其设置为Array.Empty<IMessageMonitor>()或null。