[+] Configurable mod key map manager
Build AquaMai / build (push) Has been cancelled Details

pull/81/head
Clansty 2024-11-01 17:03:57 +08:00
parent ac4db91df4
commit fb96e93184
No known key found for this signature in database
GPG Key ID: 3A6BE8BAF2EDE134
14 changed files with 331 additions and 136 deletions

View File

@ -42,6 +42,10 @@ public class Config
zh: "音符和判定表示以及一些其他贴图的视觉效果调整")]
public Visual.Config Visual { get; set; } = new();
[ConfigComment(
zh: "Mod 内功能的按键设置")]
public ModKeyMap.Config ModKeyMap { get; set; } = new();
[ConfigComment(
zh: "窗口相关设置")]
public WindowState.Config WindowState { get; set; } = new();

View File

@ -0,0 +1,28 @@
using AquaMai.Attributes;
using HarmonyLib;
using Manager;
namespace AquaMai.Fix;
[GameVersion(23000, 23499)]
public class FestivalQuickRetryFix
{
// Fix for the game not resetting Fast and Late counts when quick retrying
// For game version < 1.35.0
[HarmonyPostfix]
[HarmonyPatch(typeof(GamePlayManager), "SetQuickRetryFrag")]
public static void PostGamePlayManagerSetQuickRetryFrag(GamePlayManager __instance, bool flag)
{
// Since 1.35.0, `GameScoreList.Initialize()` resets the Fast and Late counts
if (flag && !Traverse.Create(typeof(GameScoreList)).Methods().Contains("Initialize"))
{
for (int i = 0; i < 4; i++)
{
var gameScoreList = __instance.GetGameScore(i);
var traverse = Traverse.Create(gameScoreList);
traverse.Property("Fast").SetValue((uint)0);
traverse.Property("Late").SetValue((uint)0);
}
}
}
}

View File

@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
using AquaMai.Attributes;
using AquaMai.Fix;
using AquaMai.Helpers;
using AquaMai.ModKeyMap;
using AquaMai.Resources;
using AquaMai.Utils;
using AquaMai.UX;
@ -167,6 +168,7 @@ namespace AquaMai
Patch(typeof(FixCheckAuth));
Patch(typeof(DebugFeature));
Patch(typeof(FixConnSlide));
Patch(typeof(FestivalQuickRetryFix));
// Visual
Patch(typeof(FixSlideAutoPlay)); // Rename: SlideAutoPlayTweak -> FixSlideAutoPlay, 不过这个应该无副作用所以不需要改配置文件
Patch(typeof(FixCircleSlideJudge)); // 这个我觉得算无副作用, 可以常开
@ -179,6 +181,13 @@ namespace AquaMai
// Utils
Patch(typeof(JudgeAdjust));
Patch(typeof(TouchPanelBaudRate));
// ModKeyMap
// TODO: Make it configurable
Patch(typeof(ModKeyListener));
Patch(typeof(QuickSkip));
Patch(typeof(TestProof));
Patch(typeof(PractiseMode));
Patch(typeof(HideSelfMadeCharts));
// Apply patches based on the settings
ApplyPatches();

View File

