Skip to content

[+] ReviveFinaleVSlide#53

Merged
clansty merged 1 commit intoMuNET-OSS:mainfrom
r0x5a:main
Sep 15, 2025
Merged

[+] ReviveFinaleVSlide#53
clansty merged 1 commit intoMuNET-OSS:mainfrom
r0x5a:main

Conversation

@r0x5a
Copy link
Copy Markdown
Contributor

@r0x5a r0x5a commented Sep 12, 2025

Allow v-shaped slide with the same starting and ending point, such as 1v1 in Simai notation.

Sourcery 总结

通过使用 Harmony 转译器在运行时修补游戏的滑条数据和判定区域列表,启用同点V形滑条,并为该功能公开一个本地化的配置选项。

新功能:

  • 支持起点和终点相同的V形滑条(例如,Simai 记谱中的 "1v1")

改进:

  • 通过 Harmony 转译器将自定义 SVG 数据和判定区域配置注入到 SlideManager 初始化中
  • 添加一个本地化配置部分,用于切换 ReviveFinaleVSlide 功能
Original summary in English

Summary by Sourcery

Enable same-point v-shaped slides by patching the game�s slide data and hit area lists at runtime using a Harmony transpiler and expose a localized config option for the feature.

New Features:

  • Support v-shaped slides where the starting and ending points are identical (e.g., "1v1" in Simai notation)

Enhancements:

  • Inject custom SVG data and hit area configurations into SlideManager initialization via a Harmony transpiler
  • Add a localized configuration section for toggling the ReviveFinaleVSlide feature

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Sep 12, 2025

审阅者指南

此PR引入了一个新的ReviveFinaleVSlide模组,通过Harmony转译器补丁将自定义SVG和HitArea数据注入到SlideManager中,从而实现起点和终点相同的V形滑块。

ReviveFinaleVSlide模组及相关类型的类图

classDiagram
    class ReviveFinaleVSlide {
        +InsertData(list: List<string>) List<string>
        +InsertHitArea(list: List<List<SlideManager.HitArea>>) List<List<SlideManager.HitArea>>
    }
    class ReviveFinaleVSlide.SlideDataPatch {
        +Transpiler(instructions: IEnumerable<CodeInstruction>) IEnumerable<CodeInstruction>
    }
    class SlideManager {
        -_vDataList: List<string>
        -_vHitAreaList: List<List<HitArea>>
    }
    class SlideManager.HitArea {
        +HitPoints: List<InputManager.TouchPanelArea>
        +PushDistance: float
        +ReleaseDistance: float
    }
    class InputManager.TouchPanelArea
    ReviveFinaleVSlide <|-- ReviveFinaleVSlide.SlideDataPatch
    SlideManager "1" *-- "many" SlideManager.HitArea
    SlideManager.HitArea "1" *-- "many" InputManager.TouchPanelArea
Loading

文件级更改

更改 详情 文件
添加带有配置的ReviveFinaleVSlide模组类
  • 定义带有ConfigSection属性的静态ReviveFinaleVSlide类
  • 导入所需的命名空间 (HarmonyLib, MelonLoader, Core/Config attributes)
AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs
实现V形滑块资产的数据插入方法
  • InsertData记录日志并将SVG列表索引0替换为“V_1.svg”
  • InsertHitArea记录日志并将碰撞区域列表索引0替换为自定义的五点V形模式
AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs
通过Harmony转译器在SlideManager构造函数上注入自定义数据
  • 检测针对_vDataList和_vHitAreaList的Stfld操作
  • 在生成原始IL指令之前注入对InsertData和InsertHitArea的调用
AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs

提示和命令

