Skip to content
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
Expand All @@ -9,17 +11,38 @@
namespace NitroxPatcher.Patches.Persistent;

/// <summary>
/// Inserts Nitrox's keybinds in the new Subnautica input system
/// 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 partial 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 static void Prefix(GameInputSystem __instance)
public override void Patch(Harmony harmony)
{
CachedEnumString<GameInput.Button> actionNames = GameInput.ActionNames;
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);
}

public static void Prefix(GameInputSystem __instance)
{
// Extend AllActions so the game creates InputAction entries for Nitrox buttons when building the action map.
// Without this, gameInputSystem.actions[NitroxButton] would not exist and RegisterKeybindsActions would throw.
int buttonId = KeyBindingManager.NITROX_BASE_ID;
oldAllActions = GameInput.AllActions;
FieldInfo allActionsField = typeof(GameInput).GetField(nameof(GameInput.AllActions), BindingFlags.Public | BindingFlags.Static);
GameInput.Button[] allActions =
[
.. GameInput.AllActions,
.. Enumerable.Range(buttonId, KeyBindingManager.KeyBindings.Count).Cast<GameInput.Button>()
];
allActionsField?.SetValue(null, allActions);

CachedEnumString<GameInput.Button> actionNames = GameInput.ActionNames;
foreach (KeyBinding keyBinding in KeyBindingManager.KeyBindings)
{
GameInput.Button button = (GameInput.Button)buttonId++;
Expand All @@ -38,6 +61,12 @@ public static void Prefix(GameInputSystem __instance)
}
}

public static void DeinitializePrefix()
{
FieldInfo allActionsField = typeof(GameInput).GetField(nameof(GameInput.AllActions), BindingFlags.Public | BindingFlags.Static);
allActionsField?.SetValue(null, oldAllActions);
}

/*
* Modifying the actions must happen before actionMapGameplay.Enable because that line is responsible
* for activating the actions callback we'll be setting
Expand All @@ -60,15 +89,35 @@ public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructio
}

/// <summary>
/// Set the actions callbacks for our own keybindings once they're actually created only
/// 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>
public static void RegisterKeybindsActions(GameInputSystem gameInputSystem)
{
int buttonId = KeyBindingManager.NITROX_BASE_ID;
foreach (KeyBinding keyBinding in KeyBindingManager.KeyBindings)
{
GameInput.Button button = (GameInput.Button)buttonId++;
gameInputSystem.actions[button].started += keyBinding.Execute;
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;
}
}
}