@ -0,0 +1,54 @@
using AquaMai.Attributes;
namespace AquaMai.ModKeyMap;
public class Config
{
[ConfigComment(
en: "Skip to next step",
zh: """
""")]
public ModKeyCode QuickSkip { get; set; } = ModKeyCode.None;
public bool QuickSkipLongPress { get; set; }
[ConfigComment(
en: "Quick retry in-game",
zh: "游戏内快速重试")]
public ModKeyCode InGameRetry { get; set; } = ModKeyCode.None;
public bool InGameRetryLongPress { get; set; }
[ConfigComment(
en: "Quick skip in-game",
zh: "游戏内快速跳过")]
public ModKeyCode InGameSkip { get; set; } = ModKeyCode.None;
public bool InGameSkipLongPress { get; set; }
[ConfigComment(
en: "Enter game test mode",
zh: "进入游戏测试模式")]
public ModKeyCode TestMode { get; set; } = ModKeyCode.Test;
public bool TestModeLongPress { get; set; }
[ConfigComment(
zh: "练习模式")]
public ModKeyCode PractiseMode { get; set; } = ModKeyCode.None;
public bool PractiseModeLongPress { get; set; }
[ConfigComment(
zh: "选歌界面隐藏所有自制谱")]
public ModKeyCode HideSelfMadeCharts { get; set; } = ModKeyCode.None;
public bool HideSelfMadeChartsLongPress { get; set; }
[ConfigComment(
en: "Hold the bottom four buttons (3456) for official quick retry (non-utage only)",
zh: "按住下方四个按钮3456使用官方快速重开仅对非宴谱有效")]
public bool EnableNativeQuickRetry { get; set; }
}

View File

@ -0,0 +1,19 @@
using AquaMai.Attributes;
using HarmonyLib;
using Manager;
using Monitor;
namespace AquaMai.ModKeyMap;
[GameVersion(23000)]
public class EnableNativeQuickRetry
{
[HarmonyPrefix]
[HarmonyPatch(typeof(QuickRetry), "IsQuickRetryEnable")]
public static bool OnQuickRetryIsQuickRetryEnable(ref bool __result)
{
var isUtageProperty = Traverse.Create(typeof(GameManager)).Property("IsUtage");
__result = !isUtageProperty.PropertyExists() || !isUtageProperty.GetValue<bool>();
return false;
}
}

View File

@ -13,7 +13,7 @@ using Process;
using UnityEngine;
using Util;
namespace AquaMai.UX;
namespace AquaMai.ModKeyMap;
public class HideSelfMadeCharts
{
@ -53,33 +53,20 @@ public class HideSelfMadeCharts
__result = _musicsNoneSelfMade;
}
private static int _keyPressFrames;
[HarmonyPostfix]
[HarmonyPatch(typeof(MusicSelectProcess), "OnUpdate")]
public static void MusicSelectProcessOnUpdate(ref MusicSelectProcess __instance)
{
if (isForceDisable) return;
if (Input.GetKey(KeyCode.Alpha7) || InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonService))
if (!ModKeyListener.GetKeyDownOrLongPress(AquaMai.AppConfig.ModKeyMap.HideSelfMadeCharts, AquaMai.AppConfig.ModKeyMap.HideSelfMadeChartsLongPress)) return;
isShowSelfMadeCharts = !isShowSelfMadeCharts;
MelonLogger.Msg($"[HideSelfMadeCharts] isShowSelfMadeCharts: {isShowSelfMadeCharts}");
SharedInstances.ProcessDataContainer.processManager.AddProcess(new FadeProcess(SharedInstances.ProcessDataContainer, __instance, new MusicSelectProcess(SharedInstances.ProcessDataContainer)));
Task.Run(async () =>
{
_keyPressFrames++;
}
else if (_keyPressFrames is > 0 and < 30 && !Input.GetKey(KeyCode.Alpha7) && !InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonService))
{
_keyPressFrames = 0;
isShowSelfMadeCharts = !isShowSelfMadeCharts;
MelonLogger.Msg($"[HideSelfMadeCharts] isShowSelfMadeCharts: {isShowSelfMadeCharts}");
SharedInstances.ProcessDataContainer.processManager.AddProcess(new FadeProcess(SharedInstances.ProcessDataContainer, __instance, new MusicSelectProcess(SharedInstances.ProcessDataContainer)));
Task.Run(async () =>
{
await Task.Delay(1000);
MessageHelper.ShowMessage($"{(isShowSelfMadeCharts ? "Show" : "Hide")} Self-Made Charts");
});
}
else
{
_keyPressFrames = 0;
}
await Task.Delay(1000);
MessageHelper.ShowMessage($"{(isShowSelfMadeCharts ? "Show" : "Hide")} Self-Made Charts");
});
}
[HarmonyPrefix]