与Sourcery互动

  • 触发新审阅: 在拉取请求上评论 @sourcery-ai review
  • 继续讨论: 直接回复Sourcery的审阅评论。
  • 从审阅评论生成GitHub议题: 通过回复审阅评论,请求Sourcery从该评论创建议题。您也可以回复审阅评论并加上 @sourcery-ai issue 来创建议题。
  • 生成拉取请求标题: 在拉取请求标题的任意位置写入 @sourcery-ai,即可随时生成标题。您也可以在拉取请求上评论 @sourcery-ai title 来随时(重新)生成标题。
  • 生成拉取请求摘要: 在拉取请求正文的任意位置写入 @sourcery-ai summary,即可随时在您想要的位置生成PR摘要。您也可以在拉取请求上评论 @sourcery-ai summary 来随时(重新)生成摘要。
  • 生成审阅者指南: 在拉取请求上评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 解决所有Sourcery评论: 在拉取请求上评论 @sourcery-ai resolve,以解决所有Sourcery评论。如果您已处理完所有评论且不想再看到它们,这将非常有用。
  • 驳回所有Sourcery审阅: 在拉取请求上评论 @sourcery-ai dismiss,以驳回所有现有Sourcery审阅。如果您想重新开始新的审阅,这将特别有用——别忘了评论 @sourcery-ai review 来触发新的审阅!

自定义您的体验

访问您的仪表盘以:

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

获取帮助

Original review guide in English

Reviewer's Guide

This PR introduces a new ReviveFinaleVSlide mod that enables v-shaped slides with identical start and end points by injecting custom SVG and HitArea data into SlideManager via a Harmony transpiler patch.

Class diagram for ReviveFinaleVSlide mod and related types

classDiagram
    class ReviveFinaleVSlide {
        +InsertData(list: List<string>) List<string>
        +InsertHitArea(list: List<List<SlideManager.HitArea>>) List<List<SlideManager.HitArea>>
    }
    class ReviveFinaleVSlide.SlideDataPatch {
        +Transpiler(instructions: IEnumerable<CodeInstruction>) IEnumerable<CodeInstruction>
    }
    class SlideManager {
        -_vDataList: List<string>
        -_vHitAreaList: List<List<HitArea>>
    }
    class SlideManager.HitArea {
        +HitPoints: List<InputManager.TouchPanelArea>
        +PushDistance: float
        +ReleaseDistance: float
    }
    class InputManager.TouchPanelArea
    ReviveFinaleVSlide <|-- ReviveFinaleVSlide.SlideDataPatch
    SlideManager "1" *-- "many" SlideManager.HitArea
    SlideManager.HitArea "1" *-- "many" InputManager.TouchPanelArea
Loading

File-Level Changes

Change Details Files
Add ReviveFinaleVSlide mod class with configuration
  • Define static ReviveFinaleVSlide class with ConfigSection attribute
  • Import required namespaces (HarmonyLib, MelonLoader, Core/Config attributes)
AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs
Implement data insertion methods for V-slide assets
  • InsertData logs and replaces SVG list index 0 with “V_1.svg”
  • InsertHitArea logs and replaces hit area list index 0 with a custom five-point V pattern
AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs
Inject custom data via Harmony Transpiler on SlideManager constructor
  • Detect Stfld operations targeting _vDataList and _vHitAreaList
  • Inject calls to InsertData and InsertHitArea before yielding the original IL instruction
AquaMai.Mods/GameSystem/ReviveFinaleVSlide.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.

你好 - 我已审阅了你的更改,它们看起来很棒!

AI 代理提示
请解决此代码审查中的评论:
## Individual Comments

### Comment 1
<location> `AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs:20` </location>
<code_context>
+    {
+        MelonLogger.Msg("[ReviveFinaleVSlide] Insert SVG data");
+        
+        list[0] = "V_1.svg";
+        return list;
+    }
</code_context>

<issue_to_address>
直接覆盖 list[0] 可能会在列表为空时导致问题。

在不检查列表是否非空的情况下赋值给 list[0] 将会引发异常。请添加长度检查或使用安全处理空列表的方法。
</issue_to_address>

