-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathGameInputSystem_Initialize_Patch.cs
More file actions
148 lines (133 loc) · 6.89 KB
/
GameInputSystem_Initialize_Patch.cs
File metadata and controls
148 lines (133 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using NitroxClient.MonoBehaviours.Gui.Input;
using NitroxClient.MonoBehaviours.Gui.Input.KeyBindings;
using UnityEngine.InputSystem;
namespace NitroxPatcher.Patches.Persistent;
/// <summary>
/// Inserts Nitrox's keybinds in the new Subnautica input system.
/// Extends GameInput.AllActions so the game creates InputAction entries for Nitrox buttons,
/// which is required for compatibility with Nautilus when both mods run together.
/// </summary>
public class GameInputSystem_Initialize_Patch : NitroxPatch, IPersistentPatch
{
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((GameInputSystem t) => t.Initialize());
private static readonly MethodInfo DEINITIALIZE_METHOD = Reflect.Method((GameInputSystem t) => t.Deinitialize());
private static GameInput.Button[] oldAllActions;
public override void Patch(Harmony harmony)
{
PatchPrefix(harmony, TARGET_METHOD, ((Action<GameInputSystem>)Prefix).Method);
PatchTranspiler(harmony, TARGET_METHOD, ((Func<IEnumerable<CodeInstruction>, IEnumerable<CodeInstruction>>)Transpiler).Method);
PatchPrefix(harmony, DEINITIALIZE_METHOD, ((Action)DeinitializePrefix).Method);
}
/// <summary>
/// Set the actions callbacks for our own keybindings once they're actually created.
/// If the game didn't create entries (e.g. when AllActions extension doesn't apply to this game version),
/// we create and add the InputActions ourselves, matching Nautilus's approach.
/// </summary>
private static void RegisterKeybindsActions(GameInputSystem gameInputSystem)
{
int buttonId = KeyBindingManager.NITROX_BASE_ID;
foreach (KeyBinding keyBinding in KeyBindingManager.KeyBindings)
{
GameInput.Button button = (GameInput.Button)buttonId++;
if (!gameInputSystem.actions.TryGetValue(button, out InputAction action))
{
// Game didn't create this action (AllActions may not be used for action creation in this build).
// Create and add it ourselves, matching Nautilus's InitializePostfix pattern.
string buttonName = GameInput.ActionNames.valueToString.TryGetValue(button, out string name) ? name : button.ToString();
action = new InputAction(buttonName, InputActionType.Button);
if (!string.IsNullOrEmpty(keyBinding.DefaultKeyboardKey))
{
action.AddBinding($"<Keyboard>/{keyBinding.DefaultKeyboardKey}");
}
if (!string.IsNullOrEmpty(keyBinding.DefaultControllerKey))
{
action.AddBinding($"<Gamepad>/{keyBinding.DefaultControllerKey}");
}
gameInputSystem.actions[button] = action;
action.started += gameInputSystem.OnActionStarted;
action.Enable();
}
action.started += keyBinding.Execute;
}
}
private static void Prefix(GameInputSystem __instance)
{
int buttonId = KeyBindingManager.NITROX_BASE_ID;
oldAllActions = GameInput.AllActions;
// Only extend AllActions if Nitrox buttons aren't already present.
// AllActions is typically initialized from Enum.GetValues (patched by Enum_GetValues_Patch),
// so it may already contain Nitrox buttons. Adding them again would cause duplicate keybinds
// in the settings UI since the game builds the binding list from both sources.
if (!GameInput.AllActions.Any(b => (int)b >= buttonId && (int)b < buttonId + KeyBindingManager.KeyBindings.Count))
{
GameInputAccessor.AllActions =
[
.. GameInput.AllActions,
.. Enumerable.Range(buttonId, KeyBindingManager.KeyBindings.Count).Cast<GameInput.Button>()
];
}
CachedEnumString<GameInput.Button> actionNames = GameInput.ActionNames;
// Access Language.main's internal strings dictionary so we can register "Option{buttonId}"
// entries for the binding conflict dialog (the game formats conflict messages using
// "Option" + button.ToString(), which yields "Option1000" for Nitrox buttons).
Dictionary<string, string> langStrings = null;
if (Language.main != null)
{
langStrings = (Dictionary<string, string>)AccessTools.Field(typeof(Language), "strings").GetValue(Language.main);
}
foreach (KeyBinding keyBinding in KeyBindingManager.KeyBindings)
{
GameInput.Button button = (GameInput.Button)buttonId++;
actionNames.valueToString[button] = keyBinding.ButtonLabel;
// Register the "Option1000" style key so the conflict dialog shows the translated label
langStrings?.TryAdd($"Option{(int)button}", Language.main.Get(keyBinding.ButtonLabel));
if (!string.IsNullOrEmpty(keyBinding.DefaultKeyboardKey))
{
// See GameInputSystem.bindingsKeyboard definition
GameInputSystem.bindingsKeyboard.Add(button, $"<Keyboard>/{keyBinding.DefaultKeyboardKey}");
}
if (!string.IsNullOrEmpty(keyBinding.DefaultControllerKey))
{
// See GameInputSystem.bindingsController definition
GameInputSystem.bindingsController.Add(button, $"<Gamepad>/{keyBinding.DefaultControllerKey}");
}
}
}
private static void DeinitializePrefix()
{
GameInputAccessor.AllActions = oldAllActions;
}
/*
* Modifying the actions must happen before actionMapGameplay.Enable because that line is responsible
* for activating the actions callback we'll be setting
*
* GameInputSystem_Initialize_Patch.RegisterKeybindsActions(this); <--- [INSERTED LINE]
* this.actionMapGameplay.Enable();
*/
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions).MatchStartForward(new(OpCodes.Ldarg_0), new(OpCodes.Ldfld), new(OpCodes.Callvirt, Reflect.Method((InputActionMap t) => t.Enable())))
.Insert(new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Call, Reflect.Method(() => RegisterKeybindsActions(default))))
.InstructionEnumeration();
}
private static class GameInputAccessor
{
/// <summary>
/// Required because "GameInput.AllActions" is a read only field.
/// </summary>
private static readonly FieldInfo allActionsField = Reflect.Field(() => GameInput.AllActions);
public static GameInput.Button[] AllActions
{
set
{
allActionsField.SetValue(null, value);
}
}
}
}