View File

@ -0,0 +1,53 @@
namespace AquaMai.ModKeyMap;
public enum ModKeyCode
{
None,
Alpha0,
Alpha1,
Alpha2,
Alpha3,
Alpha4,
Alpha5,
Alpha6,
Alpha7,
Alpha8,
Alpha9,
Keypad0,
Keypad1,
Keypad2,
Keypad3,
Keypad4,
Keypad5,
Keypad6,
Keypad7,
Keypad8,
Keypad9,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
Insert,
Delete,
Home,
End,
PageUp,
PageDown,
UpArrow,
DownArrow,
LeftArrow,
RightArrow,
Select1P,
Select2P,
Service,
Test,
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using HarmonyLib;
using Main;
using Manager;
using MelonLoader;
using UnityEngine;
namespace AquaMai.ModKeyMap;
public static class ModKeyListener
{
private static readonly Dictionary<ModKeyCode, int> _keyPressFrames = new();
private static readonly Dictionary<ModKeyCode, int> _keyPressFramesPrev = new();
static ModKeyListener()
{
foreach (ModKeyCode key in Enum.GetValues(typeof(ModKeyCode)))
{
_keyPressFrames[key] = 0;
_keyPressFramesPrev[key] = 0;
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameMainObject), "Update")]
public static void CheckLongPush()
{
foreach (ModKeyCode key in Enum.GetValues(typeof(ModKeyCode)))
{
_keyPressFramesPrev[key] = _keyPressFrames[key];
if (GetKeyPush(key))
{
# if DEBUG
MelonLogger.Msg($"CheckLongPush {key} is push {_keyPressFrames[key]}");
# endif
_keyPressFrames[key]++;
}
else
{
_keyPressFrames[key] = 0;
}
}
}
public static bool GetKeyPush(ModKeyCode key) =>
key switch
{
ModKeyCode.None => false,
< ModKeyCode.Select1P => Input.GetKey(key.GetKeyCode()),
ModKeyCode.Test => InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonTest),
ModKeyCode.Service => InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonService),
ModKeyCode.Select1P => InputManager.GetButtonPush(0, InputManager.ButtonSetting.Select),
ModKeyCode.Select2P => InputManager.GetButtonPush(1, InputManager.ButtonSetting.Select),
_ => throw new ArgumentOutOfRangeException(nameof(key), key, "我也不知道这是什么键")
};
public static bool GetKeyDown(ModKeyCode key)
{
// return key switch
// {
// ModKeyCode.None => false,
// < ModKeyCode.Select1P => Input.GetKeyDown(key.GetKeyCode()),
// ModKeyCode.Test => InputManager.GetSystemInputDown(InputManager.SystemButtonSetting.ButtonTest),
// ModKeyCode.Service => InputManager.GetSystemInputDown(InputManager.SystemButtonSetting.ButtonService),
// ModKeyCode.Select1P => InputManager.GetButtonDown(0, InputManager.ButtonSetting.Select),
// ModKeyCode.Select2P => InputManager.GetButtonDown(1, InputManager.ButtonSetting.Select),
// _ => throw new ArgumentOutOfRangeException(nameof(key), key, "我也不知道这是什么键")
// };
// 不用这个,我们检测按键是否弹起以及弹起之前按下的时间是否小于 30这样可以防止要长按时按下的时候就触发
return _keyPressFrames[key] == 0 && 0 < _keyPressFramesPrev[key] && _keyPressFramesPrev[key] < 30;
}
public static bool GetKeyDownOrLongPress(ModKeyCode key, bool isLongPress)
{
bool ret;
if (isLongPress)
{
ret = _keyPressFrames[key] == 60;
}
else
{
ret = GetKeyDown(key);
}
# if DEBUG
if (ret)
{
MelonLogger.Msg($"Key {key} is pressed");
MelonLogger.Msg(new StackTrace());
}
# endif
return ret;
}
private static KeyCode GetKeyCode(this ModKeyCode modKeyCode) =>
modKeyCode switch
{
ModKeyCode.Alpha0 => KeyCode.Alpha0,
ModKeyCode.Alpha1 => KeyCode.Alpha1,
ModKeyCode.Alpha2 => KeyCode.Alpha2,
ModKeyCode.Alpha3 => KeyCode.Alpha3,
ModKeyCode.Alpha4 => KeyCode.Alpha4,
ModKeyCode.Alpha5 => KeyCode.Alpha5,
ModKeyCode.Alpha6 => KeyCode.Alpha6,
ModKeyCode.Alpha7 => KeyCode.Alpha7,
ModKeyCode.Alpha8 => KeyCode.Alpha8,
ModKeyCode.Alpha9 => KeyCode.Alpha9,
ModKeyCode.Keypad0 => KeyCode.Keypad0,
ModKeyCode.Keypad1 => KeyCode.Keypad1,
ModKeyCode.Keypad2 => KeyCode.Keypad2,
ModKeyCode.Keypad3 => KeyCode.Keypad3,
ModKeyCode.Keypad4 => KeyCode.Keypad4,
ModKeyCode.Keypad5 => KeyCode.Keypad5,
ModKeyCode.Keypad6 => KeyCode.Keypad6,
ModKeyCode.Keypad7 => KeyCode.Keypad7,
ModKeyCode.Keypad8 => KeyCode.Keypad8,
ModKeyCode.Keypad9 => KeyCode.Keypad9,
ModKeyCode.F1 => KeyCode.F1,
ModKeyCode.F2 => KeyCode.F2,
ModKeyCode.F3 => KeyCode.F3,
ModKeyCode.F4 => KeyCode.F4,
ModKeyCode.F5 => KeyCode.F5,
ModKeyCode.F6 => KeyCode.F6,
ModKeyCode.F7 => KeyCode.F7,
ModKeyCode.F8 => KeyCode.F8,
ModKeyCode.F9 => KeyCode.F9,
ModKeyCode.F10 => KeyCode.F10,
ModKeyCode.F11 => KeyCode.F11,
ModKeyCode.F12 => KeyCode.F12,
ModKeyCode.Insert => KeyCode.Insert,
ModKeyCode.Delete => KeyCode.Delete,
ModKeyCode.Home => KeyCode.Home,
ModKeyCode.End => KeyCode.End,
ModKeyCode.PageUp => KeyCode.PageUp,
ModKeyCode.PageDown => KeyCode.PageDown,
ModKeyCode.UpArrow => KeyCode.UpArrow,
ModKeyCode.DownArrow => KeyCode.DownArrow,
ModKeyCode.LeftArrow => KeyCode.LeftArrow,
ModKeyCode.RightArrow => KeyCode.RightArrow,
_ => throw new ArgumentOutOfRangeException(nameof(modKeyCode), modKeyCode, "游戏功能键需要单独处理")
};
}