### Comment 2
<location> `AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs:63` </location>
<code_context>
+        return list;
+    }
+
+    [HarmonyPatch(typeof(SlideManager), MethodType.Constructor)]
+    public static class SlideDataPatch
+    {
</code_context>

<issue_to_address>
修补所有构造函数可能会产生意外的副作用。

如果 SlideManager 有多个构造函数,修补所有构造函数可能会导致问题。请指定构造函数签名以仅针对预期的构造函数。

建议的实现:

```csharp
    // Example: If the intended constructor is SlideManager(int a, string b), use:
    [HarmonyPatch(typeof(SlideManager), MethodType.Constructor, new Type[] { typeof(int), typeof(string) })]
    public static class SlideDataPatch
    {

```

你必须更新 `new Type[] { ... }` 数组以匹配你想要修补的特定 SlideManager 构造函数的参数类型。例如,如果构造函数是 `SlideManager(float x, bool y)`,请使用 `new Type[] { typeof(float), typeof(bool) }`。
</issue_to_address>

### Comment 3
<location> `AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs:73` </location>
<code_context>
+
+            foreach (var insn in instructions)
+            {
+                if (insn.StoresField(vDataList))
+                {
+                    yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ReviveFinaleVSlide), "InsertData"));
+                }
+                else if (insn.StoresField(vHitAreaList))
+                {
+                    yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ReviveFinaleVSlide), "InsertHitArea"));
</code_context>

<issue_to_address>
用方法调用替换字段存储指令可能会扰乱预期的堆栈行为。

请验证 InsertDataInsertHitArea 返回的值是否适合字段赋值,并确认这些方法调用后堆栈保持一致。
</issue_to_address>

### Comment 4
<location> `AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs:66` </location>
<code_context>
+    [HarmonyPatch(typeof(SlideManager), MethodType.Constructor)]
+    public static class SlideDataPatch
+    {
+        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
+        {
+            var vDataList = AccessTools.Field(typeof(SlideManager), "_vDataList");
</code_context>

<issue_to_address>
考虑用一个简单的 Postfix 补丁和一个辅助函数替换 transpiler 和辅助方法,以使代码更短、更易读。

```markdown
你可以将所有 ILtranspiler 代码和两个“Insert…”方法折叠成 SlideManager 构造函数上的一个简单 Postfix,然后将 HitArea 构造移动到一个带有集合初始化器的小型辅助函数中。这保持了相同的行为,但更短且更易读。

```csharp
[HarmonyPatch(typeof(SlideManager), MethodType.Constructor)]
static class SlideManagerCtorPatch
{
    // Postfix can mutate the freshly created backing fields directly
    static void Postfix(
        ref List<string> ___vDataList,
        ref List<List<SlideManager.HitArea>> ___vHitAreaList
    )
    {
        MelonLogger.Msg("[ReviveFinaleVSlide] applying v-slide patch");
        ___vDataList[0] = "V_1.svg";
        ___vHitAreaList[0] = BuildVHitAreas();
    }

    // Helper builds the v-slide HitArea list in one place
    static List<SlideManager.HitArea> BuildVHitAreas()
    {
        return new List<SlideManager.HitArea>
        {
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.A1 },
                PushDistance   = 156.42124938964844,
                ReleaseDistance=  43.27423858642578
            },
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.B1 },
                PushDistance   = 128.9917755126953,
                ReleaseDistance=  42.19921875
            },
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.C1 },
                PushDistance   = 218.6302947998047,
                ReleaseDistance=  42.19921875
            },
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.B1 },
                PushDistance   = 128.9917755126953,
                ReleaseDistance=  43.27423858642578
            },
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.A1 },
                PushDistance   = 156.42124938964844,
                ReleaseDistance=   0.0
            },
        };
    }
}
```

步骤:
1. 删除 Transpiler 和两个 `Insert…` 方法。
2. 切换到 SlideManager 构造函数上的 `Postfix`,直接修改 `___vDataList` 和 `___vHitAreaList`。
3. 使用列表初始化器将 HitArea 数组提取到 `BuildVHitAreas()` 中——无需手动数组语法或冗余的 `return list;`。
4. 保持你的 `[ConfigSection]` 属性独立(如果仍然需要),但放在此补丁类之外。
</issue_to_address>

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs:20` </location>
<code_context>
+    {
+        MelonLogger.Msg("[ReviveFinaleVSlide] Insert SVG data");
+        
+        list[0] = "V_1.svg";
+        return list;
+    }
</code_context>

