using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Utilities;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("CustomKeybinds")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CustomKeybinds")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("32460eb4-e201-4ed4-9723-296aac0d0d64")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace InstantItemSlots;
[BepInPlugin("kviks.instantitemslots", "Instant Item Slots", "4.0.2")]
public class InstantItemSlotsPlugin : BaseUnityPlugin
{
internal static ManualLogSource Log;
internal static bool DebugEnabled;
internal static bool FixEmotesEnabled;
internal static int ControlledSlots;
internal static float CooldownSeconds;
internal static float SpawnDelaySeconds;
internal static bool IgnoreBusyDuringSpawnDelay;
internal static bool IgnoreBusyChecksAlways;
internal static bool EnsureArrowBindings;
internal static bool PreventDuplicateArrowBindings;
internal static bool PlayGrabSfx;
internal static bool SyncSteps;
private static Harmony _harmony;
private static ConfigEntry<bool> _debug;
private static ConfigEntry<bool> _fixEmotes;
private static ConfigEntry<int> _controlledSlots;
private static ConfigEntry<float> _cooldownSeconds;
private static ConfigEntry<float> _spawnDelaySeconds;
private static ConfigEntry<bool> _ignoreBusyDuringSpawnDelay;
private static ConfigEntry<bool> _ignoreBusyChecksAlways;
private static ConfigEntry<bool> _ensureArrowBindings;
private static ConfigEntry<bool> _preventDuplicateArrowBindings;
private static ConfigEntry<bool> _playGrabSfx;
private static ConfigEntry<bool> _syncSteps;
private void Awake()
{
//IL_018d: Unknown result type (might be due to invalid IL or missing references)
//IL_0197: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
_debug = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Enables debug logging.");
_fixEmotes = ((BaseUnityPlugin)this).Config.Bind<bool>("Emotes", "FixEmotesToArrowsOnly", true, "Forces emotes to only trigger from Up/Down arrows and adds those bindings.");
_controlledSlots = ((BaseUnityPlugin)this).Config.Bind<int>("Hotbar", "ControlledSlots", 4, "How many slots are controlled by 1/2/3/4 (1..4).");
_cooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Hotbar", "CooldownSeconds", 0.3f, "Minimum time since last switch allowed.");
_spawnDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Spawn", "SpawnDelaySeconds", 0f, "Seconds after level load where switching rules can be relaxed.");
_ignoreBusyDuringSpawnDelay = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawn", "IgnoreBusyChecksDuringSpawnDelay", true, "Ignore animation/interact/activatingItem during SpawnDelaySeconds.");
_ignoreBusyChecksAlways = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawn", "IgnoreBusyChecksAlways", false, "Always ignore animation/interact/activatingItem checks.");
_ensureArrowBindings = ((BaseUnityPlugin)this).Config.Bind<bool>("Emotes", "EnsureArrowBindings", true, "Ensures Up/Down are bound to Emote1/Emote2.");
_preventDuplicateArrowBindings = ((BaseUnityPlugin)this).Config.Bind<bool>("Emotes", "PreventDuplicateArrowBindings", true, "Prevents adding the same binding multiple times.");
_playGrabSfx = ((BaseUnityPlugin)this).Config.Bind<bool>("Hotbar", "PlayGrabSfx", true, "Play grab sound when switching.");
_syncSteps = ((BaseUnityPlugin)this).Config.Bind<bool>("Hotbar", "SyncStepsServerRpc", true, "Call SwitchItemSlotsServerRpc steps to sync with others.");
ApplyConfig();
HookConfigChanges();
GameAccess.Init();
_harmony = new Harmony("kviks.instantitemslots");
_harmony.PatchAll();
if (DebugEnabled)
{
Log.LogInfo((object)"[IIS] Loaded.");
}
}
private void ApplyConfig()
{
DebugEnabled = _debug.Value;
FixEmotesEnabled = _fixEmotes.Value;
ControlledSlots = Mathf.Clamp(_controlledSlots.Value, 1, 4);
CooldownSeconds = Mathf.Max(0f, _cooldownSeconds.Value);
SpawnDelaySeconds = Mathf.Max(0f, _spawnDelaySeconds.Value);
IgnoreBusyDuringSpawnDelay = _ignoreBusyDuringSpawnDelay.Value;
IgnoreBusyChecksAlways = _ignoreBusyChecksAlways.Value;
EnsureArrowBindings = _ensureArrowBindings.Value;
PreventDuplicateArrowBindings = _preventDuplicateArrowBindings.Value;
PlayGrabSfx = _playGrabSfx.Value;
SyncSteps = _syncSteps.Value;
}
private void HookConfigChanges()
{
_debug.SettingChanged += delegate
{
ApplyConfig();
};
_fixEmotes.SettingChanged += delegate
{
ApplyConfig();
};
_controlledSlots.SettingChanged += delegate
{
ApplyConfig();
};
_cooldownSeconds.SettingChanged += delegate
{
ApplyConfig();
};
_spawnDelaySeconds.SettingChanged += delegate
{
ApplyConfig();
};
_ignoreBusyDuringSpawnDelay.SettingChanged += delegate
{
ApplyConfig();
};
_ignoreBusyChecksAlways.SettingChanged += delegate
{
ApplyConfig();
};
_ensureArrowBindings.SettingChanged += delegate
{
ApplyConfig();
};
_preventDuplicateArrowBindings.SettingChanged += delegate
{
ApplyConfig();
};
_playGrabSfx.SettingChanged += delegate
{
ApplyConfig();
};
_syncSteps.SettingChanged += delegate
{
ApplyConfig();
};
}
internal static void HandleLocalUpdate(PlayerControllerB player)
{
if ((Object)(object)player == (Object)null || !((NetworkBehaviour)player).IsOwner || (!player.isPlayerControlled && !player.isTestingPlayer))
{
return;
}
Keyboard current = Keyboard.current;
if (current == null)
{
return;
}
int num = -1;
if (current.digit1Key != null && ((ButtonControl)current.digit1Key).wasPressedThisFrame)
{
num = 0;
}
else if (current.digit2Key != null && ((ButtonControl)current.digit2Key).wasPressedThisFrame)
{
num = 1;
}
else if (current.digit3Key != null && ((ButtonControl)current.digit3Key).wasPressedThisFrame)
{
num = 2;
}
else if (current.digit4Key != null && ((ButtonControl)current.digit4Key).wasPressedThisFrame)
{
num = 3;
}
if (num >= 0)
{
if (DebugEnabled)
{
Log.LogInfo((object)("[IIS] Key pressed -> slot " + (num + 1)));
}
TrySwitchToSlot(player, num);
}
}
private static void TrySwitchToSlot(PlayerControllerB player, int targetSlot)
{
try
{
if (!GameAccess.Ready)
{
if (DebugEnabled)
{
Log.LogWarning((object)"[IIS] Not ready: reflection cache missing.");
}
return;
}
if (player.inTerminalMenu || player.isTypingChat || player.inSpecialMenu)
{
if (DebugEnabled)
{
Log.LogInfo((object)"[IIS] Blocked: menu/terminal/chat.");
}
return;
}
bool flag = SpawnDelaySeconds > 0f && Time.timeSinceLevelLoad < SpawnDelaySeconds;
if (!IgnoreBusyChecksAlways && (!flag || !IgnoreBusyDuringSpawnDelay) && (player.isGrabbingObjectAnimation || player.inSpecialInteractAnimation || player.activatingItem))
{
if (DebugEnabled)
{
Log.LogInfo((object)"[IIS] Blocked: animation/interact/activatingItem.");
}
return;
}
if (player.jetpackControls || player.disablingJetpackControls)
{
if (DebugEnabled)
{
Log.LogInfo((object)"[IIS] Blocked: jetpackControls.");
}
return;
}
if ((Object)(object)player.quickMenuManager != (Object)null && player.quickMenuManager.isMenuOpen)
{
if (DebugEnabled)
{
Log.LogInfo((object)"[IIS] Blocked: quick menu.");
}
return;
}
if (GameAccess.GetThrowingObject(player))
{
if (DebugEnabled)
{
Log.LogInfo((object)"[IIS] Blocked: throwingObject.");
}
return;
}
float timeSinceSwitchingSlots = GameAccess.GetTimeSinceSwitchingSlots(player);
if (timeSinceSwitchingSlots >= 0f && timeSinceSwitchingSlots < CooldownSeconds)
{
if (DebugEnabled)
{
Log.LogInfo((object)("[IIS] Blocked: cooldown timeSinceSwitchingSlots=" + timeSinceSwitchingSlots));
}
return;
}
GrabbableObject[] itemSlots = player.ItemSlots;
if (itemSlots == null || itemSlots.Length == 0)
{
return;
}
int num = Mathf.Min(itemSlots.Length, ControlledSlots);
if (num <= 0)
{
return;
}
targetSlot = Mathf.Clamp(targetSlot, 0, num - 1);
int currentItemSlot = player.currentItemSlot;
if (currentItemSlot == targetSlot)
{
return;
}
int num2 = (targetSlot - currentItemSlot + num) % num;
int num3 = (currentItemSlot - targetSlot + num) % num;
bool flag2 = num2 <= num3;
int num4 = (flag2 ? num2 : num3);
if (num4 > 0)
{
ShipBuildModeManager instance = ShipBuildModeManager.Instance;
if ((Object)(object)instance != (Object)null && instance.InBuildMode)
{
instance.CancelBuildMode(true);
}
for (int i = 0; i < num4; i++)
{
DoOneStep(player, flag2, num);
}
GameAccess.SetTimeSinceSwitchingSlots(player, 0f);
}
}
catch (Exception ex)
{
Log.LogError((object)("[IIS] Switch error: " + ex));
}
}
private static void DoOneStep(PlayerControllerB player, bool forward, int len)
{
int slot = (forward ? ((player.currentItemSlot + 1) % len) : ((player.currentItemSlot == 0) ? (len - 1) : (player.currentItemSlot - 1)));
GameAccess.SwitchToItemSlot(player, slot);
if (SyncSteps)
{
GameAccess.SwitchItemSlotsServerRpc(player, forward);
}
if (!PlayGrabSfx || !((Object)(object)player.currentlyHeldObjectServer != (Object)null))
{
return;
}
Item itemProperties = player.currentlyHeldObjectServer.itemProperties;
if ((Object)(object)itemProperties != (Object)null && (Object)(object)itemProperties.grabSFX != (Object)null)
{
AudioSource component = ((Component)player.currentlyHeldObjectServer).gameObject.GetComponent<AudioSource>();
if ((Object)(object)component != (Object)null)
{
component.PlayOneShot(itemProperties.grabSFX, 0.6f);
}
}
}
}
internal static class GameAccess
{
internal static bool Ready;
private static MethodInfo _switchToItemSlot;
private static MethodInfo _switchItemSlotsServerRpc;
private static FieldInfo _throwingObject;
private static FieldInfo _timeSinceSwitchingSlots;
internal static void Init()
{
try
{
Type typeFromHandle = typeof(PlayerControllerB);
_switchToItemSlot = AccessTools.Method(typeFromHandle, "SwitchToItemSlot", new Type[2]
{
typeof(int),
typeof(GrabbableObject)
}, (Type[])null);
_switchItemSlotsServerRpc = AccessTools.Method(typeFromHandle, "SwitchItemSlotsServerRpc", new Type[1] { typeof(bool) }, (Type[])null);
_throwingObject = AccessTools.Field(typeFromHandle, "throwingObject");
_timeSinceSwitchingSlots = AccessTools.Field(typeFromHandle, "timeSinceSwitchingSlots");
Ready = _switchToItemSlot != null && _switchItemSlotsServerRpc != null && _throwingObject != null && _timeSinceSwitchingSlots != null;
if (InstantItemSlotsPlugin.DebugEnabled && InstantItemSlotsPlugin.Log != null)
{
InstantItemSlotsPlugin.Log.LogInfo((object)("[IIS] Reflection: SwitchToItemSlot=" + (_switchToItemSlot != null) + " SwitchItemSlotsServerRpc=" + (_switchItemSlotsServerRpc != null) + " throwingObject=" + (_throwingObject != null) + " timeSinceSwitchingSlots=" + (_timeSinceSwitchingSlots != null)));
}
}
catch (Exception ex)
{
Ready = false;
if (InstantItemSlotsPlugin.Log != null)
{
InstantItemSlotsPlugin.Log.LogError((object)("[IIS] Reflection init failed: " + ex));
}
}
}
internal static void SwitchToItemSlot(PlayerControllerB player, int slot)
{
_switchToItemSlot.Invoke(player, new object[2] { slot, null });
}
internal static void SwitchItemSlotsServerRpc(PlayerControllerB player, bool forward)
{
_switchItemSlotsServerRpc.Invoke(player, new object[1] { forward });
}
internal static bool GetThrowingObject(PlayerControllerB player)
{
try
{
return (bool)_throwingObject.GetValue(player);
}
catch
{
return false;
}
}
internal static float GetTimeSinceSwitchingSlots(PlayerControllerB player)
{
try
{
return (float)_timeSinceSwitchingSlots.GetValue(player);
}
catch
{
return -1f;
}
}
internal static void SetTimeSinceSwitchingSlots(PlayerControllerB player, float value)
{
try
{
_timeSinceSwitchingSlots.SetValue(player, value);
}
catch
{
}
}
internal static void EnsureEmoteBindings()
{
if (!InstantItemSlotsPlugin.EnsureArrowBindings)
{
return;
}
try
{
IngamePlayerSettings instance = IngamePlayerSettings.Instance;
if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerInput == (Object)null)
{
return;
}
InputActionAsset actions = instance.playerInput.actions;
if (!((Object)(object)actions == (Object)null))
{
InputAction val = actions.FindAction("Emote1", false);
InputAction val2 = actions.FindAction("Emote2", false);
if (val != null)
{
AddBindingOnce(val, "<Keyboard>/upArrow");
}
if (val2 != null)
{
AddBindingOnce(val2, "<Keyboard>/downArrow");
}
}
}
catch
{
}
}
private static void AddBindingOnce(InputAction action, string path)
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
if (!InstantItemSlotsPlugin.PreventDuplicateArrowBindings)
{
InputActionSetupExtensions.AddBinding(action, path, (string)null, (string)null, (string)null);
return;
}
ReadOnlyArray<InputBinding> bindings = action.bindings;
for (int i = 0; i < bindings.Count; i++)
{
InputBinding val = bindings[i];
if (string.Equals(((InputBinding)(ref val)).path, path, StringComparison.OrdinalIgnoreCase))
{
return;
}
}
InputActionSetupExtensions.AddBinding(action, path, (string)null, (string)null, (string)null);
}
}
[HarmonyPatch]
internal static class Patches
{
[HarmonyPatch(typeof(PlayerControllerB), "Update")]
[HarmonyPostfix]
private static void PlayerUpdatePostfix(PlayerControllerB __instance)
{
InstantItemSlotsPlugin.HandleLocalUpdate(__instance);
}
[HarmonyPatch(typeof(PlayerControllerB), "OnEnable")]
[HarmonyPostfix]
private static void PlayerOnEnablePostfix(PlayerControllerB __instance)
{
if (InstantItemSlotsPlugin.FixEmotesEnabled)
{
GameAccess.EnsureEmoteBindings();
if (InstantItemSlotsPlugin.DebugEnabled && InstantItemSlotsPlugin.Log != null)
{
InstantItemSlotsPlugin.Log.LogInfo((object)"[IIS] Emote bindings ensured: UpArrow/DownArrow.");
}
}
}
[HarmonyPatch(typeof(PlayerControllerB), "PerformEmote")]
[HarmonyPrefix]
private static bool PerformEmotePrefix(CallbackContext context, int emoteID)
{
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Invalid comparison between Unknown and I4
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
//IL_0054: Invalid comparison between Unknown and I4
if (!InstantItemSlotsPlugin.FixEmotesEnabled)
{
return true;
}
try
{
if (!((CallbackContext)(ref context)).performed)
{
return false;
}
InputControl control = ((CallbackContext)(ref context)).control;
KeyControl val = (KeyControl)(object)((control is KeyControl) ? control : null);
if (val == null)
{
return false;
}
Key keyCode = val.keyCode;
return (int)keyCode == 63 || (int)keyCode == 64;
}
catch (Exception ex)
{
if (InstantItemSlotsPlugin.Log != null)
{
InstantItemSlotsPlugin.Log.LogError((object)("[IIS] PerformEmote filter error: " + ex));
}
return true;
}
}
}