View File

@ -6,6 +6,7 @@ using System.Reflection;
using AquaMai.Fix;
using AquaMai.Helpers;
using AquaMai.Resources;
using AquaMai.Utils;
using HarmonyLib;
using Manager;
using Monitor;
@ -13,7 +14,7 @@ using Monitor.Game;
using Process;
using UnityEngine;
namespace AquaMai.Utils;
namespace AquaMai.ModKeyMap;
public class PractiseMode
{
@ -172,7 +173,7 @@ public class PractiseMode
[HarmonyPostfix]
public static void GameProcessPostUpdate(GameProcess __instance, GameMonitor[] ____monitors)
{
if (InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonTest) && ui is null)
if (ModKeyListener.GetKeyDownOrLongPress(AquaMai.AppConfig.ModKeyMap.PractiseMode, AquaMai.AppConfig.ModKeyMap.PractiseModeLongPress) && ui is null)
{
ui = ____monitors[0].gameObject.AddComponent<PractiseModeUI>();
}

View File

@ -5,7 +5,7 @@ using AquaMai.Resources;
using Manager;
using UnityEngine;
namespace AquaMai.Utils;
namespace AquaMai.ModKeyMap;
public class PractiseModeUI : MonoBehaviour
{

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using AquaMai.Attributes;
using AquaMai.Helpers;
using HarmonyLib;
using Mai2.Mai2Cue;
@ -7,31 +6,17 @@ using MAI2.Util;
using Main;
using Manager;
using MelonLoader;
using Monitor;
using Process;
using UnityEngine;
namespace AquaMai.UX;
namespace AquaMai.ModKeyMap;
public class QuickSkip
{
private static int _keyPressFrames;
[HarmonyPrefix]
[HarmonyPatch(typeof(GameMainObject), "Update")]
public static void OnGameMainObjectUpdate()
{
// The button between [1p] and [2p] button on ADX
if (Input.GetKey(KeyCode.Alpha7) || InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonService)) _keyPressFrames++;
if (_keyPressFrames > 0 && !Input.GetKey(KeyCode.Alpha7) && !InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonService))
{
_keyPressFrames = 0;
MelonLogger.Msg(SharedInstances.ProcessDataContainer.processManager.Dump());
return;
}
if (_keyPressFrames != 60) return;
if (!ModKeyListener.GetKeyDownOrLongPress(AquaMai.AppConfig.ModKeyMap.QuickSkip, AquaMai.AppConfig.ModKeyMap.QuickSkipLongPress)) return;
MelonLogger.Msg("[QuickSkip] Activated");
var traverse = Traverse.Create(SharedInstances.ProcessDataContainer.processManager);
@ -72,7 +57,7 @@ public class QuickSkip
[HarmonyPatch(typeof(GameProcess), "OnUpdate")]
public static void PostGameProcessUpdate(GameProcess __instance, Message[] ____message, ProcessDataContainer ___container)
{
if (InputManager.GetButtonDown(0, InputManager.ButtonSetting.Select))
if (ModKeyListener.GetKeyDownOrLongPress(AquaMai.AppConfig.ModKeyMap.InGameSkip, AquaMai.AppConfig.ModKeyMap.InGameSkipLongPress))
{
var traverse = Traverse.Create(__instance);
___container.processManager.SendMessage(____message[0]);
@ -80,42 +65,10 @@ public class QuickSkip
traverse.Method("SetRelease").GetValue();
}
if (Input.GetKey(KeyCode.Alpha7) || InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonService) && GameInfo.GameVersion >= 23000)
if (ModKeyListener.GetKeyDownOrLongPress(AquaMai.AppConfig.ModKeyMap.InGameRetry, AquaMai.AppConfig.ModKeyMap.InGameRetryLongPress) && GameInfo.GameVersion >= 23000)
{
// This is original typo in Assembly-CSharp
Singleton<GamePlayManager>.Instance.SetQuickRetryFrag(flag: true);
}
}
[GameVersion(23000)]
public class FestivalAndLaterQuickRetryPatch
{
[HarmonyPrefix]
[HarmonyPatch(typeof(QuickRetry), "IsQuickRetryEnable")]
public static bool OnQuickRetryIsQuickRetryEnable(ref bool __result)
{
var isUtageProperty = Traverse.Create(typeof(GameManager)).Property("IsUtage");
__result = !isUtageProperty.PropertyExists() || !isUtageProperty.GetValue<bool>();
return false;
}
// Fix for the game not resetting Fast and Late counts when quick retrying
// For game version < 1.35.0
[HarmonyPostfix]
[HarmonyPatch(typeof(GamePlayManager), "SetQuickRetryFrag")]
public static void PostGamePlayManagerSetQuickRetryFrag(GamePlayManager __instance, bool flag)
{
// Since 1.35.0, `GameScoreList.Initialize()` resets the Fast and Late counts
if (flag && !Traverse.Create(typeof(GameScoreList)).Methods().Contains("Initialize"))
{
for (int i = 0; i < 4; i++)
{
var gameScoreList = __instance.GetGameScore(i);
var traverse = Traverse.Create(gameScoreList);
traverse.Property("Fast").SetValue((uint)0);
traverse.Property("Late").SetValue((uint)0);
}
}
}
}
}

