Decompiled source of InstantItemSlots v15.4.2026

BepInEx/plugins/InstantItemSlots/InstantItemSlots.dll

Decompiled 2 months ago
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;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("InstantItemSlots")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("InstantItemSlots")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("4cc5e582-3cd6-41fb-87fe-c92a18523560")]
[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", "15.04.2026")]
public class InstantItemSlotsPlugin : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	internal static bool DebugEnabled;

	internal static bool ArrowEmotesOnly;

	internal static int ControlledSlots;

	internal static float CooldownSeconds;

	internal static float SpawnDelaySeconds;

	internal static bool IgnoreBusyDuringSpawnDelay;

	internal static bool IgnoreBusyChecksAlways;

	internal static bool PlayGrabSfx;

	internal static bool SyncStepsFallback;

	private static Harmony _harmony;

	private static ConfigEntry<bool> _debug;

	private static ConfigEntry<bool> _arrowEmotesOnly;

	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> _playGrabSfx;

	private static ConfigEntry<bool> _syncStepsFallback;

	private void Awake()
	{
		//IL_014d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0157: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		_debug = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableDebugLogging", false, "Enables debug logging.");
		_arrowEmotesOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("Emotes", "UseArrowEmotesOnly", true, "UpArrow = emote 1, DownArrow = emote 2. Vanilla emote input is blocked while enabled.");
		_controlledSlots = ((BaseUnityPlugin)this).Config.Bind<int>("Hotbar", "DigitControlledSlots", 4, "How many slots are controlled by 1/2/3/4 (1..4).");
		_cooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Hotbar", "DigitSwitchCooldownSeconds", 0f, "Minimum time since last switch allowed.");
		_spawnDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Spawn", "RelaxedRulesWindowSeconds", 0f, "Seconds after level load where switching rules can be relaxed.");
		_ignoreBusyDuringSpawnDelay = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawn", "IgnoreBusyChecksInsideRelaxedWindow", true, "Ignore animation/interact/activatingItem/twoHanded during the relaxed window.");
		_ignoreBusyChecksAlways = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawn", "IgnoreBusyChecksAlways", false, "Always ignore animation/interact/activatingItem/twoHanded checks.");
		_playGrabSfx = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "PlayGrabSoundOnDigitSwitch", true, "Play grab sound when switching with digits.");
		_syncStepsFallback = ((BaseUnityPlugin)this).Config.Bind<bool>("Network", "UseLegacyStepRpcFallback", true, "Fallback sync for old versions without direct slot RPC.");
		ApplyConfig();
		HookConfigChanges();
		GameAccess.Init();
		_harmony = new Harmony("kviks.instantitemslots");
		_harmony.PatchAll();
		if (DebugEnabled)
		{
			Log.LogInfo((object)"[IIS] Loaded.");
		}
	}

	private void ApplyConfig()
	{
		DebugEnabled = _debug.Value;
		ArrowEmotesOnly = _arrowEmotesOnly.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;
		PlayGrabSfx = _playGrabSfx.Value;
		SyncStepsFallback = _syncStepsFallback.Value;
	}

	private void HookConfigChanges()
	{
		_debug.SettingChanged += delegate
		{
			ApplyConfig();
		};
		_arrowEmotesOnly.SettingChanged += delegate
		{
			ApplyConfig();
		};
		_controlledSlots.SettingChanged += delegate
		{
			ApplyConfig();
		};
		_cooldownSeconds.SettingChanged += delegate
		{
			ApplyConfig();
		};
		_spawnDelaySeconds.SettingChanged += delegate
		{
			ApplyConfig();
		};
		_ignoreBusyDuringSpawnDelay.SettingChanged += delegate
		{
			ApplyConfig();
		};
		_ignoreBusyChecksAlways.SettingChanged += delegate
		{
			ApplyConfig();
		};
		_playGrabSfx.SettingChanged += delegate
		{
			ApplyConfig();
		};
		_syncStepsFallback.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;
		}
		if (ArrowEmotesOnly)
		{
			if (current.upArrowKey != null && ((ButtonControl)current.upArrowKey).wasPressedThisFrame)
			{
				TryPerformArrowEmote(player, 1);
				return;
			}
			if (current.downArrowKey != null && ((ButtonControl)current.downArrowKey).wasPressedThisFrame)
			{
				TryPerformArrowEmote(player, 2);
				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 TryPerformArrowEmote(PlayerControllerB player, int emoteId)
	{
		try
		{
			if (!GameAccess.EmoteReady)
			{
				if (DebugEnabled)
				{
					Log.LogWarning((object)"[IIS] Emote reflection not ready.");
				}
			}
			else if (((((NetworkBehaviour)player).IsOwner && player.isPlayerControlled && (!((NetworkBehaviour)player).IsServer || player.isHostPlayerObject)) || player.isTestingPlayer) && GameAccess.CheckConditionsForEmote(player) && !(GameAccess.GetTimeSinceStartingEmote(player) < 0.5f))
			{
				GameAccess.SetTimeSinceStartingEmote(player, 0f);
				GameAccess.SetPerformingEmote(player, value: true);
				if ((Object)(object)player.playerBodyAnimator != (Object)null)
				{
					player.playerBodyAnimator.SetInteger("emoteNumber", emoteId);
				}
				GameAccess.StartPerformingEmoteServerRpc(player);
			}
		}
		catch (Exception ex)
		{
			Log.LogError((object)("[IIS] Emote error: " + ex));
		}
	}

	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 || player.twoHanded))
			{
				if (DebugEnabled)
				{
					Log.LogInfo((object)"[IIS] Blocked: animation/interact/activatingItem/twoHanded.");
				}
				return;
			}
			if ((player.jetpackControls || player.disablingJetpackControls) && (Object)(object)player.currentlyHeldObjectServer != (Object)null && (Object)(object)player.currentlyHeldObjectServer.itemProperties != (Object)null && player.currentlyHeldObjectServer.itemProperties.itemId == 13)
			{
				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);
			if (player.currentItemSlot != targetSlot)
			{
				ShipBuildModeManager instance = ShipBuildModeManager.Instance;
				if ((Object)(object)instance != (Object)null && instance.InBuildMode)
				{
					instance.CancelBuildMode(true);
				}
				if ((Object)(object)player.playerBodyAnimator != (Object)null)
				{
					player.playerBodyAnimator.SetBool("GrabValidated", false);
				}
				if (GameAccess.HasDirectSlotRpc)
				{
					GameAccess.SwitchToItemSlot(player, targetSlot);
					GameAccess.SwitchToSlotServerRpc(player, targetSlot);
				}
				else
				{
					DoLegacyStepSwitch(player, targetSlot, num);
				}
				PlayGrabSound(player);
				GameAccess.SetTimeSinceSwitchingSlots(player, 0f);
			}
		}
		catch (Exception ex)
		{
			Log.LogError((object)("[IIS] Switch error: " + ex));
		}
	}

	private static void DoLegacyStepSwitch(PlayerControllerB player, int targetSlot, int len)
	{
		if (!SyncStepsFallback)
		{
			GameAccess.SwitchToItemSlot(player, targetSlot);
			return;
		}
		int currentItemSlot = player.currentItemSlot;
		int num = (targetSlot - currentItemSlot + len) % len;
		int num2 = (currentItemSlot - targetSlot + len) % len;
		bool flag = num < num2;
		int num3 = (flag ? num : num2);
		if (num3 <= 0 && currentItemSlot != targetSlot)
		{
			flag = true;
			num3 = num;
		}
		for (int i = 0; i < num3; i++)
		{
			int slot = (flag ? ((player.currentItemSlot + 1) % len) : ((player.currentItemSlot == 0) ? (len - 1) : (player.currentItemSlot - 1)));
			GameAccess.SwitchToItemSlot(player, slot);
			GameAccess.SwitchItemSlotsServerRpc(player, flag);
		}
	}

	private static void PlayGrabSound(PlayerControllerB player)
	{
		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;

	internal static bool EmoteReady;

	private static MethodInfo _switchToItemSlot;

	private static MethodInfo _switchToSlotServerRpc;

	private static MethodInfo _switchItemSlotsServerRpc;

	private static MethodInfo _checkConditionsForEmote;

	private static MethodInfo _startPerformingEmoteServerRpc;

	private static FieldInfo _throwingObject;

	private static FieldInfo _timeSinceSwitchingSlots;

	private static FieldInfo _timeSinceStartingEmote;

	private static FieldInfo _performingEmote;

	internal static bool HasDirectSlotRpc => _switchToSlotServerRpc != null;

	internal static void Init()
	{
		try
		{
			Type typeFromHandle = typeof(PlayerControllerB);
			_switchToItemSlot = AccessTools.Method(typeFromHandle, "SwitchToItemSlot", new Type[2]
			{
				typeof(int),
				typeof(GrabbableObject)
			}, (Type[])null);
			_switchToSlotServerRpc = AccessTools.Method(typeFromHandle, "SwitchToSlotServerRpc", new Type[1] { typeof(int) }, (Type[])null);
			_switchItemSlotsServerRpc = AccessTools.Method(typeFromHandle, "SwitchItemSlotsServerRpc", new Type[1] { typeof(bool) }, (Type[])null);
			_throwingObject = AccessTools.Field(typeFromHandle, "throwingObject");
			_timeSinceSwitchingSlots = AccessTools.Field(typeFromHandle, "timeSinceSwitchingSlots");
			_checkConditionsForEmote = AccessTools.Method(typeFromHandle, "CheckConditionsForEmote", (Type[])null, (Type[])null);
			_startPerformingEmoteServerRpc = AccessTools.Method(typeFromHandle, "StartPerformingEmoteServerRpc", (Type[])null, (Type[])null);
			_timeSinceStartingEmote = AccessTools.Field(typeFromHandle, "timeSinceStartingEmote");
			_performingEmote = AccessTools.Field(typeFromHandle, "performingEmote");
			Ready = _switchToItemSlot != null && _throwingObject != null && _timeSinceSwitchingSlots != null && (_switchToSlotServerRpc != null || _switchItemSlotsServerRpc != null);
			EmoteReady = _checkConditionsForEmote != null && _startPerformingEmoteServerRpc != null && _timeSinceStartingEmote != null && _performingEmote != null;
			if (InstantItemSlotsPlugin.DebugEnabled && InstantItemSlotsPlugin.Log != null)
			{
				InstantItemSlotsPlugin.Log.LogInfo((object)("[IIS] Reflection: SwitchToItemSlot=" + (_switchToItemSlot != null) + " SwitchToSlotServerRpc=" + (_switchToSlotServerRpc != null) + " SwitchItemSlotsServerRpc=" + (_switchItemSlotsServerRpc != null) + " CheckConditionsForEmote=" + (_checkConditionsForEmote != null) + " StartPerformingEmoteServerRpc=" + (_startPerformingEmoteServerRpc != null)));
			}
		}
		catch (Exception ex)
		{
			Ready = false;
			EmoteReady = 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 SwitchToSlotServerRpc(PlayerControllerB player, int slot)
	{
		_switchToSlotServerRpc?.Invoke(player, new object[1] { slot });
	}

	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 bool CheckConditionsForEmote(PlayerControllerB player)
	{
		try
		{
			return (bool)_checkConditionsForEmote.Invoke(player, null);
		}
		catch
		{
			return false;
		}
	}

	internal static float GetTimeSinceStartingEmote(PlayerControllerB player)
	{
		try
		{
			return (float)_timeSinceStartingEmote.GetValue(player);
		}
		catch
		{
			return -1f;
		}
	}

	internal static void SetTimeSinceStartingEmote(PlayerControllerB player, float value)
	{
		try
		{
			_timeSinceStartingEmote.SetValue(player, value);
		}
		catch
		{
		}
	}

	internal static void SetPerformingEmote(PlayerControllerB player, bool value)
	{
		try
		{
			_performingEmote.SetValue(player, value);
		}
		catch
		{
		}
	}

	internal static void StartPerformingEmoteServerRpc(PlayerControllerB player)
	{
		_startPerformingEmoteServerRpc?.Invoke(player, null);
	}
}
[HarmonyPatch]
internal static class Patches
{
	[HarmonyPatch(typeof(PlayerControllerB), "Update")]
	[HarmonyPostfix]
	private static void PlayerUpdatePostfix(PlayerControllerB __instance)
	{
		InstantItemSlotsPlugin.HandleLocalUpdate(__instance);
	}

	[HarmonyPatch(typeof(PlayerControllerB), "PerformEmote")]
	[HarmonyPrefix]
	private static bool PerformEmotePrefix()
	{
		return !InstantItemSlotsPlugin.ArrowEmotesOnly;
	}
}