<issue_to_address>
Directly overwriting list[0] may cause issues if the list is empty.

Assigning to list[0] without checking if the list is non-empty will raise an exception. Add a length check or use a method that safely handles empty lists.
</issue_to_address>

### Comment 2
<location> `AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs:63` </location>
<code_context>
+        return list;
+    }
+
+    [HarmonyPatch(typeof(SlideManager), MethodType.Constructor)]
+    public static class SlideDataPatch
+    {
</code_context>

<issue_to_address>
Patching all constructors may have unintended side effects.

If SlideManager has multiple constructors, patching all of them may cause issues. Specify the constructor signature to target only the intended one.

Suggested implementation:

```csharp
    // Example: If the intended constructor is SlideManager(int a, string b), use:
    [HarmonyPatch(typeof(SlideManager), MethodType.Constructor, new Type[] { typeof(int), typeof(string) })]
    public static class SlideDataPatch
    {

```

You must update the `new Type[] { ... }` array to match the parameter types of the specific SlideManager constructor you want to patch. For example, if the constructor is `SlideManager(float x, bool y)`, use `new Type[] { typeof(float), typeof(bool) }`.
</issue_to_address>

### Comment 3
<location> `AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs:73` </location>
<code_context>
+
+            foreach (var insn in instructions)
+            {
+                if (insn.StoresField(vDataList))
+                {
+                    yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ReviveFinaleVSlide), "InsertData"));
+                }
+                else if (insn.StoresField(vHitAreaList))
+                {
+                    yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ReviveFinaleVSlide), "InsertHitArea"));
</code_context>

<issue_to_address>
Replacing field store instructions with method calls may disrupt expected stack behavior.

Verify that InsertData and InsertHitArea return values suitable for field assignment, and confirm the stack remains consistent after these method calls.
</issue_to_address>

### Comment 4
<location> `AquaMai.Mods/GameSystem/ReviveFinaleVSlide.cs:66` </location>
<code_context>
+    [HarmonyPatch(typeof(SlideManager), MethodType.Constructor)]
+    public static class SlideDataPatch
+    {
+        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
+        {
+            var vDataList = AccessTools.Field(typeof(SlideManager), "_vDataList");
</code_context>

<issue_to_address>
Consider replacing the transpiler and helper methods with a simple Postfix patch and a helper function to make the code shorter and more readable.

```markdown
You can collapse all that ILtranspiler code and the twoInsert…” methods into a simple Postfix on SlideManagers ctor, then move the HitArea construction into a small helper with a collection initializer. This keeps the same behavior but is far shorter and easier to read.

```csharp
[HarmonyPatch(typeof(SlideManager), MethodType.Constructor)]
static class SlideManagerCtorPatch
{
    // Postfix can mutate the freshly created backing fields directly
    static void Postfix(
        ref List<string> ___vDataList,
        ref List<List<SlideManager.HitArea>> ___vHitAreaList
    )
    {
        MelonLogger.Msg("[ReviveFinaleVSlide] applying v-slide patch");
        ___vDataList[0] = "V_1.svg";
        ___vHitAreaList[0] = BuildVHitAreas();
    }

    // Helper builds the v-slide HitArea list in one place
    static List<SlideManager.HitArea> BuildVHitAreas()
    {
        return new List<SlideManager.HitArea>
        {
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.A1 },
                PushDistance   = 156.42124938964844,
                ReleaseDistance=  43.27423858642578
            },
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.B1 },
                PushDistance   = 128.9917755126953,
                ReleaseDistance=  42.19921875
            },
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.C1 },
                PushDistance   = 218.6302947998047,
                ReleaseDistance=  42.19921875
            },
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.B1 },
                PushDistance   = 128.9917755126953,
                ReleaseDistance=  43.27423858642578
            },
            new() {
                HitPoints = new[]{ InputManager.TouchPanelArea.A1 },
                PushDistance   = 156.42124938964844,
                ReleaseDistance=   0.0
            },
        };
    }
}
```

Steps:
1. Remove the Transpiler and the two `Insert…` methods.
2. Switch to a `Postfix` on SlideManagers constructor, mutating `___vDataList` and `___vHitAreaList` directly.
3. Pull out the HitArea array into `BuildVHitAreas()` using a list initializerno manual array syntax or redundant `return list;`.
4. Keep your `[ConfigSection]` attribute separate (if still needed) but outside of this patch class.
</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.

{
MelonLogger.Msg("[ReviveFinaleVSlide] Insert SVG data");

list[0] = "V_1.svg";
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: 直接覆盖 list[0] 可能会在列表为空时导致问题。

在不检查列表是否非空的情况下赋值给 list[0] 将会引发异常。请添加长度检查或使用安全处理空列表的方法。

Original comment in English

issue: Directly overwriting list[0] may cause issues if the list is empty.

Assigning to list[0] without checking if the list is non-empty will raise an exception. Add a length check or use a method that safely handles empty lists.

return list;
}

[HarmonyPatch(typeof(SlideManager), MethodType.Constructor)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): 修补所有构造函数可能会产生意外的副作用。

如果 SlideManager 有多个构造函数,修补所有构造函数可能会导致问题。请指定构造函数签名以仅针对预期的构造函数。

建议的实现:

    // Example: If the intended constructor is SlideManager(int a, string b), use:
    [HarmonyPatch(typeof(SlideManager), MethodType.Constructor, new Type[] { typeof(int), typeof(string) })]
    public static class SlideDataPatch
    {

你必须更新 new Type[] { ... } 数组以匹配你想要修补的特定 SlideManager 构造函数的参数类型。例如,如果构造函数是 SlideManager(float x, bool y),请使用 new Type[] { typeof(float), typeof(bool) }

Original comment in English

suggestion (bug_risk): Patching all constructors may have unintended side effects.

If SlideManager has multiple constructors, patching all of them may cause issues. Specify the constructor signature to target only the intended one.

Suggested implementation:

    // Example: If the intended constructor is SlideManager(int a, string b), use:
    [HarmonyPatch(typeof(SlideManager), MethodType.Constructor, new Type[] { typeof(int), typeof(string) })]
    public static class SlideDataPatch
    {

You must update the new Type[] { ... } array to match the parameter types of the specific SlideManager constructor you want to patch. For example, if the constructor is SlideManager(float x, bool y), use new Type[] { typeof(float), typeof(bool) }.

Comment on lines +73 to +77
if (insn.StoresField(vDataList))
{
yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ReviveFinaleVSlide), "InsertData"));
}
else if (insn.StoresField(vHitAreaList))
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 (bug_risk): 用方法调用替换字段存储指令可能会扰乱预期的堆栈行为。

请验证 InsertData 和 InsertHitArea 返回的值是否适合字段赋值,并确认这些方法调用后堆栈保持一致。

Original comment in English

issue (bug_risk): Replacing field store instructions with method calls may disrupt expected stack behavior.

Verify that InsertData and InsertHitArea return values suitable for field assignment, and confirm the stack remains consistent after these method calls.

@AdemJensen
Copy link
Copy Markdown
Contributor

Is there any official chart that contains 1v1 slide?

@r0x5a
Copy link
Copy Markdown
Contributor Author

r0x5a commented Sep 13, 2025

Is there any official chart that contains 1v1 slide?

Sadly, no AFAIK. It exists in some fanmade charts though.
Also, 1v1 slide is recognizable in pre-DX versions, and funnily enough, the SVG file V_1.svg has been in the slide directory since the first version of maimai DX, containing the correct path of 1v1 and remaining unused to this day.

@clansty clansty merged commit 2a09d26 into MuNET-OSS:main Sep 15, 2025
2 checks 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.

3 participants