View File

@ -3,12 +3,10 @@ using System.Linq;
using HarmonyLib;
using Manager;
namespace AquaMai.UX;
namespace AquaMai.ModKeyMap;
public class TestProof
{
private static int _keyPressFrames;
[HarmonyPrefix]
[HarmonyPatch(typeof(InputManager), "GetSystemInputDown")]
public static bool GetSystemInputDown(ref bool __result, InputManager.SystemButtonSetting button, bool[] ___SystemButtonDown)
@ -16,27 +14,13 @@ public class TestProof
__result = ___SystemButtonDown[(int)button];
if (button != InputManager.SystemButtonSetting.ButtonTest)
return false;
if (!InputManager.GetSystemInputPush(button))
{
_keyPressFrames = 0;
return false;
}
var stackTrace = new StackTrace(); // get call stack
var stackFrames = stackTrace.GetFrames(); // get method calls (frames)
if (stackFrames.Any(it => it.GetMethod().Name == "DMD<Main.GameMainObject::Update>"))
{
__result = false;
if (InputManager.GetSystemInputPush(button))
{
_keyPressFrames++;
}
if (_keyPressFrames == 60)
{
__result = true;
}
__result = ModKeyListener.GetKeyDownOrLongPress(AquaMai.AppConfig.ModKeyMap.TestMode, AquaMai.AppConfig.ModKeyMap.TestModeLongPress);
}
return false;

View File

@ -32,21 +32,6 @@ public class Config
""")]
public bool LoadAssetBundleWithoutManifest { get; set; }
[ConfigComment(
en: """
Press key "7" for 1 second to skip to next step or restart current song
Hold the bottom four buttons (3456) for official quick retry (non-utage only)
""",
zh: """
Service 7 ADX
-
-
7 Service 1P
3456使
""")]
public bool QuickSkip { get; set; }
[ConfigComment(
en: """
Random BGM, put Mai2Cue.{acb,awb} of old version of the game in `LocalAssets\Mai2Cue` and rename them
@ -90,22 +75,6 @@ public class Config
""")]
public bool LoadLocalBga { get; set; }
[ConfigComment(
en: "Prevent accidental touch of the Test button, requires 1 second long press to take effect",
zh: "防止你不小心按到 Test 键Test 键需要长按 1 秒才能生效")]
public bool TestProof { get; set; }
[ConfigComment(
en: """
In the song selection screen, press the Service button or the "7" key (the round button in the middle of the arrow keys in the default ADX firmware) to toggle the display of self-made charts.
A directory is considered to contain self-made charts if it does not have DataConfig.xml or OfficialChartsMark.txt in the Axxx directory.
""",
zh: """
Service 7 ADX
Axxx DataConfig.xml OfficialChartsMark.txt
""")]
public bool HideSelfMadeCharts { get; set; }
[ConfigComment(
en: """
Place font.ttf in the LocalAssets directory to replace the game's global font

View File

@ -24,17 +24,6 @@ public class Config
zh: "触摸屏延迟,单位为毫秒,一秒 = 1000 毫秒。必须是整数")]
public int TouchDelay { get; set; }
[ConfigComment(
en: """
Practice mode, activated by pressing Test in the game
Must be used together with TestProof
""",
zh: """
Test
TestProof
""")]
public bool PractiseMode { get; set; }
[ConfigComment(
en: "Show detail of selected song in music selection screen",
zh: "选歌界面显示选择的歌曲的详情")]