Skip to content

[+] TouchHold judgment support in non-C areas#45

Merged
clansty merged 4 commits intoMuNET-OSS:mainfrom
ERR0RPR0MPT:main
Jul 17, 2025
Merged

[+] TouchHold judgment support in non-C areas#45
clansty merged 4 commits intoMuNET-OSS:mainfrom
ERR0RPR0MPT:main

Conversation

@ERR0RPR0MPT
Copy link
Copy Markdown
Contributor

@ERR0RPR0MPT ERR0RPR0MPT commented Jul 15, 2025

好的,这是将拉取请求摘要翻译成中文的结果:

Sourcery 总结

通过修补游戏的输入检查,增加对非 C 区域 Touch Hold 输入的正确判断的支持,并将该功能集成到配置迁移和排序中。

新特性:

  • 引入 JudgeTouchHoldInNormalArea Mod,它使用 Harmony transpiler 将 C 区触摸检查重定向到所有触摸面板区域的自定义逻辑

增强功能:

  • 更新 V1.0→V2.0 配置迁移,以包含新的 Fancy.GamePlay.JudgeTouchHoldInNormalArea 设置
  • 将 Fancy.GamePlay.JudgeTouchHoldInNormalArea 条目添加到 configSort.yaml
Original summary in English

Summary by Sourcery

Add support for correctly judging Touch Hold inputs in non-C areas by patching the game's input checks and integrate the feature into configuration migration and sorting

New Features:

  • Introduce JudgeTouchHoldInNormalArea mod that uses a Harmony transpiler to redirect C-zone touch checks to custom logic for all touch panel areas

Enhancements:

  • Update V1.0→V2.0 config migration to include the new Fancy.GamePlay.JudgeTouchHoldInNormalArea setting
  • Add Fancy.GamePlay.JudgeTouchHoldInNormalArea entry to configSort.yaml

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Jul 15, 2025

审核者指南

介绍了通过添加新的配置选项、更新迁移和排序顺序,以及实现一个基于 Harmony 的 mod 来修补 TouchHoldC.NoteCheck,从而支持正确判断默认 C 区之外的 Touch Hold 音符,该 mod 基于传感器区域路由触摸输入通过自定义逻辑。

JudgeTouchHoldInNormalArea 及相关更改的类图

classDiagram
    class JudgeTouchHoldInNormalArea {
        <<ConfigSection>>
        static TouchHoldC _currentInstance
        +static IEnumerable<CodeInstruction> NoteCheckTranspiler(IEnumerable<CodeInstruction> instructions)
        +static void SetCurrentInstance(TouchHoldC instance)
        +static int GetTouchAreaValue(TouchHoldC instance)
        +static bool CustomTouchDown(int monitorId)
        +static bool CustomTouchPush(int monitorId)
        -static InputManager.TouchPanelArea GetTouchPanelAreaFromButtonId(InputManager.ButtonSetting buttonId, int touchAreaValue)
    }
    class TouchHoldC {
        +NoteCheck()
        +ButtonId
        ...
    }
    class InputManager {
        +static bool InGameTouchPanelArea_C_Down(int monitorId)
        +static bool InGameTouchPanelArea_C_Push(int monitorId)
        +static bool InGameTouchPanelArea_B_Down(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_B_Push(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_E_Down(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_E_Push(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelAreaDown(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelAreaPush(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_D_Down(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_D_Push(int monitorId, ButtonSetting buttonId)
        +static bool IsUsedThisFrame(int monitorId, TouchPanelArea area)
        enum ButtonSetting
        enum TouchPanelArea
    }
    class GameManager {
        +static bool IsAutoPlay()
    }
    JudgeTouchHoldInNormalArea -- TouchHoldC : patches
    JudgeTouchHoldInNormalArea ..> InputManager : uses
    JudgeTouchHoldInNormalArea ..> GameManager : uses
Loading

文件级别更改

变更 详情 文件
为新的 TouchHold 判断设置添加了迁移映射
  • 为 MaimaiDX2077 下的 JudgeTouchHoldInNormalArea 添加了 MapBooleanTrueToSectionEnable
AquaMai.Config/Migration/ConfigMigration_V1_0_V2_0.cs
更新了配置排序以包含新的部分
  • 将 Fancy.GamePlay.JudgeTouchHoldInNormalArea 插入到 Fancy.GamePlay 下的 configSort.yaml 中
AquaMai/configSort.yaml
通过新的 Harmony 补丁实现了 JudgeTouchHoldInNormalArea mod
  • 创建了带有 [ConfigSection] 属性的 JudgeTouchHoldInNormalArea 类
  • Transpile 了 TouchHoldC.NoteCheck 以注入实例跟踪并替换对 C_Down/C_Push 的调用
  • 添加了基于反射的逻辑来检测 TouchHoldC 中的 TouchArea 字段/属性
  • 实现了 CustomTouchDown 和 CustomTouchPush 处理多个传感器区域、自动播放检查以及用于计算 TouchPanelArea 的助手
AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs

提示和命令

与 Sourcery 互动

  • 触发新的审查: 在 pull request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 通过回复审查评论,要求 Sourcery 从审查评论创建一个 issue。您也可以回复审查评论并使用 @sourcery-ai issue 从中创建一个 issue。
  • 生成 pull request 标题: 在 pull request 标题中的任何位置写入 @sourcery-ai 以随时生成标题。您也可以在 pull request 上评论 @sourcery-ai title 以随时(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文中的任何位置写入 @sourcery-ai summary 以随时在您想要的位置生成 PR 摘要。您也可以在 pull request 上评论 @sourcery-ai summary 以随时(重新)生成摘要。
  • 生成审核者指南: 在 pull request 上评论 @sourcery-ai guide 以随时(重新)生成审核者指南。
  • 解决所有 Sourcery 评论: 在 pull request 上评论 @sourcery-ai resolve 以解决所有 Sourcery 评论。如果您已经解决了所有评论并且不想再看到它们,这将非常有用。
  • 驳回所有 Sourcery 审查: 在 pull request 上评论 @sourcery-ai dismiss 以驳回所有现有的 Sourcery 审查。如果您想重新开始新的审查,这将特别有用 - 不要忘记评论 @sourcery-ai review 以触发新的审查!

自定义您的体验

访问您的 仪表板 以:

  • 启用或禁用审查功能,例如 Sourcery 生成的 pull request 摘要、审核者指南等。
  • 更改审查语言。
  • 添加、删除或编辑自定义审查说明。
  • 调整其他审查设置。

获得帮助

Original review guide in English

Reviewer's Guide

Introduces support for correct judging of Touch Hold notes outside the default C zone by adding a new configuration option, updating migration and sort order, and implementing a Harmony-based mod that patches TouchHoldC.NoteCheck to route touch inputs through custom logic based on the sensor area.

Class diagram for JudgeTouchHoldInNormalArea and related changes

classDiagram
    class JudgeTouchHoldInNormalArea {
        <<ConfigSection>>
        static TouchHoldC _currentInstance
        +static IEnumerable<CodeInstruction> NoteCheckTranspiler(IEnumerable<CodeInstruction> instructions)
        +static void SetCurrentInstance(TouchHoldC instance)
        +static int GetTouchAreaValue(TouchHoldC instance)
        +static bool CustomTouchDown(int monitorId)
        +static bool CustomTouchPush(int monitorId)
        -static InputManager.TouchPanelArea GetTouchPanelAreaFromButtonId(InputManager.ButtonSetting buttonId, int touchAreaValue)
    }
    class TouchHoldC {
        +NoteCheck()
        +ButtonId
        ...
    }
    class InputManager {
        +static bool InGameTouchPanelArea_C_Down(int monitorId)
        +static bool InGameTouchPanelArea_C_Push(int monitorId)
        +static bool InGameTouchPanelArea_B_Down(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_B_Push(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_E_Down(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_E_Push(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelAreaDown(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelAreaPush(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_D_Down(int monitorId, ButtonSetting buttonId)
        +static bool InGameTouchPanelArea_D_Push(int monitorId, ButtonSetting buttonId)
        +static bool IsUsedThisFrame(int monitorId, TouchPanelArea area)
        enum ButtonSetting
        enum TouchPanelArea
    }
    class GameManager {
        +static bool IsAutoPlay()
    }
    JudgeTouchHoldInNormalArea -- TouchHoldC : patches
    JudgeTouchHoldInNormalArea ..> InputManager : uses
    JudgeTouchHoldInNormalArea ..> GameManager : uses
Loading

File-Level Changes

Change Details Files
Added migration mapping for the new TouchHold judgment setting
  • Added MapBooleanTrueToSectionEnable for JudgeTouchHoldInNormalArea under MaimaiDX2077
AquaMai.Config/Migration/ConfigMigration_V1_0_V2_0.cs
Updated configuration sorting to include the new section
  • Inserted Fancy.GamePlay.JudgeTouchHoldInNormalArea into configSort.yaml under Fancy.GamePlay
AquaMai/configSort.yaml
Implemented the JudgeTouchHoldInNormalArea mod via a new Harmony patch
  • Created JudgeTouchHoldInNormalArea class with [ConfigSection] attribute
  • Transpiled TouchHoldC.NoteCheck to inject instance tracking and replace calls to C_Down/C_Push
  • Added reflection-based logic to detect TouchArea field/property in TouchHoldC
  • Implemented CustomTouchDown and CustomTouchPush handling multiple sensor zones, autoplay check, and helper for calculating TouchPanelArea
AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

@ERR0RPR0MPT - 我已经查看了你的更改 - 这里有一些反馈:

  • CustomTouchDown 和 CustomTouchPush 共享几乎相同的 switch‐case 逻辑——考虑提取一个助手来减少重复。
  • GetTouchAreaValue 扫描每次调用中的所有字段和属性——缓存已解析的 FieldInfo/PropertyInfo 以避免重复的反射开销。
  • 你的转译器盲目地在开头插入——定位一个特定的 IL 模式或偏移量,以使补丁对未来的 TouchHoldC 更改更健壮。
AI 代理的提示
请解决此代码审查中的评论:
## 总体评论
- CustomTouchDown 和 CustomTouchPush 共享几乎相同的 switch‐case 逻辑——考虑提取一个助手来减少重复。
- GetTouchAreaValue 扫描每次调用中的所有字段和属性——缓存已解析的 FieldInfo/PropertyInfo 以避免重复的反射开销。
- 你的转译器盲目地在开头插入——定位一个特定的 IL 模式或偏移量,以使补丁对未来的 TouchHoldC 更改更健壮。

## 单独评论

### 评论 1
<location> `AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs:19` </location>
<code_context>
+)]
+public class JudgeTouchHoldInNormalArea
+{
+    private static TouchHoldC _currentInstance;
+
+    [HarmonyPatch(typeof(TouchHoldC), "NoteCheck")]
</code_context>

<issue_to_address>
静态字段 _currentInstance 可能会在多线程或多实例场景中导致问题。

这里的静态字段可能会导致竞争条件或并发或多个 NoteCheck 调用中的不正确行为。如果需要并发,请考虑显式传递实例或使用线程本地存储。
</issue_to_address>

### 评论 2
<location> `AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs:52` </location>
<code_context>
+
+            // 替换方法调用
+            int replacedCount = 0;
+            for (int i = 0; i < codes.Count; i++)
+            {
+                if (codes[i].opcode == OpCodes.Call && codes[i].operand is MethodInfo method)
+                {
+                    if (method == cDownMethod)
+                    {
+                        codes[i] = new CodeInstruction(OpCodes.Call, customDownMethod);
+                        replacedCount++;
+                        MelonLogger.Msg($"Replaced C_Down method call at index {i}");
+                    }
+                    else if (method == cPushMethod)
+                    {
+                        codes[i] = new CodeInstruction(OpCodes.Call, customPushMethod);
+                        replacedCount++;
+                        MelonLogger.Msg($"Replaced C_Push method call at index {i}");
+                    }
+                }
</code_context>

<issue_to_address>
就地替换指令可能无法保留原始指令元数据。

替换 CodeInstruction 对象时,请确保您还从原始指令传输任何元数据(例如,标签、异常块)以保持正确的控制流和异常处理。
</issue_to_address>

### 评论 3
<location> `AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs:258` </location>
<code_context>
+    }
+
+    // 根据ButtonId和TouchArea计算正确的TouchPanelArea枚举值
+    private static InputManager.TouchPanelArea GetTouchPanelAreaFromButtonId(InputManager.ButtonSetting buttonId,
+        int touchAreaValue)
+    {
</code_context>

<issue_to_address>
假定枚举值是连续且有序的。

如果枚举值不是严格连续且有序的,则此方法可能会产生无效结果。请验证此假设或使用显式映射。
</issue_to_address>

### 评论 4
<location> `AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs:184` </location>
<code_context>
+        }
+    }
+
+    public static bool CustomTouchDown(int monitorId)
+    {
+        try
</code_context>

<issue_to_address>
考虑使用字典和统一的反射方法将重复的 switch 语句和反射逻辑重构为数据驱动的助手。

以下是一些小的重构,它们会将您的样板开关和繁重的反射折叠为数据驱动的助手,而无需触及任何公共行为。

1. 将您的“Down”/“Push”处理程序缓存在字典中,而不是重复两次 switch:

```csharp
// 在类级别
private static readonly Dictionary<int, Func<int, InputManager.ButtonSetting, bool>> _downHandlers =
    new Dictionary<int, Func<int, InputManager.ButtonSetting, bool>>
{
    [0] = InputManager.InGameTouchPanelArea_B_Down,
    [1] = (m, b) => InputManager.InGameTouchPanelArea_C_Down(m),
    [2] = InputManager.InGameTouchPanelArea_E_Down,
    [3] = InputManager.InGameTouchPanelAreaDown,
    [4] = InputManager.InGameTouchPanelArea_D_Down
};

private static readonly Dictionary<int, Func<int, InputManager.ButtonSetting, bool>> _pushHandlers =
    new Dictionary<int, Func<int, InputManager.ButtonSetting, bool>>
{
    [0] = InputManager.InGameTouchPanelArea_B_Push,
    [1] = (m, b) => InputManager.InGameTouchPanelArea_C_Push(m),
    [2] = InputManager.InGameTouchPanelArea_E_Push,
    [3] = InputManager.InGameTouchPanelAreaPush,
    [4] = InputManager.InGameTouchPanelArea_D_Push
};

// 然后在 CustomTouchDown 中:
var handler = _downHandlers.TryGetValue(area, out var h)
    ? h
    : (m,b) => InputManager.InGameTouchPanelArea_C_Down(m);
bool touchDown = handler(monitorId, buttonId);

// 并在 CustomTouchPush 中:
var push = (_pushHandlers.TryGetValue(area, out var p) ? p : (m,b) => InputManager.InGameTouchPanelArea_C_Push(m))
            .Invoke(monitorId, buttonId);
```

2. 将您的许多基于 switch 的面板区域映射折叠为一个小的基本值数组:

```csharp
// 类级别
private static readonly InputManager.TouchPanelArea[] _baseAreas =
{
    InputManager.TouchPanelArea.B1,
    InputManager.TouchPanelArea.C1,
    InputManager.TouchPanelArea.E1,
    InputManager.TouchPanelArea.A1,
    InputManager.TouchPanelArea.D1
};

// 将 GetTouchPanelAreaFromButtonId 替换为:
private static InputManager.TouchPanelArea GetTouchPanelAreaFromButtonId(
    InputManager.ButtonSetting btn, int area)
{
    var baseArea = (area >= 0 && area < _baseAreas.Length)
        ? _baseAreas[area]
        : InputManager.TouchPanelArea.C1;
    return baseArea + (int)btn;
}
```

3. 将您的字段/属性搜索减少为一个助手,该助手尝试名称,然后回退到任何枚举类型的成员:

```csharp
private static readonly string[] _candidateNames = { "TouchArea", "touchArea", "_touchArea", "m_touchArea" };

private static int GetTouchAreaValue(TouchHoldC inst)
{
    // 1) 尝试候选名称
    foreach (var name in _candidateNames)
    {
        var f = typeof(TouchHoldC).GetField(name, ALL_FLAGS);
        if (f != null && TryEnumValue(f.GetValue(inst), out var v)) return v;
        var p = typeof(TouchHoldC).GetProperty(name, ALL_FLAGS);
        if (p != null && TryEnumValue(p.GetValue(inst), out v)) return v;
    }

    // 2) 回退:任何枚举类型的字段/属性
    foreach (var mi in typeof(TouchHoldC).GetMembers(ALL_FLAGS))
    {
        object val = mi switch
        {
            FieldInfo f => f.GetValue(inst),
            PropertyInfo p => TrySafe(() => p.GetValue(inst)),
            _ => null
        };
        if (TryEnumValue(val, out var v)) return v;
    }

    MelonLogger.Error("Could not find TouchArea");
    return 1;
}

private static bool TryEnumValue(object o, out int val)
{
    if (o != null && o.GetType().IsEnum)
    {
        val = (int)o;
        return true;
    }
    val = default;
    return false;
}

private static object TrySafe(Func<object> f)
{
    try { return f(); }
    catch { return null; }
}

const BindingFlags ALL_FLAGS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
```

这三个更改删除了您的重复 switch 块,将面板区域逻辑折叠为数据,并将反射统一为一个简洁的助手——所有这些都同时保留了现有行为。
</issue_to_address>

Sourcery 对开源是免费的 - 如果您喜欢我们的评论,请考虑分享它们 ✨
帮助我更有用!请点击每个评论上的 👍 或 👎,我将使用反馈来改进您的评论。
Original comment in English

Hey @ERR0RPR0MPT - I've reviewed your changes - here's some feedback:

  • CustomTouchDown and CustomTouchPush share nearly identical switch‐case logic—consider extracting a helper to reduce duplication.
  • GetTouchAreaValue scans all fields and properties on every call—cache the resolved FieldInfo/PropertyInfo to avoid repeated reflection overhead.
  • Your transpiler blindly inserts at the beginning—target a specific IL pattern or offset instead to make the patch more robust against future TouchHoldC changes.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- CustomTouchDown and CustomTouchPush share nearly identical switch‐case logic—consider extracting a helper to reduce duplication.
- GetTouchAreaValue scans all fields and properties on every call—cache the resolved FieldInfo/PropertyInfo to avoid repeated reflection overhead.
- Your transpiler blindly inserts at the beginning—target a specific IL pattern or offset instead to make the patch more robust against future TouchHoldC changes.

## Individual Comments

### Comment 1
<location> `AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs:19` </location>
<code_context>
+)]
+public class JudgeTouchHoldInNormalArea
+{
+    private static TouchHoldC _currentInstance;
+
+    [HarmonyPatch(typeof(TouchHoldC), "NoteCheck")]
</code_context>

<issue_to_address>
Static field _currentInstance may cause issues in multi-threaded or multi-instance scenarios.

A static field here can cause race conditions or incorrect behavior with concurrent or multiple NoteCheck calls. Consider passing the instance explicitly or using thread-local storage if concurrency is needed.
</issue_to_address>

### Comment 2
<location> `AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs:52` </location>
<code_context>
+
+            // 替换方法调用
+            int replacedCount = 0;
+            for (int i = 0; i < codes.Count; i++)
+            {
+                if (codes[i].opcode == OpCodes.Call && codes[i].operand is MethodInfo method)
+                {
+                    if (method == cDownMethod)
+                    {
+                        codes[i] = new CodeInstruction(OpCodes.Call, customDownMethod);
+                        replacedCount++;
+                        MelonLogger.Msg($"Replaced C_Down method call at index {i}");
+                    }
+                    else if (method == cPushMethod)
+                    {
+                        codes[i] = new CodeInstruction(OpCodes.Call, customPushMethod);
+                        replacedCount++;
+                        MelonLogger.Msg($"Replaced C_Push method call at index {i}");
+                    }
+                }
</code_context>

<issue_to_address>
Replacing instructions in-place may not preserve original instruction metadata.

When replacing CodeInstruction objects, ensure you also transfer any metadata (e.g., labels, exception blocks) from the original instruction to maintain correct control flow and exception handling.
</issue_to_address>

### Comment 3
<location> `AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs:258` </location>
<code_context>
+    }
+
+    // 根据ButtonId和TouchArea计算正确的TouchPanelArea枚举值
+    private static InputManager.TouchPanelArea GetTouchPanelAreaFromButtonId(InputManager.ButtonSetting buttonId,
+        int touchAreaValue)
+    {
</code_context>

<issue_to_address>
Assumes enum values are contiguous and ordered.

If enum values are not strictly contiguous and ordered, this approach may yield invalid results. Please validate this assumption or use an explicit mapping.
</issue_to_address>

### Comment 4
<location> `AquaMai.Mods/Fancy/GamePlay/JudgeTouchHoldInNormalArea.cs:184` </location>
<code_context>
+        }
+    }
+
+    public static bool CustomTouchDown(int monitorId)
+    {
+        try
</code_context>

<issue_to_address>
Consider refactoring repeated switch statements and reflection logic into data-driven helpers using dictionaries and unified reflection methods.

Here are a few small refactorings that will collapse your boiler-plate switches and heavy reflection into data-driven helpers without touching any public behavior.

1. Cache your “Down”/“Push” handlers in dictionaries instead of repeating the switch twice:

```csharp
// at class level
private static readonly Dictionary<int, Func<int, InputManager.ButtonSetting, bool>> _downHandlers =
    new Dictionary<int, Func<int, InputManager.ButtonSetting, bool>>
{
    [0] = InputManager.InGameTouchPanelArea_B_Down,
    [1] = (m, b) => InputManager.InGameTouchPanelArea_C_Down(m),
    [2] = InputManager.InGameTouchPanelArea_E_Down,
    [3] = InputManager.InGameTouchPanelAreaDown,
    [4] = InputManager.InGameTouchPanelArea_D_Down
};

private static readonly Dictionary<int, Func<int, InputManager.ButtonSetting, bool>> _pushHandlers =
    new Dictionary<int, Func<int, InputManager.ButtonSetting, bool>>
{
    [0] = InputManager.InGameTouchPanelArea_B_Push,
    [1] = (m, b) => InputManager.InGameTouchPanelArea_C_Push(m),
    [2] = InputManager.InGameTouchPanelArea_E_Push,
    [3] = InputManager.InGameTouchPanelAreaPush,
    [4] = InputManager.InGameTouchPanelArea_D_Push
};

// then in CustomTouchDown:
var handler = _downHandlers.TryGetValue(area, out var h)
    ? h
    : (m,b) => InputManager.InGameTouchPanelArea_C_Down(m);
bool touchDown = handler(monitorId, buttonId);

// and in CustomTouchPush:
var push = (_pushHandlers.TryGetValue(area, out var p) ? p : (m,b) => InputManager.InGameTouchPanelArea_C_Push(m))
            .Invoke(monitorId, buttonId);
```

2. Collapse your many switch‐based panel‐area mapping into a small base‐value array:

```csharp
// class‐level
private static readonly InputManager.TouchPanelArea[] _baseAreas =
{
    InputManager.TouchPanelArea.B1,
    InputManager.TouchPanelArea.C1,
    InputManager.TouchPanelArea.E1,
    InputManager.TouchPanelArea.A1,
    InputManager.TouchPanelArea.D1
};

// replace GetTouchPanelAreaFromButtonId with:
private static InputManager.TouchPanelArea GetTouchPanelAreaFromButtonId(
    InputManager.ButtonSetting btn, int area)
{
    var baseArea = (area >= 0 && area < _baseAreas.Length)
        ? _baseAreas[area]
        : InputManager.TouchPanelArea.C1;
    return baseArea + (int)btn;
}
```

3. Reduce your field/property hunts into one helper that tries names, then falls back to any enum‐typed member:

```csharp
private static readonly string[] _candidateNames = { "TouchArea", "touchArea", "_touchArea", "m_touchArea" };

private static int GetTouchAreaValue(TouchHoldC inst)
{
    // 1) try candidate names
    foreach (var name in _candidateNames)
    {
        var f = typeof(TouchHoldC).GetField(name, ALL_FLAGS);
        if (f != null && TryEnumValue(f.GetValue(inst), out var v)) return v;
        var p = typeof(TouchHoldC).GetProperty(name, ALL_FLAGS);
        if (p != null && TryEnumValue(p.GetValue(inst), out v)) return v;
    }

    // 2) fallback: any enum‐typed field/property
    foreach (var mi in typeof(TouchHoldC).GetMembers(ALL_FLAGS))
    {
        object val = mi switch
        {
            FieldInfo f => f.GetValue(inst),
            PropertyInfo p => TrySafe(() => p.GetValue(inst)),
            _ => null
        };
        if (TryEnumValue(val, out var v)) return v;
    }

    MelonLogger.Error("Could not find TouchArea");
    return 1;
}

private static bool TryEnumValue(object o, out int val)
{
    if (o != null && o.GetType().IsEnum)
    {
        val = (int)o;
        return true;
    }
    val = default;
    return false;
}

private static object TrySafe(Func<object> f)
{
    try { return f(); }
    catch { return null; }
}

const BindingFlags ALL_FLAGS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
```

These three changes remove your duplicate switch blocks, collapse panel‐area logic into data, and unify reflection into a single concise helper—all while preserving existing behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

}
}

public static bool CustomTouchDown(int monitorId)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): 考虑使用字典和统一的反射方法将重复的 switch 语句和反射逻辑重构为数据驱动的助手。

以下是一些小的重构,它们会将您的样板开关和繁重的反射折叠为数据驱动的助手,而无需触及任何公共行为。

  1. 将您的“Down”/“Push”处理程序缓存在字典中,而不是重复两次 switch:
// 在类级别
private static readonly Dictionary<int, Func<int, InputManager.ButtonSetting, bool>> _downHandlers =
    new Dictionary<int, Func<int, InputManager.ButtonSetting, bool>>
{
    [0] = InputManager.InGameTouchPanelArea_B_Down,
    [1] = (m, b) => InputManager.InGameTouchPanelArea_C_Down(m),
    [2] = InputManager.InGameTouchPanelArea_E_Down,
    [3] = InputManager.InGameTouchPanelAreaDown,
    [4] = InputManager.InGameTouchPanelArea_D_Down
};

private static readonly Dictionary<int, Func<int, InputManager.ButtonSetting, bool>> _pushHandlers =
    new Dictionary<int, Func<int, InputManager.ButtonSetting, bool>>
{
    [0] = InputManager.InGameTouchPanelArea_B_Push,
    [1] = (m, b) => InputManager.InGameTouchPanelArea_C_Push(m),
    [2] = InputManager.InGameTouchPanelArea_E_Push,
    [3] = InputManager.InGameTouchPanelAreaPush,
    [4] = InputManager.InGameTouchPanelArea_D_Push
};

// 然后在 CustomTouchDown 中:
var handler = _downHandlers.TryGetValue(area, out var h)
    ? h
    : (m,b) => InputManager.InGameTouchPanelArea_C_Down(m);
bool touchDown = handler(monitorId, buttonId);

// 并在 CustomTouchPush 中:
var push = (_pushHandlers.TryGetValue(area, out var p) ? p : (m,b) => InputManager.InGameTouchPanelArea_C_Push(m))
            .Invoke(monitorId, buttonId);
  1. 将您的许多基于 switch 的面板区域映射折叠为一个小的基本值数组:
// 类级别
private static readonly InputManager.TouchPanelArea[] _baseAreas =
{
    InputManager.TouchPanelArea.B1,
    InputManager.TouchPanelArea.C1,
    InputManager.TouchPanelArea.E1,
    InputManager.TouchPanelArea.A1,
    InputManager.TouchPanelArea.D1
};

// 将 GetTouchPanelAreaFromButtonId 替换为:
private static InputManager.TouchPanelArea GetTouchPanelAreaFromButtonId(
    InputManager.ButtonSetting btn, int area)
{
    var baseArea = (area >= 0 && area < _baseAreas.Length)
        ? _baseAreas[area]
        : InputManager.TouchPanelArea.C1;
    return baseArea + (int)btn;
}
  1. 将您的字段/属性搜索减少为一个助手,该助手尝试名称,然后回退到任何枚举类型的成员:
private static readonly string[] _candidateNames = { "TouchArea", "touchArea", "_touchArea", "m_touchArea" };

private static int GetTouchAreaValue(TouchHoldC inst)
{
    // 1) 尝试候选名称
    foreach (var name in _candidateNames)
    {
        var f = typeof(TouchHoldC).GetField(name, ALL_FLAGS);
        if (f != null && TryEnumValue(f.GetValue(inst), out var v)) return v;
        var p = typeof(TouchHoldC).GetProperty(name, ALL_FLAGS);
        if (p != null && TryEnumValue(p.GetValue(inst), out v)) return v;
    }

    // 2) 回退:任何枚举类型的字段/属性
    foreach (var mi in typeof(TouchHoldC).GetMembers(ALL_FLAGS))
    {
        object val = mi switch
        {
            FieldInfo f => f.GetValue(inst),
            PropertyInfo p => TrySafe(() => p.GetValue(inst)),
            _ => null
        };
        if (TryEnumValue(val, out var v)) return v;
    }

    MelonLogger.Error("Could not find TouchArea");
    return 1;
}

private static bool TryEnumValue(object o, out int val)
{
    if (o != null && o.GetType().IsEnum)
    {
        val = (int)o;
        return true;
    }
    val = default;
    return false;
}

private static object TrySafe(Func<object> f)
{
    try { return f(); }
    catch { return null; }
}

const BindingFlags ALL_FLAGS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

这三个更改删除了您的重复 switch 块,将面板区域逻辑折叠为数据,并将反射统一为一个简洁的助手——所有这些都同时保留了现有行为。

Original comment in English

issue (complexity): Consider refactoring repeated switch statements and reflection logic into data-driven helpers using dictionaries and unified reflection methods.

Here are a few small refactorings that will collapse your boiler-plate switches and heavy reflection into data-driven helpers without touching any public behavior.

  1. Cache your “Down”/“Push” handlers in dictionaries instead of repeating the switch twice:
// at class level
private static readonly Dictionary<int, Func<int, InputManager.ButtonSetting, bool>> _downHandlers =
    new Dictionary<int, Func<int, InputManager.ButtonSetting, bool>>
{
    [0] = InputManager.InGameTouchPanelArea_B_Down,
    [1] = (m, b) => InputManager.InGameTouchPanelArea_C_Down(m),
    [2] = InputManager.InGameTouchPanelArea_E_Down,
    [3] = InputManager.InGameTouchPanelAreaDown,
    [4] = InputManager.InGameTouchPanelArea_D_Down
};

private static readonly Dictionary<int, Func<int, InputManager.ButtonSetting, bool>> _pushHandlers =
    new Dictionary<int, Func<int, InputManager.ButtonSetting, bool>>
{
    [0] = InputManager.InGameTouchPanelArea_B_Push,
    [1] = (m, b) => InputManager.InGameTouchPanelArea_C_Push(m),
    [2] = InputManager.InGameTouchPanelArea_E_Push,
    [3] = InputManager.InGameTouchPanelAreaPush,
    [4] = InputManager.InGameTouchPanelArea_D_Push
};

// then in CustomTouchDown:
var handler = _downHandlers.TryGetValue(area, out var h)
    ? h
    : (m,b) => InputManager.InGameTouchPanelArea_C_Down(m);
bool touchDown = handler(monitorId, buttonId);

// and in CustomTouchPush:
var push = (_pushHandlers.TryGetValue(area, out var p) ? p : (m,b) => InputManager.InGameTouchPanelArea_C_Push(m))
            .Invoke(monitorId, buttonId);
  1. Collapse your many switch‐based panel‐area mapping into a small base‐value array:
// class‐level
private static readonly InputManager.TouchPanelArea[] _baseAreas =
{
    InputManager.TouchPanelArea.B1,
    InputManager.TouchPanelArea.C1,
    InputManager.TouchPanelArea.E1,
    InputManager.TouchPanelArea.A1,
    InputManager.TouchPanelArea.D1
};

// replace GetTouchPanelAreaFromButtonId with:
private static InputManager.TouchPanelArea GetTouchPanelAreaFromButtonId(
    InputManager.ButtonSetting btn, int area)
{
    var baseArea = (area >= 0 && area < _baseAreas.Length)
        ? _baseAreas[area]
        : InputManager.TouchPanelArea.C1;
    return baseArea + (int)btn;
}
  1. Reduce your field/property hunts into one helper that tries names, then falls back to any enum‐typed member:
private static readonly string[] _candidateNames = { "TouchArea", "touchArea", "_touchArea", "m_touchArea" };

private static int GetTouchAreaValue(TouchHoldC inst)
{
    // 1) try candidate names
    foreach (var name in _candidateNames)
    {
        var f = typeof(TouchHoldC).GetField(name, ALL_FLAGS);
        if (f != null && TryEnumValue(f.GetValue(inst), out var v)) return v;
        var p = typeof(TouchHoldC).GetProperty(name, ALL_FLAGS);
        if (p != null && TryEnumValue(p.GetValue(inst), out v)) return v;
    }

    // 2) fallback: any enum‐typed field/property
    foreach (var mi in typeof(TouchHoldC).GetMembers(ALL_FLAGS))
    {
        object val = mi switch
        {
            FieldInfo f => f.GetValue(inst),
            PropertyInfo p => TrySafe(() => p.GetValue(inst)),
            _ => null
        };
        if (TryEnumValue(val, out var v)) return v;
    }

    MelonLogger.Error("Could not find TouchArea");
    return 1;
}

private static bool TryEnumValue(object o, out int val)
{
    if (o != null && o.GetType().IsEnum)
    {
        val = (int)o;
        return true;
    }
    val = default;
    return false;
}

private static object TrySafe(Func<object> f)
{
    try { return f(); }
    catch { return null; }
}

const BindingFlags ALL_FLAGS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

These three changes remove your duplicate switch blocks, collapse panel‐area logic into data, and unify reflection into a single concise helper—all while preserving existing behavior.


// MaimaiDX2077 (WTF is the name?)
MapBooleanTrueToSectionEnable(src, dst, "MaimaiDX2077.CustomNoteTypePatch", "Fancy.GamePlay.CustomNoteTypes");
MapBooleanTrueToSectionEnable(src, dst, "MaimaiDX2077.JudgeTouchHoldInNormalArea", "Fancy.GamePlay.JudgeTouchHoldInNormalArea");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

不用修改这个文件,这是用来迁移很老版本的配置文件的

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

已修复

using MelonLoader;
using Monitor;

namespace AquaMai.Mods.Fancy.GamePlay;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

如果原先游戏无法正常判定,这个 patch 应该放在 fix 命名空间中

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

已修复

try
{
// 尝试多种可能的字段名
var possibleFieldNames = new[] { "TouchArea", "touchArea", "_touchArea", "m_touchArea" };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

这个字段在游戏里没有固定的名称吗

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

已简化

@clansty clansty merged commit a619f81 into MuNET-OSS:main Jul 17, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants