Decompiled source of BetterReviveExperience v0.3.5

plugins/BetterReviveExperience/BetterReviveExperience.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterReviveExperience.Patches;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BetterReviveExperience")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.5.0")]
[assembly: AssemblyInformationalVersion("0.3.5+541bb47aa5d00ac3b166b99c8031dfe7a7c07d19")]
[assembly: AssemblyProduct("BetterReviveExperience")]
[assembly: AssemblyTitle("BetterReviveExperience")]
[assembly: AssemblyVersion("0.3.5.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BetterReviveExperience
{
	[BepInPlugin("com.mods.betterreviveexperience", "BetterReviveExperience", "0.3.5")]
	[BepInDependency("nickklmao.repoconfig", "1.2.6")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PLUGIN_GUID = "com.mods.betterreviveexperience";

		public const string PLUGIN_NAME = "BetterReviveExperience";

		public const string PLUGIN_VERSION = "0.3.5";

		private const int ReviveCostStep = 1000;

		private const int ReviveCostMaximum = 100000;

		private static readonly string[] HeldHeadReviveKeyOptions = new string[4] { "H", "R", "Y", "F" };

		private static readonly string[] ReviveCostOptions = BuildReviveCostOptions();

		private Harmony _harmony;

		public static ManualLogSource Log { get; private set; }

		public static ConfigEntry<bool> KeepItemsOnDeath { get; private set; }

		public static ConfigEntry<bool> ProtectHeldItems { get; private set; }

		public static ConfigEntry<bool> SwapHeldItemOnOccupiedSlot { get; private set; }

		public static ConfigEntry<bool> ReturnHeldItemOnDeath { get; private set; }

		public static ConfigEntry<string> ReviveTrigger { get; private set; }

		public static ConfigEntry<string> ReviveCost { get; private set; }

		public static ConfigEntry<int> ReviveHealthPercent { get; private set; }

		public static ConfigEntry<bool> EnableHeldHeadRevive { get; private set; }

		public static ConfigEntry<string> HeldHeadReviveKey { get; private set; }

		public static ConfigEntry<bool> EnableCartRevive { get; private set; }

		public static ConfigEntry<bool> EnableShopRevive { get; private set; }

		public static ConfigEntry<bool> DebugLogging { get; private set; }

		public static int ReviveCostAmount
		{
			get
			{
				if (!int.TryParse(ReviveCost.Value, out var result))
				{
					return 0;
				}
				return Mathf.Clamp(result, 0, 100000);
			}
		}

		public static ReviveMode CurrentReviveMode
		{
			get
			{
				if (!Enum.TryParse<ReviveMode>(ReviveTrigger.Value, ignoreCase: true, out var result))
				{
					return ReviveMode.Disabled;
				}
				return result;
			}
		}

		public static KeyCode HeldHeadReviveKeyCode
		{
			get
			{
				//IL_005a: Unknown result type (might be due to invalid IL or missing references)
				string value = HeldHeadReviveKey.Value;
				if (value != null && value.Length == 1 && value[0] >= '0' && value[0] <= '9')
				{
					return (KeyCode)(48 + (value[0] - 48));
				}
				if (string.Equals(value, "Enter", StringComparison.OrdinalIgnoreCase))
				{
					return (KeyCode)13;
				}
				if (!Enum.TryParse<KeyCode>(value, ignoreCase: true, out KeyCode result))
				{
					return (KeyCode)104;
				}
				return result;
			}
		}

		private void Awake()
		{
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Expected O, but got Unknown
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Expected O, but got Unknown
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Expected O, but got Unknown
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_021a: Expected O, but got Unknown
			//IL_0356: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			KeepItemsOnDeath = ((BaseUnityPlugin)this).Config.Bind<bool>("Inventory", "KeepItemsOnDeath", true, "Keep inventory-slot items when a player dies.");
			ProtectHeldItems = ((BaseUnityPlugin)this).Config.Bind<bool>("Inventory", "ProtectHeldItems", true, "Return a storable held item to its original or first free vanilla inventory slot after an impact or tumble. If all three slots are occupied, the local player keeps holding it.");
			SwapHeldItemOnOccupiedSlot = ((BaseUnityPlugin)this).Config.Bind<bool>("Inventory", "SwapHeldItemOnOccupiedSlot", true, "When an occupied inventory hotkey is pressed while holding another storable item, store the held item in the vacated slot instead of dropping it.");
			ReturnHeldItemOnDeath = ((BaseUnityPlugin)this).Config.Bind<bool>("Inventory", "ReturnHeldItemOnDeath", true, "Return the storable item held at death to the first free vanilla inventory slot (slots 1-3). If all three are occupied, place it near the death head instead.");
			ReviveTrigger = ((BaseUnityPlugin)this).Config.Bind<string>("Revive", "Mode", "ExtractionOrTruck", new ConfigDescription("Disabled, extraction machine activated, or direct extraction/truck revive.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Disabled", "ExtractionMachineActivated", "ExtractionOrTruck" }), Array.Empty<object>()));
			ReviveCost = ((BaseUnityPlugin)this).Config.Bind<string>("Revive", "Cost", "0", new ConfigDescription("Shared team currency consumed per revive. Selectable in 1,000-currency steps.", (AcceptableValueBase)(object)new AcceptableValueList<string>(ReviveCostOptions), Array.Empty<object>()));
			ReviveHealthPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Revive", "HealthPercent", 25, new ConfigDescription("Health after revive, from 1 to 100 percent.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			EnableHeldHeadRevive = ((BaseUnityPlugin)this).Config.Bind<bool>("Revive", "EnableHeldHeadRevive", true, "Allow the host to revive the death head currently held with the physics grabber by pressing H.");
			HeldHeadReviveKey = ((BaseUnityPlugin)this).Config.Bind<string>("Revive", "HeldHeadReviveKey", "H", new ConfigDescription("Key used for held-head revive.", (AcceptableValueBase)(object)new AcceptableValueList<string>(HeldHeadReviveKeyOptions), Array.Empty<object>()));
			EnableCartRevive = ((BaseUnityPlugin)this).Config.Bind<bool>("Revive", "EnableCartRevive", true, "Immediately revive a player when their death head is placed inside a cart.");
			EnableShopRevive = ((BaseUnityPlugin)this).Config.Bind<bool>("Revive", "EnableShopRevive", true, "Immediately revive players killed in the shop.");
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", true, "Write detailed death-head, inventory, and Harmony diagnostics to the BepInEx log. Enabled by default during development.");
			NormalizeConfig();
			ReviveController.ValidateGameApi();
			_harmony = new Harmony("com.mods.betterreviveexperience");
			RegisterPatches();
			int num = 0;
			foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods())
			{
				num++;
				Debug("[BRE] patched: " + patchedMethod.DeclaringType?.FullName + "." + patchedMethod.Name);
			}
			Log.LogInfo((object)"BetterReviveExperience v0.3.5 loaded");
			Log.LogInfo((object)($"[BRE] patches={num}, keepItems={KeepItemsOnDeath.Value}, " + $"protectHeldItems={ProtectHeldItems.Value}, swapHeldItems={SwapHeldItemOnOccupiedSlot.Value}, " + $"returnDeathItem={ReturnHeldItemOnDeath.Value}, " + $"mode={CurrentReviveMode}, cost={ReviveCostAmount}, " + $"health={ReviveHealthPercent.Value}%, heldHead={EnableHeldHeadRevive.Value}/" + $"{HeldHeadReviveKeyCode}, " + $"cart={EnableCartRevive.Value}, shop={EnableShopRevive.Value}"));
			if (Chainloader.PluginInfos.ContainsKey("zichen.gametools"))
			{
				Log.LogWarning((object)"[BRE] GameTools detected. Disable its automatic death-head revive options to keep BetterReviveExperience cost, health, and trigger rules authoritative.");
			}
			WarnOverlappingReviveMod("Hypn.ReviveHeadInTruckOrExtractionPoint", "ReviveHeadInTruckOrExtractionPoint");
			WarnOverlappingReviveMod("Kai.Revive_at_Cart", "CartRevive");
			WarnOverlappingReviveMod("endersaltz.LetMeShop", "LetMeShop");
			WarnOverlappingReviveMod("com.yuniverse.reviveplayer", "UltimateReviveNew");
			if (Chainloader.PluginInfos.ContainsKey("Mistyck.NoForcedDropMod"))
			{
				Log.LogWarning((object)"[BRE] NoForcedDropMod detected. Disable its overlapping forced-drop hook while BRE held-item protection is enabled.");
			}
		}

		private void RegisterPatches()
		{
			RegisterPostfix(typeof(PlayerAvatar), "Update", typeof(PlayerAvatarUpdatePatch), "Postfix");
			RegisterPrefix(typeof(PlayerAvatar), "PlayerDeathRPC", typeof(PlayerDeathPatch), "Prefix", 800);
			RegisterPostfix(typeof(PlayerAvatar), "PlayerDeathRPC", typeof(PlayerDeathPatch), "Postfix", 0);
			RegisterPostfix(typeof(PlayerDeathHead), "Trigger", typeof(DeathHeadTriggerPatch), "Postfix");
			RegisterPostfix(typeof(PlayerDeathHead), "Update", typeof(DeathHeadUpdatePatch), "Postfix", 400, new string[1] { "zichen.gametools" });
			RegisterPostfix(typeof(PlayerAvatar), "ReviveRPC", typeof(PlayerRevivePatch), "Postfix", 0);
			RegisterPostfix(typeof(PhysGrabCart), "Update", typeof(CartUpdatePatch), "Postfix");
			RegisterPrefix(typeof(PhysGrabber), "ReleaseObjectRPC", typeof(ForcedGrabReleaseReceivePatch), "Prefix", 800);
			RegisterPrefix(typeof(ItemEquippable), "RPC_CompleteUnequip", typeof(ForcedUnequipPatch), "Prefix", 800);
			RegisterPrefix(typeof(RunManager), "ChangeLevel", typeof(LevelChangePatch), "Prefix");
			RegisterPostfix(typeof(RoundDirector), "Start", typeof(RoundStartPatch), "Postfix");
			RegisterPostfix(typeof(MainMenuOpen), "Start", typeof(MainMenuPatch), "Postfix");
		}

		private void RegisterPrefix(MethodInfo target, Type patchType, string patchName, int priority = 400)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(patchType, patchName, (Type[])null, (Type[])null);
			if (target == null || methodInfo == null)
			{
				throw new MissingMethodException("[BRE] Cannot register prefix for " + target?.DeclaringType?.FullName + "." + target?.Name);
			}
			_harmony.Patch((MethodBase)target, new HarmonyMethod(methodInfo)
			{
				priority = priority
			}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private void RegisterPrefix(Type targetType, string targetName, Type patchType, string patchName, int priority = 400)
		{
			RegisterPatch(targetType, targetName, patchType, patchName, isPrefix: true, priority, null);
		}

		private void RegisterPostfix(Type targetType, string targetName, Type patchType, string patchName, int priority = 400, string[] after = null)
		{
			RegisterPatch(targetType, targetName, patchType, patchName, isPrefix: false, priority, after);
		}

		private void RegisterPatch(Type targetType, string targetName, Type patchType, string patchName, bool isPrefix, int priority, string[] after)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(targetType, targetName, (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(patchType, patchName, (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				throw new MissingMethodException("[BRE] Cannot patch " + targetType.FullName + "." + targetName + " with " + patchType.FullName + "." + patchName);
			}
			HarmonyMethod val = new HarmonyMethod(methodInfo2)
			{
				priority = priority,
				after = after
			};
			_harmony.Patch((MethodBase)methodInfo, isPrefix ? val : null, isPrefix ? null : val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void NormalizeConfig()
		{
			ReviveHealthPercent.Value = Mathf.Clamp(ReviveHealthPercent.Value, 1, 100);
		}

		private static string[] BuildReviveCostOptions()
		{
			string[] array = new string[101];
			for (int i = 0; i <= 100000; i += 1000)
			{
				array[i / 1000] = i.ToString();
			}
			return array;
		}

		public static void Debug(string message)
		{
			ConfigEntry<bool> debugLogging = DebugLogging;
			if (debugLogging != null && debugLogging.Value)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)message);
				}
			}
		}

		private static void WarnOverlappingReviveMod(string guid, string name)
		{
			if (Chainloader.PluginInfos.ContainsKey(guid))
			{
				Log.LogWarning((object)("[BRE] " + name + " detected. Disable it while BRE manages the same revive trigger to prevent duplicate ReviveRPC calls, health changes, or charges."));
			}
		}

		private void OnDestroy()
		{
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogWarning((object)"[BRE] plugin component destroyed; keeping session patches installed");
			}
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.mods.betterreviveexperience";

		public const string PLUGIN_NAME = "BetterReviveExperience";

		public const string PLUGIN_VERSION = "0.3.5";
	}
	internal static class ReviveController
	{
		private sealed class PendingRevive
		{
			public int Cost;

			public int TargetHealth;

			public float StartedAt;

			public string Trigger;
		}

		private sealed class PendingHealth
		{
			public PlayerAvatar Player;

			public int TargetHealth;

			public float ApplyAt;
		}

		private const float ReviveTimeoutSeconds = 5f;

		private const float HealthSyncDelaySeconds = 0.35f;

		private static readonly FieldInfo SteamIdField = AccessTools.Field(typeof(PlayerAvatar), "steamID");

		private static readonly FieldInfo DeadSetField = AccessTools.Field(typeof(PlayerAvatar), "deadSet");

		private static readonly FieldInfo PlayerDeathHeadField = AccessTools.Field(typeof(PlayerAvatar), "playerDeathHead");

		private static readonly FieldInfo TriggeredField = AccessTools.Field(typeof(PlayerDeathHead), "triggered");

		private static readonly FieldInfo TriggeredTimerField = AccessTools.Field(typeof(PlayerDeathHead), "triggeredTimer");

		private static readonly FieldInfo DeathHeadPhysGrabObjectField = AccessTools.Field(typeof(PlayerDeathHead), "physGrabObject");

		private static readonly FieldInfo InExtractionPointField = AccessTools.Field(typeof(PlayerDeathHead), "inExtractionPoint");

		private static readonly FieldInfo DeathHeadRoomVolumeField = AccessTools.Field(typeof(PlayerDeathHead), "roomVolumeCheck");

		private static readonly FieldInfo RoomInTruckField = AccessTools.Field(typeof(RoomVolumeCheck), "inTruck");

		private static readonly FieldInfo RoomInExtractionPointField = AccessTools.Field(typeof(RoomVolumeCheck), "inExtractionPoint");

		private static readonly FieldInfo CartItemsField = AccessTools.Field(typeof(PhysGrabCart), "itemsInCart");

		private static readonly FieldInfo ExtractionPointActiveField = AccessTools.Field(typeof(RoundDirector), "extractionPointActive");

		private static readonly FieldInfo MaxHealthField = AccessTools.Field(typeof(PlayerHealth), "maxHealth");

		private static readonly FieldInfo InventorySpotIndexField = AccessTools.Field(typeof(ItemEquippable), "inventorySpotIndex");

		private static readonly MethodInfo UpdateItemStateMethod = AccessTools.Method(typeof(ItemEquippable), "RPC_UpdateItemState", (Type[])null, (Type[])null);

		private static readonly MethodInfo UpdateHealthMethod = AccessTools.Method(typeof(PlayerHealth), "UpdateHealthRPC", (Type[])null, (Type[])null);

		private static readonly MethodInfo StatGetRunCurrencyMethod = AccessTools.Method(typeof(SemiFunc), "StatGetRunCurrency", (Type[])null, (Type[])null);

		private static readonly MethodInfo StatSetRunCurrencyMethod = AccessTools.Method(typeof(SemiFunc), "StatSetRunCurrency", (Type[])null, (Type[])null);

		private static readonly Dictionary<string, PendingRevive> PendingRevives = new Dictionary<string, PendingRevive>();

		private static readonly Dictionary<string, PendingHealth> PendingHealthSyncs = new Dictionary<string, PendingHealth>();

		private static readonly Dictionary<string, string> LastHeadStates = new Dictionary<string, string>();

		private static readonly HashSet<string> InsufficientFundsLogged = new HashSet<string>();

		private static readonly HashSet<string> ObservedPlayerUpdates = new HashSet<string>();

		private static readonly HashSet<string> ObservedDeathHeadUpdates = new HashSet<string>();

		public static bool ValidateGameApi()
		{
			List<string> list = new List<string>();
			Require(SteamIdField, "PlayerAvatar.steamID", list);
			Require(DeadSetField, "PlayerAvatar.deadSet", list);
			Require(PlayerDeathHeadField, "PlayerAvatar.playerDeathHead", list);
			Require(TriggeredField, "PlayerDeathHead.triggered", list);
			Require(TriggeredTimerField, "PlayerDeathHead.triggeredTimer", list);
			Require(DeathHeadPhysGrabObjectField, "PlayerDeathHead.physGrabObject", list);
			Require(InExtractionPointField, "PlayerDeathHead.inExtractionPoint", list);
			Require(DeathHeadRoomVolumeField, "PlayerDeathHead.roomVolumeCheck", list);
			Require(RoomInTruckField, "RoomVolumeCheck.inTruck", list);
			Require(RoomInExtractionPointField, "RoomVolumeCheck.inExtractionPoint", list);
			Require(CartItemsField, "PhysGrabCart.itemsInCart", list);
			Require(ExtractionPointActiveField, "RoundDirector.extractionPointActive", list);
			Require(MaxHealthField, "PlayerHealth.maxHealth", list);
			Require(InventorySpotIndexField, "ItemEquippable.inventorySpotIndex", list);
			Require(UpdateItemStateMethod, "ItemEquippable.RPC_UpdateItemState", list);
			Require(UpdateHealthMethod, "PlayerHealth.UpdateHealthRPC", list);
			Require(StatGetRunCurrencyMethod, "SemiFunc.StatGetRunCurrency", list);
			Require(StatSetRunCurrencyMethod, "SemiFunc.StatSetRunCurrency", list);
			WeaponProtectionController.ValidateGameApi(list);
			if (list.Count == 0)
			{
				Plugin.Debug("[BRE] game API validation passed");
				return true;
			}
			Plugin.Log.LogError((object)("[BRE] game API validation failed: " + string.Join(", ", list)));
			return false;
		}

		private static void Require(MemberInfo member, string name, ICollection<string> missing)
		{
			if (member == null)
			{
				missing.Add(name);
			}
		}

		public static string GetPlayerId(PlayerAvatar player)
		{
			if (!Object.op_Implicit((Object)(object)player))
			{
				return string.Empty;
			}
			string text = SteamIdField?.GetValue(player) as string;
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			if (!Object.op_Implicit((Object)(object)player.photonView))
			{
				return $"instance:{((Object)player).GetInstanceID()}";
			}
			return $"view:{player.photonView.ViewID}";
		}

		internal static bool IsDead(PlayerAvatar player)
		{
			if (Object.op_Implicit((Object)(object)player) && DeadSetField != null)
			{
				return (bool)DeadSetField.GetValue(player);
			}
			return false;
		}

		internal static bool IsHost()
		{
			if ((Object)(object)GameManager.instance == (Object)null)
			{
				return false;
			}
			if (!SemiFunc.IsMultiplayer())
			{
				return SemiFunc.IsMasterClientOrSingleplayer();
			}
			if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
			{
				return SemiFunc.IsMasterClientOrSingleplayer();
			}
			return false;
		}

		private static bool IsHeadReady(PlayerDeathHead deathHead)
		{
			if (!Object.op_Implicit((Object)(object)deathHead) || TriggeredField == null || !(bool)TriggeredField.GetValue(deathHead))
			{
				return false;
			}
			if (!(TriggeredTimerField == null))
			{
				return (float)TriggeredTimerField.GetValue(deathHead) <= 0f;
			}
			return true;
		}

		private static PlayerDeathHead GetDeathHead(PlayerAvatar player)
		{
			if (!Object.op_Implicit((Object)(object)player) || !(PlayerDeathHeadField != null))
			{
				return null;
			}
			object? value = PlayerDeathHeadField.GetValue(player);
			return (PlayerDeathHead)((value is PlayerDeathHead) ? value : null);
		}

		public static void OnPlayerDeath(PlayerAvatar player)
		{
			if (Object.op_Implicit((Object)(object)player) && IsHost())
			{
				string playerId = GetPlayerId(player);
				bool flag = IsDead(player);
				Plugin.Debug($"[BRE] PlayerDeathRPC observed: player={playerId}, host=true, deadSet={flag}");
				if (flag)
				{
					PendingHealthSyncs.Remove(playerId);
					InsufficientFundsLogged.Remove(playerId);
					Plugin.Debug("[BRE] death confirmed: player=" + playerId);
				}
			}
		}

		public static void OnPlayerAvatarUpdated(PlayerAvatar player)
		{
			if (Object.op_Implicit((Object)(object)player) && IsHost())
			{
				string playerId = GetPlayerId(player);
				if (ObservedPlayerUpdates.Add(playerId))
				{
					Plugin.Debug("[BRE] runtime probe: PlayerAvatar.Update reached, player=" + playerId + ", host=true");
				}
				ApplyPendingHealth(playerId);
				if (!IsDead(player) && PendingRevives.TryGetValue(playerId, out var value))
				{
					CompleteRevive(playerId, player, value);
				}
				PlayerController instance = PlayerController.instance;
				if (Object.op_Implicit((Object)(object)instance) && (Object)(object)instance.playerAvatarScript == (Object)(object)player)
				{
					TryHeldHeadRevive(instance);
				}
			}
		}

		private static void TryHeldHeadRevive(PlayerController controller)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.EnableHeldHeadRevive.Value && Input.GetKeyDown(Plugin.HeldHeadReviveKeyCode) && controller.physGrabActive && Object.op_Implicit((Object)(object)controller.physGrabObject))
			{
				PlayerDeathHead val = controller.physGrabObject.GetComponent<PlayerDeathHead>();
				if (!Object.op_Implicit((Object)(object)val))
				{
					val = controller.physGrabObject.GetComponentInParent<PlayerDeathHead>();
				}
				if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.playerAvatar) || !IsDead(val.playerAvatar) || !IsHeadReady(val))
				{
					Plugin.Debug("[BRE] held-head revive ignored: grabbed object is not a ready death head");
				}
				else
				{
					BeginRevive(val, revivedByTruck: false, "held-head-" + Plugin.HeldHeadReviveKey.Value);
				}
			}
		}

		public static void OnDeathHeadTriggered(PlayerDeathHead deathHead)
		{
			if (Object.op_Implicit((Object)(object)deathHead) && IsHost() && Object.op_Implicit((Object)(object)deathHead.playerAvatar))
			{
				Plugin.Debug("[BRE] death head triggered: player=" + GetPlayerId(deathHead.playerAvatar) + ", host=true");
			}
		}

		public static void OnDeathHeadUpdated(PlayerDeathHead deathHead)
		{
			if (!Object.op_Implicit((Object)(object)deathHead) || !IsHost() || !Object.op_Implicit((Object)(object)deathHead.playerAvatar))
			{
				return;
			}
			PlayerAvatar playerAvatar = deathHead.playerAvatar;
			string playerId = GetPlayerId(playerAvatar);
			if (ObservedDeathHeadUpdates.Add(playerId))
			{
				Plugin.Debug("[BRE] runtime probe: PlayerDeathHead.Update reached, player=" + playerId + ", host=true");
			}
			ApplyPendingHealth(playerId);
			if (!IsDead(playerAvatar))
			{
				return;
			}
			if (PendingRevives.TryGetValue(playerId, out var value))
			{
				if (Time.time - value.StartedAt < 5f)
				{
					return;
				}
				PendingRevives.Remove(playerId);
				Refund(value.Cost);
				Plugin.Log.LogWarning((object)("[BRE] revive timed out and refunded: player=" + playerId + ", " + $"trigger={value.Trigger}, cost={value.Cost}"));
			}
			if (!IsHeadReady(deathHead))
			{
				return;
			}
			if (Plugin.EnableShopRevive.Value && SemiFunc.RunIsShop())
			{
				BeginRevive(deathHead, revivedByTruck: false, "shop");
			}
			else
			{
				if (Plugin.CurrentReviveMode == ReviveMode.Disabled)
				{
					return;
				}
				object? obj = DeathHeadRoomVolumeField?.GetValue(deathHead);
				RoomVolumeCheck val = (RoomVolumeCheck)((obj is RoomVolumeCheck) ? obj : null);
				bool flag = InExtractionPointField != null && (bool)InExtractionPointField.GetValue(deathHead);
				bool flag2 = ReadBool(val, RoomInExtractionPointField);
				bool flag3 = flag || flag2;
				bool flag4 = ReadBool(val, RoomInTruckField);
				bool flag5 = (Object)(object)RoundDirector.instance != (Object)null && ExtractionPointActiveField != null && (bool)ExtractionPointActiveField.GetValue(RoundDirector.instance);
				string text = $"roomCheck={Object.op_Implicit((Object)(object)val)}, headExtraction={flag}, " + $"roomExtraction={flag2}, truck={flag4}, machine={flag5}";
				if (!LastHeadStates.TryGetValue(playerId, out var value2) || value2 != text)
				{
					LastHeadStates[playerId] = text;
					Plugin.Debug("[BRE] death head: player=" + playerId + ", " + text);
				}
				if (Plugin.CurrentReviveMode == ReviveMode.ExtractionMachineActivated)
				{
					if (flag3 && flag5)
					{
						BeginRevive(deathHead, revivedByTruck: false, "extraction-machine");
					}
				}
				else if (flag4)
				{
					BeginRevive(deathHead, revivedByTruck: true, "truck");
				}
				else if (flag3)
				{
					BeginRevive(deathHead, revivedByTruck: false, "extraction");
				}
			}
		}

		public static void OnCartUpdated(PhysGrabCart cart)
		{
			if (!Plugin.EnableCartRevive.Value || !IsHost() || !Object.op_Implicit((Object)(object)cart) || (Object)(object)GameDirector.instance == (Object)null || CartItemsField == null || !(CartItemsField.GetValue(cart) is IEnumerable items))
			{
				return;
			}
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if (!Object.op_Implicit((Object)(object)player) || !IsDead(player))
				{
					continue;
				}
				PlayerDeathHead deathHead = GetDeathHead(player);
				if (IsHeadReady(deathHead))
				{
					object? obj = DeathHeadPhysGrabObjectField?.GetValue(deathHead);
					PhysGrabObject val = (PhysGrabObject)((obj is PhysGrabObject) ? obj : null);
					if (Object.op_Implicit((Object)(object)val) && Contains(items, val))
					{
						BeginRevive(deathHead, revivedByTruck: false, "cart");
					}
				}
			}
		}

		private static bool Contains(IEnumerable items, PhysGrabObject target)
		{
			foreach (object item in items)
			{
				PhysGrabObject val = (PhysGrabObject)((item is PhysGrabObject) ? item : null);
				if (val != null && (Object)(object)val == (Object)(object)target)
				{
					return true;
				}
			}
			return false;
		}

		private static bool ReadBool(object instance, FieldInfo field)
		{
			if (instance != null && field != null)
			{
				return (bool)field.GetValue(instance);
			}
			return false;
		}

		private static void BeginRevive(PlayerDeathHead deathHead, bool revivedByTruck, string trigger)
		{
			if (!IsHost())
			{
				return;
			}
			PlayerAvatar val = (Object.op_Implicit((Object)(object)deathHead) ? deathHead.playerAvatar : null);
			if (!Object.op_Implicit((Object)(object)val) || !IsDead(val) || PendingRevives.ContainsKey(GetPlayerId(val)))
			{
				return;
			}
			if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null)
			{
				Plugin.Log.LogWarning((object)("[BRE] revive postponed: currency managers are not ready, trigger=" + trigger));
				return;
			}
			string playerId = GetPlayerId(val);
			int reviveCostAmount = Plugin.ReviveCostAmount;
			int num = SemiFunc.StatGetRunCurrency();
			if (num < reviveCostAmount)
			{
				if (InsufficientFundsLogged.Add(playerId))
				{
					Plugin.Log.LogInfo((object)("[BRE] revive denied: player=" + playerId + ", trigger=" + trigger + ", " + $"cost={reviveCostAmount}, currency={num}"));
				}
				return;
			}
			int maxHealth = GetMaxHealth(val.playerHealth);
			int num2 = Mathf.Clamp(Mathf.CeilToInt((float)(maxHealth * Plugin.ReviveHealthPercent.Value) / 100f), 1, maxHealth);
			if (reviveCostAmount > 0)
			{
				SemiFunc.StatSetRunCurrency(num - reviveCostAmount);
			}
			PendingRevives[playerId] = new PendingRevive
			{
				Cost = reviveCostAmount,
				TargetHealth = num2,
				StartedAt = Time.time,
				Trigger = trigger
			};
			Plugin.Log.LogInfo((object)("[BRE] revive request: player=" + playerId + ", trigger=" + trigger + ", " + $"cost={reviveCostAmount}, health={num2}/{maxHealth}"));
			try
			{
				val.Revive(revivedByTruck);
			}
			catch (Exception ex)
			{
				if (!IsDead(val))
				{
					Plugin.Log.LogWarning((object)("[BRE] native revive threw after clearing death state; " + $"continuing health sync: player={playerId}, trigger={trigger}, {ex}"));
					if (PendingRevives.TryGetValue(playerId, out var value))
					{
						CompleteRevive(playerId, val, value);
					}
				}
				else
				{
					PendingRevives.Remove(playerId);
					Refund(reviveCostAmount);
					Plugin.Log.LogError((object)("[BRE] revive call failed and refunded: player=" + playerId + ", " + $"trigger={trigger}, {ex}"));
				}
			}
		}

		public static void OnPlayerRevived(PlayerAvatar player)
		{
			if (IsHost() && Object.op_Implicit((Object)(object)player) && !IsDead(player))
			{
				string playerId = GetPlayerId(player);
				if (!PendingRevives.TryGetValue(playerId, out var value))
				{
					Plugin.Debug("[BRE] vanilla/other revive observed: player=" + playerId);
				}
				else
				{
					CompleteRevive(playerId, player, value);
				}
			}
		}

		private static void CompleteRevive(string playerId, PlayerAvatar player, PendingRevive pending)
		{
			PendingRevives.Remove(playerId);
			InsufficientFundsLogged.Remove(playerId);
			LastHeadStates.Remove(playerId);
			PendingHealthSyncs[playerId] = new PendingHealth
			{
				Player = player,
				TargetHealth = pending.TargetHealth,
				ApplyAt = Time.time + 0.35f
			};
			Plugin.Log.LogInfo((object)$"[BRE] revive confirmed: player={playerId}, trigger={pending.Trigger}, cost={pending.Cost}");
		}

		private static void ApplyPendingHealth(string playerId)
		{
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			if (!IsHost() || !PendingHealthSyncs.TryGetValue(playerId, out var value) || Time.time < value.ApplyAt)
			{
				return;
			}
			PendingHealthSyncs.Remove(playerId);
			PlayerAvatar player = value.Player;
			if (!Object.op_Implicit((Object)(object)player) || !Object.op_Implicit((Object)(object)player.playerHealth))
			{
				return;
			}
			PlayerHealth playerHealth = player.playerHealth;
			int maxHealth = GetMaxHealth(playerHealth);
			int num = Mathf.Clamp(value.TargetHealth, 1, maxHealth);
			if (SemiFunc.IsMultiplayer())
			{
				PhotonView component = ((Component)playerHealth).GetComponent<PhotonView>();
				if (!Object.op_Implicit((Object)(object)component))
				{
					Plugin.Log.LogWarning((object)("[BRE] revive health sync failed: player=" + playerId + ", PhotonView missing"));
					return;
				}
				component.RPC("UpdateHealthRPC", (RpcTarget)0, new object[4] { num, maxHealth, true, false });
			}
			else
			{
				playerHealth.UpdateHealthRPC(num, maxHealth, true, false, default(PhotonMessageInfo));
			}
			Plugin.Log.LogInfo((object)$"[BRE] revive health synchronized: player={playerId}, health={num}/{maxHealth}");
		}

		private static int GetMaxHealth(PlayerHealth health)
		{
			if (!Object.op_Implicit((Object)(object)health) || !(MaxHealthField != null))
			{
				return 100;
			}
			return (int)MaxHealthField.GetValue(health);
		}

		public static bool AllowForcedUnequip(ItemEquippable item, int physGrabberPhotonViewId, bool isForceUnequip)
		{
			if (!isForceUnequip || !Plugin.KeepItemsOnDeath.Value || !IsHost())
			{
				return true;
			}
			PhysGrabber val = ResolveGrabber(physGrabberPhotonViewId);
			PlayerAvatar val2 = (Object.op_Implicit((Object)(object)val) ? val.playerAvatar : null);
			if (!Object.op_Implicit((Object)(object)val2) || !IsDead(val2))
			{
				return true;
			}
			int num = ((Object.op_Implicit((Object)(object)item) && InventorySpotIndexField != null) ? ((int)InventorySpotIndexField.GetValue(item)) : (-1));
			if (!Object.op_Implicit((Object)(object)item) || num < 0)
			{
				Plugin.Log.LogWarning((object)$"[BRE] could not preserve item: player={GetPlayerId(val2)}, slot={num}");
				return true;
			}
			RestoreEquippedState(item, num, physGrabberPhotonViewId);
			Plugin.Debug($"[BRE] inventory item preserved: player={GetPlayerId(val2)}, slot={num}");
			return false;
		}

		private static PhysGrabber ResolveGrabber(int photonViewId)
		{
			if (!SemiFunc.IsMultiplayer())
			{
				return PhysGrabber.instance;
			}
			PhotonView val = PhotonView.Find(photonViewId);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return null;
			}
			return ((Component)val).GetComponent<PhysGrabber>();
		}

		internal static void RestoreEquippedState(ItemEquippable item, int spot, int ownerId)
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			if (SemiFunc.IsMultiplayer())
			{
				((MonoBehaviourPun)item).photonView.RPC("RPC_UpdateItemState", (RpcTarget)0, new object[3] { 3, spot, ownerId });
				((MonoBehaviourPun)item).photonView.RPC("RPC_UpdateItemState", (RpcTarget)0, new object[3] { 2, spot, ownerId });
			}
			else
			{
				UpdateItemStateMethod.Invoke(item, new object[4]
				{
					3,
					spot,
					ownerId,
					(object)default(PhotonMessageInfo)
				});
				UpdateItemStateMethod.Invoke(item, new object[4]
				{
					2,
					spot,
					ownerId,
					(object)default(PhotonMessageInfo)
				});
			}
		}

		internal static int GetInventorySpotIndex(ItemEquippable item)
		{
			if (!Object.op_Implicit((Object)(object)item) || !(InventorySpotIndexField != null))
			{
				return -1;
			}
			return (int)InventorySpotIndexField.GetValue(item);
		}

		private static void Refund(int cost)
		{
			if (IsHost() && cost > 0 && !((Object)(object)StatsManager.instance == (Object)null) && !((Object)(object)PunManager.instance == (Object)null))
			{
				SemiFunc.StatSetRunCurrency(SemiFunc.StatGetRunCurrency() + cost);
			}
		}

		public static void Reset(bool refundPending = false)
		{
			if (refundPending)
			{
				foreach (PendingRevive value in PendingRevives.Values)
				{
					Refund(value.Cost);
				}
			}
			PendingRevives.Clear();
			PendingHealthSyncs.Clear();
			LastHeadStates.Clear();
			InsufficientFundsLogged.Clear();
			ObservedPlayerUpdates.Clear();
			ObservedDeathHeadUpdates.Clear();
		}
	}
	public enum ReviveMode
	{
		Disabled,
		ExtractionMachineActivated,
		ExtractionOrTruck
	}
	internal static class WeaponProtectionController
	{
		private sealed class HeldWeaponRecord
		{
			public PlayerAvatar Player;

			public PhysGrabObject Physical;

			public ItemEquippable Item;

			public int OwnerViewId;

			public int PreferredSlot;

			public float LastSeenAt;
		}

		private sealed class PendingWeaponReturn
		{
			public HeldWeaponRecord Weapon;

			public float ReadyAt;

			public float GiveUpAt;
		}

		private sealed class PendingForcedDropRecovery
		{
			public HeldWeaponRecord Item;

			public float ReadyAt;

			public float GiveUpAt;
		}

		private sealed class PendingInventorySwap
		{
			public HeldWeaponRecord HeldItem;

			public ItemEquippable OutgoingItem;

			public int TargetSlot;

			public float ReadyAt;

			public float GiveUpAt;
		}

		private const float RecentHoldWindowSeconds = 0.75f;

		private const float DeathCleanupDelaySeconds = 0.6f;

		private const float ReturnTimeoutSeconds = 4f;

		private const float ForcedDropRecoveryDelaySeconds = 0.05f;

		private const float ForcedDropRecoveryTimeoutSeconds = 2f;

		private const float InventorySwapDelaySeconds = 0.25f;

		private const float InventorySwapTimeoutSeconds = 2f;

		private const float ImpactReleaseMinimumDisableSeconds = 0.95f;

		private const float ImpactReleaseMaximumDisableSeconds = 2.05f;

		private static readonly FieldInfo GrabbedPhysObjectField = AccessTools.Field(typeof(PhysGrabber), "grabbedPhysGrabObject");

		private static readonly FieldInfo ForceGrabTimerField = AccessTools.Field(typeof(ItemEquippable), "forceGrabTimer");

		private static readonly FieldInfo ItemUnequipAutoHoldField = AccessTools.Field(typeof(GameplayManager), "itemUnequipAutoHold");

		private static readonly Dictionary<string, HeldWeaponRecord> LastWeaponByPlayer = new Dictionary<string, HeldWeaponRecord>();

		private static readonly Dictionary<int, string> LastOwnerByWeapon = new Dictionary<int, string>();

		private static readonly Dictionary<string, HeldWeaponRecord> DeathCandidates = new Dictionary<string, HeldWeaponRecord>();

		private static readonly Dictionary<string, PendingWeaponReturn> PendingReturns = new Dictionary<string, PendingWeaponReturn>();

		private static readonly Dictionary<string, PendingForcedDropRecovery> PendingForcedDropRecoveries = new Dictionary<string, PendingForcedDropRecovery>();

		private static readonly Dictionary<string, PendingInventorySwap> PendingInventorySwaps = new Dictionary<string, PendingInventorySwap>();

		private static readonly Dictionary<int, float> LastProtectionLogAt = new Dictionary<int, float>();

		private static bool AutoHoldWarningLogged;

		public static bool ValidateGameApi(ICollection<string> missing)
		{
			if (GrabbedPhysObjectField == null)
			{
				missing.Add("PhysGrabber.grabbedPhysGrabObject");
			}
			if (ForceGrabTimerField == null)
			{
				missing.Add("ItemEquippable.forceGrabTimer");
			}
			if (GrabbedPhysObjectField != null)
			{
				return ForceGrabTimerField != null;
			}
			return false;
		}

		public static void ObservePlayer(PlayerAvatar player)
		{
			if (ReviveController.IsHost() && Object.op_Implicit((Object)(object)player) && !ReviveController.IsDead(player))
			{
				WarnIfNativeAutoHoldDisabled(player);
				PhysGrabObject heldPhysical = GetHeldPhysical(player.physGrabber);
				if (IsStorableItem(heldPhysical, out var item) && IsLatestActiveHolder(player.physGrabber, heldPhysical))
				{
					RecordHolder(player, heldPhysical, item);
				}
			}
		}

		public static void CaptureBeforeDeath(PlayerAvatar player)
		{
			if (Plugin.ReturnHeldItemOnDeath.Value && ReviveController.IsHost() && Object.op_Implicit((Object)(object)player))
			{
				string playerId = ReviveController.GetPlayerId(player);
				PhysGrabObject heldPhysical = GetHeldPhysical(player.physGrabber);
				HeldWeaponRecord heldWeaponRecord = null;
				HeldWeaponRecord value;
				if (IsStorableItem(heldPhysical, out var item) && IsLatestActiveHolder(player.physGrabber, heldPhysical))
				{
					heldWeaponRecord = RecordHolder(player, heldPhysical, item);
				}
				else if (LastWeaponByPlayer.TryGetValue(playerId, out value) && Time.time - value.LastSeenAt <= 0.75f && IsStillLastOwner(playerId, value.Physical))
				{
					heldWeaponRecord = value;
				}
				if (heldWeaponRecord != null)
				{
					DeathCandidates[playerId] = heldWeaponRecord;
					Plugin.Debug("[BRE] death item captured: player=" + playerId + ", item=" + ItemName(heldWeaponRecord.Item));
				}
			}
		}

		public static void ConfirmDeath(PlayerAvatar player)
		{
			if (Plugin.ReturnHeldItemOnDeath.Value && ReviveController.IsHost() && Object.op_Implicit((Object)(object)player) && ReviveController.IsDead(player))
			{
				string playerId = ReviveController.GetPlayerId(player);
				PendingForcedDropRecoveries.Remove(playerId);
				PendingInventorySwaps.Remove(playerId);
				if (DeathCandidates.TryGetValue(playerId, out var value))
				{
					DeathCandidates.Remove(playerId);
					PendingReturns[playerId] = new PendingWeaponReturn
					{
						Weapon = value,
						ReadyAt = Time.time + 0.6f,
						GiveUpAt = Time.time + 4f
					};
					Plugin.Log.LogInfo((object)("[BRE] held item queued for death return: player=" + playerId + ", item=" + ItemName(value.Item)));
				}
			}
		}

		public static void ProcessPendingReturn(PlayerAvatar player, PlayerDeathHead deathHead = null)
		{
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			if (!ReviveController.IsHost() || !Object.op_Implicit((Object)(object)player))
			{
				return;
			}
			string playerId = ReviveController.GetPlayerId(player);
			if (!PendingReturns.TryGetValue(playerId, out var value) || Time.time < value.ReadyAt)
			{
				return;
			}
			HeldWeaponRecord weapon = value.Weapon;
			if (weapon == null || !Object.op_Implicit((Object)(object)weapon.Item) || !Object.op_Implicit((Object)(object)weapon.Physical))
			{
				PendingReturns.Remove(playerId);
				Plugin.Log.LogWarning((object)("[BRE] held item return cancelled: player=" + playerId + ", item no longer exists"));
			}
			else if (!IsStillLastOwner(playerId, weapon.Physical) || IsHeldByAnotherPlayer(playerId, weapon.Physical))
			{
				PendingReturns.Remove(playerId);
				Plugin.Log.LogInfo((object)("[BRE] held item return cancelled: player=" + playerId + ", item=" + ItemName(weapon.Item) + ", reason=new-holder"));
			}
			else if (weapon.Item.IsEquipped())
			{
				PendingReturns.Remove(playerId);
				Plugin.Debug("[BRE] held item already equipped: player=" + playerId + ", item=" + ItemName(weapon.Item));
			}
			else
			{
				if (weapon.Physical.playerGrabbing.Count > 0 && Time.time < value.GiveUpAt)
				{
					return;
				}
				int num = FindFreeVanillaSlot(player, playerId, weapon.PreferredSlot);
				if (num >= 0)
				{
					ReviveController.RestoreEquippedState(weapon.Item, num, weapon.OwnerViewId);
					PendingReturns.Remove(playerId);
					Plugin.Log.LogInfo((object)("[BRE] held item returned to inventory: player=" + playerId + ", " + $"item={ItemName(weapon.Item)}, slot={num}"));
				}
				else if (!((Object)(object)StatsManager.instance == (Object)null) || !(Time.time < value.GiveUpAt))
				{
					Vector3 fallbackPosition = GetFallbackPosition(player, deathHead);
					weapon.Physical.Teleport(fallbackPosition, ((Component)weapon.Physical).transform.rotation);
					if (Object.op_Implicit((Object)(object)weapon.Physical.rb))
					{
						weapon.Physical.rb.velocity = Vector3.zero;
						weapon.Physical.rb.angularVelocity = Vector3.zero;
					}
					PendingReturns.Remove(playerId);
					Plugin.Log.LogInfo((object)("[BRE] held item returned nearby: player=" + playerId + ", item=" + ItemName(weapon.Item) + ", reason=no-free-vanilla-slot"));
				}
			}
		}

		public static bool AllowForcedRelease(PhysGrabber grabber, bool physGrabEnded, float disableTimer, int releaseObjectViewId, PhotonMessageInfo info)
		{
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.ProtectHeldItems.Value || !ReviveController.IsHost() || !Object.op_Implicit((Object)(object)grabber))
			{
				return true;
			}
			if (!IsImpactOrTumbleRelease(disableTimer, releaseObjectViewId))
			{
				return true;
			}
			PlayerAvatar playerAvatar = grabber.playerAvatar;
			if (!Object.op_Implicit((Object)(object)playerAvatar) || ReviveController.IsDead(playerAvatar))
			{
				return true;
			}
			PhysGrabObject heldPhysical = GetHeldPhysical(grabber);
			if (!IsStorableItem(heldPhysical, out var item))
			{
				return true;
			}
			if (!IsLatestActiveHolder(grabber, heldPhysical))
			{
				return true;
			}
			HeldWeaponRecord heldWeaponRecord = RecordHolder(playerAvatar, heldPhysical, item);
			string playerId = ReviveController.GetPlayerId(playerAvatar);
			bool flag = IsLocalOwner(playerAvatar);
			int num = FindFreeVanillaSlot(playerAvatar, playerId, heldWeaponRecord.PreferredSlot);
			if (flag && num < 0)
			{
				int weaponKey = GetWeaponKey(heldPhysical);
				if (!LastProtectionLogAt.TryGetValue(weaponKey, out var value) || Time.time - value >= 1f)
				{
					LastProtectionLogAt[weaponKey] = Time.time;
					Plugin.Log.LogInfo((object)("[BRE] kept forced-drop item in hand: player=" + playerId + ", item=" + ItemName(item) + ", reason=no-free-vanilla-slot"));
				}
				return false;
			}
			if (flag && ForceGrabTimerField != null)
			{
				ForceGrabTimerField.SetValue(item, 0f);
			}
			PendingForcedDropRecoveries[playerId] = new PendingForcedDropRecovery
			{
				Item = heldWeaponRecord,
				ReadyAt = Time.time + 0.05f,
				GiveUpAt = Time.time + 2f
			};
			Plugin.Log.LogInfo((object)("[BRE] forced item release queued for inventory: player=" + playerId + ", " + $"item={ItemName(item)}, preferredSlot={heldWeaponRecord.PreferredSlot}, " + $"disableTimer={disableTimer:0.##}, physGrabEnded={physGrabEnded}, " + $"sender={GetSenderActorNumber(info)}, singleplayer={!SemiFunc.IsMultiplayer()}"));
			return true;
		}

		public static void ProcessForcedDropRecovery(PlayerAvatar player)
		{
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			if (!ReviveController.IsHost() || !Object.op_Implicit((Object)(object)player) || ReviveController.IsDead(player))
			{
				return;
			}
			string playerId = ReviveController.GetPlayerId(player);
			if (!PendingForcedDropRecoveries.TryGetValue(playerId, out var value) || Time.time < value.ReadyAt)
			{
				return;
			}
			HeldWeaponRecord item = value.Item;
			if (item == null || !Object.op_Implicit((Object)(object)item.Item) || !Object.op_Implicit((Object)(object)item.Physical))
			{
				PendingForcedDropRecoveries.Remove(playerId);
				return;
			}
			PhysGrabObject heldPhysical = GetHeldPhysical(player.physGrabber);
			if (item.Item.IsEquipped())
			{
				PendingForcedDropRecoveries.Remove(playerId);
			}
			else if ((Object)(object)heldPhysical == (Object)(object)item.Physical)
			{
				if (!(Time.time < value.GiveUpAt))
				{
					PendingForcedDropRecoveries.Remove(playerId);
					Plugin.Debug("[BRE] forced-drop recovery ended with item still held: player=" + playerId + ", item=" + ItemName(item.Item));
				}
			}
			else if (!IsStillLastOwner(playerId, item.Physical) || IsHeldByAnotherPlayer(playerId, item.Physical))
			{
				PendingForcedDropRecoveries.Remove(playerId);
			}
			else
			{
				if (item.Physical.playerGrabbing.Count > 0 && Time.time < value.GiveUpAt)
				{
					return;
				}
				int num = FindFreeVanillaSlot(player, playerId, item.PreferredSlot);
				if (num >= 0)
				{
					ReviveController.RestoreEquippedState(item.Item, num, item.OwnerViewId);
					PendingForcedDropRecoveries.Remove(playerId);
					Plugin.Log.LogInfo((object)("[BRE] forced-drop item recovered to inventory: player=" + playerId + ", " + $"item={ItemName(item.Item)}, slot={num}"));
				}
				else if (!((Object)(object)StatsManager.instance == (Object)null) || !(Time.time < value.GiveUpAt))
				{
					Vector3 val = ((Component)player).transform.position + ((Component)player).transform.forward * 0.75f + Vector3.up * 0.5f;
					item.Physical.Teleport(val, ((Component)item.Physical).transform.rotation);
					if (Object.op_Implicit((Object)(object)item.Physical.rb))
					{
						item.Physical.rb.velocity = Vector3.zero;
						item.Physical.rb.angularVelocity = Vector3.zero;
					}
					PendingForcedDropRecoveries.Remove(playerId);
					Plugin.Log.LogInfo((object)("[BRE] forced-drop item recovered nearby: player=" + playerId + ", item=" + ItemName(item.Item) + ", reason=no-free-vanilla-slot"));
				}
			}
		}

		public static void CaptureInventorySwap(ItemEquippable outgoingItem, int physGrabberViewId, bool isForceUnequip)
		{
			if (!Plugin.SwapHeldItemOnOccupiedSlot.Value || isForceUnequip || !ReviveController.IsHost() || !Object.op_Implicit((Object)(object)outgoingItem))
			{
				return;
			}
			int inventorySpotIndex = ReviveController.GetInventorySpotIndex(outgoingItem);
			if (inventorySpotIndex < 0 || inventorySpotIndex > 2)
			{
				Plugin.Debug("[BRE] inventory swap skipped: outgoing=" + ItemName(outgoingItem) + ", " + $"reason=invalid-slot, slot={inventorySpotIndex}");
				return;
			}
			Plugin.Debug("[BRE] inventory swap candidate: outgoing=" + ItemName(outgoingItem) + ", " + $"slot={inventorySpotIndex}, grabberView={physGrabberViewId}");
			PhysGrabber val = ResolveGrabber(physGrabberViewId);
			PlayerAvatar val2 = (Object.op_Implicit((Object)(object)val) ? val.playerAvatar : null);
			if (!Object.op_Implicit((Object)(object)val2) || ReviveController.IsDead(val2))
			{
				Plugin.Debug("[BRE] inventory swap skipped: outgoing=" + ItemName(outgoingItem) + ", reason=player-missing-or-dead");
				return;
			}
			PhysGrabObject heldPhysical = GetHeldPhysical(val);
			if (!IsStorableItem(heldPhysical, out var item) || (Object)(object)item == (Object)(object)outgoingItem || !IsLatestActiveHolder(val, heldPhysical))
			{
				Plugin.Debug("[BRE] inventory swap skipped: player=" + ReviveController.GetPlayerId(val2) + ", outgoing=" + ItemName(outgoingItem) + ", reason=no-valid-held-item");
				return;
			}
			HeldWeaponRecord heldItem = RecordHolder(val2, heldPhysical, item);
			string playerId = ReviveController.GetPlayerId(val2);
			PendingForcedDropRecoveries.Remove(playerId);
			PendingInventorySwaps[playerId] = new PendingInventorySwap
			{
				HeldItem = heldItem,
				OutgoingItem = outgoingItem,
				TargetSlot = inventorySpotIndex,
				ReadyAt = Time.time + 0.25f,
				GiveUpAt = Time.time + 2f
			};
			Plugin.Log.LogInfo((object)("[BRE] inventory swap queued: player=" + playerId + ", held=" + ItemName(item) + ", outgoing=" + ItemName(outgoingItem) + ", " + $"slot={inventorySpotIndex}"));
		}

		public static void ProcessPendingInventorySwap(PlayerAvatar player)
		{
			if (!ReviveController.IsHost() || !Object.op_Implicit((Object)(object)player))
			{
				return;
			}
			string playerId = ReviveController.GetPlayerId(player);
			if (!PendingInventorySwaps.TryGetValue(playerId, out var value) || Time.time < value.ReadyAt)
			{
				return;
			}
			if (ReviveController.IsDead(player))
			{
				CancelInventorySwap(playerId, value, "player-dead");
				return;
			}
			HeldWeaponRecord heldItem = value.HeldItem;
			if (heldItem == null || !Object.op_Implicit((Object)(object)heldItem.Item) || !Object.op_Implicit((Object)(object)heldItem.Physical) || !Object.op_Implicit((Object)(object)value.OutgoingItem))
			{
				CancelInventorySwap(playerId, value, "item-missing");
			}
			else if (heldItem.Item.IsEquipped())
			{
				if (ReviveController.GetInventorySpotIndex(heldItem.Item) == value.TargetSlot)
				{
					PendingInventorySwaps.Remove(playerId);
					Plugin.Log.LogInfo((object)("[BRE] inventory swap already completed: player=" + playerId + ", " + $"item={ItemName(heldItem.Item)}, slot={value.TargetSlot}"));
				}
				else
				{
					CancelInventorySwap(playerId, value, "held-item-equipped-elsewhere");
				}
			}
			else if (!IsStillLastOwner(playerId, heldItem.Physical) || IsHeldByAnotherPlayer(playerId, heldItem.Physical))
			{
				CancelInventorySwap(playerId, value, "new-holder");
			}
			else if (value.OutgoingItem.IsEquipped())
			{
				if (!(Time.time < value.GiveUpAt))
				{
					CancelInventorySwap(playerId, value, "outgoing-item-still-equipped");
				}
			}
			else
			{
				if (heldItem.Physical.playerGrabbing.Count > 0 && Time.time < value.GiveUpAt)
				{
					return;
				}
				if (!IsVanillaSlotFree(player, playerId, value.TargetSlot))
				{
					if (!(Time.time < value.GiveUpAt))
					{
						CancelInventorySwap(playerId, value, "target-slot-occupied");
					}
					return;
				}
				ReviveController.RestoreEquippedState(heldItem.Item, value.TargetSlot, heldItem.OwnerViewId);
				PendingInventorySwaps.Remove(playerId);
				Plugin.Log.LogInfo((object)("[BRE] inventory swap completed: player=" + playerId + ", stored=" + ItemName(heldItem.Item) + ", held=" + ItemName(value.OutgoingItem) + ", " + $"slot={value.TargetSlot}"));
			}
		}

		private static HeldWeaponRecord RecordHolder(PlayerAvatar player, PhysGrabObject physical, ItemEquippable item)
		{
			string playerId = ReviveController.GetPlayerId(player);
			int weaponKey = GetWeaponKey(physical);
			if (LastOwnerByWeapon.TryGetValue(weaponKey, out var value) && value != playerId && LastWeaponByPlayer.TryGetValue(value, out var value2) && (Object)(object)value2.Physical == (Object)(object)physical)
			{
				LastWeaponByPlayer.Remove(value);
			}
			HeldWeaponRecord value3;
			bool num = !LastWeaponByPlayer.TryGetValue(playerId, out value3) || (Object)(object)value3.Physical != (Object)(object)physical;
			value3 = new HeldWeaponRecord
			{
				Player = player,
				Physical = physical,
				Item = item,
				OwnerViewId = ((SemiFunc.IsMultiplayer() && Object.op_Implicit((Object)(object)player.physGrabber) && Object.op_Implicit((Object)(object)player.physGrabber.photonView)) ? player.physGrabber.photonView.ViewID : (-1)),
				PreferredSlot = ReviveController.GetInventorySpotIndex(item),
				LastSeenAt = Time.time
			};
			LastOwnerByWeapon[weaponKey] = playerId;
			LastWeaponByPlayer[playerId] = value3;
			if (num)
			{
				Plugin.Debug("[BRE] storable item holder recorded: player=" + playerId + ", item=" + ItemName(item));
			}
			return value3;
		}

		private static PhysGrabObject GetHeldPhysical(PhysGrabber grabber)
		{
			if (!Object.op_Implicit((Object)(object)grabber) || !grabber.grabbed || GrabbedPhysObjectField == null)
			{
				return null;
			}
			object? value = GrabbedPhysObjectField.GetValue(grabber);
			PhysGrabObject val = (PhysGrabObject)((value is PhysGrabObject) ? value : null);
			if (!Object.op_Implicit((Object)(object)val) || !val.playerGrabbing.Contains(grabber))
			{
				return null;
			}
			return val;
		}

		private static bool IsLatestActiveHolder(PhysGrabber grabber, PhysGrabObject physical)
		{
			if (!Object.op_Implicit((Object)(object)grabber) || !Object.op_Implicit((Object)(object)physical))
			{
				return false;
			}
			for (int num = physical.playerGrabbing.Count - 1; num >= 0; num--)
			{
				PhysGrabber val = physical.playerGrabbing[num];
				if (Object.op_Implicit((Object)(object)val) && val.grabbed && !(GrabbedPhysObjectField == null) && (Object)/*isinst with value type is only supported in some contexts*/ == (Object)(object)physical)
				{
					return (Object)(object)val == (Object)(object)grabber;
				}
			}
			return false;
		}

		private static bool IsImpactOrTumbleRelease(float disableTimer, int releaseObjectViewId)
		{
			if (releaseObjectViewId == -1 && disableTimer >= 0.95f)
			{
				return disableTimer <= 2.05f;
			}
			return false;
		}

		private static int GetSenderActorNumber(PhotonMessageInfo info)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (info.Sender == null)
			{
				return -1;
			}
			return info.Sender.ActorNumber;
		}

		private static void WarnIfNativeAutoHoldDisabled(PlayerAvatar player)
		{
			if (!AutoHoldWarningLogged && IsLocalOwner(player) && !((Object)(object)GameplayManager.instance == (Object)null) && !(ItemUnequipAutoHoldField == null))
			{
				object value = ItemUnequipAutoHoldField.GetValue(GameplayManager.instance);
				if (value is bool && !(bool)value)
				{
					AutoHoldWarningLogged = true;
					Plugin.Log.LogWarning((object)"[BRE] The native ItemUnequipAutoHold setting is disabled. Items taken from inventory will be released after the game's temporary hold expires. Enable the game's auto-hold setting; ProtectHeldItems only handles impact and tumble releases.");
				}
			}
		}

		private static bool IsStorableItem(PhysGrabObject physical, out ItemEquippable item)
		{
			item = null;
			if (!Object.op_Implicit((Object)(object)physical) || physical.dead)
			{
				return false;
			}
			item = ((Component)physical).GetComponent<ItemEquippable>();
			return Object.op_Implicit((Object)(object)item);
		}

		private static PhysGrabber ResolveGrabber(int photonViewId)
		{
			if (!SemiFunc.IsMultiplayer())
			{
				return PhysGrabber.instance;
			}
			PhotonView val = PhotonView.Find(photonViewId);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return null;
			}
			return ((Component)val).GetComponent<PhysGrabber>();
		}

		private static bool IsStillLastOwner(string playerId, PhysGrabObject physical)
		{
			if (Object.op_Implicit((Object)(object)physical) && LastOwnerByWeapon.TryGetValue(GetWeaponKey(physical), out var value))
			{
				return value == playerId;
			}
			return false;
		}

		private static bool IsHeldByAnotherPlayer(string playerId, PhysGrabObject physical)
		{
			foreach (PhysGrabber item in physical.playerGrabbing)
			{
				if (Object.op_Implicit((Object)(object)item) && Object.op_Implicit((Object)(object)item.playerAvatar) && ReviveController.GetPlayerId(item.playerAvatar) != playerId)
				{
					return true;
				}
			}
			return false;
		}

		private static int FindFreeVanillaSlot(PlayerAvatar player, string playerId, int preferredSlot = -1)
		{
			StatsManager instance = StatsManager.instance;
			bool flag = IsLocalOwner(player) && (Object)(object)Inventory.instance != (Object)null;
			if (!flag && ((Object)(object)instance == (Object)null || string.IsNullOrEmpty(playerId)))
			{
				return -1;
			}
			if (preferredSlot >= 0 && preferredSlot <= 2 && !IsVanillaSlotTaken(instance, playerId, preferredSlot, flag))
			{
				return preferredSlot;
			}
			for (int i = 0; i <= 2; i++)
			{
				if (!IsVanillaSlotTaken(instance, playerId, i, flag))
				{
					return i;
				}
			}
			return -1;
		}

		private static bool IsVanillaSlotFree(PlayerAvatar player, string playerId, int slot)
		{
			if (slot < 0 || slot > 2)
			{
				return false;
			}
			StatsManager instance = StatsManager.instance;
			bool flag = IsLocalOwner(player) && (Object)(object)Inventory.instance != (Object)null;
			if (!flag && ((Object)(object)instance == (Object)null || string.IsNullOrEmpty(playerId)))
			{
				return false;
			}
			return !IsVanillaSlotTaken(instance, playerId, slot, flag);
		}

		private static bool IsVanillaSlotTaken(StatsManager stats, string playerId, int slot, bool useLocalInventory)
		{
			if (useLocalInventory)
			{
				InventorySpot spotByIndex = Inventory.instance.GetSpotByIndex(slot);
				if ((Object)(object)spotByIndex != (Object)null)
				{
					return spotByIndex.IsOccupied();
				}
				return false;
			}
			return slot switch
			{
				0 => stats.playerInventorySpot1.ContainsKey(playerId), 
				1 => stats.playerInventorySpot2.ContainsKey(playerId), 
				_ => stats.playerInventorySpot3.ContainsKey(playerId), 
			};
		}

		private static bool IsLocalOwner(PlayerAvatar player)
		{
			if (Object.op_Implicit((Object)(object)player))
			{
				if (SemiFunc.IsMultiplayer())
				{
					if (Object.op_Implicit((Object)(object)player.photonView))
					{
						return player.photonView.IsMine;
					}
					return false;
				}
				return true;
			}
			return false;
		}

		private static Vector3 GetFallbackPosition(PlayerAvatar player, PlayerDeathHead deathHead)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			PhysGrabObject val = (Object.op_Implicit((Object)(object)deathHead) ? ((Component)deathHead).GetComponent<PhysGrabObject>() : null);
			if (Object.op_Implicit((Object)(object)val))
			{
				return val.centerPoint + Vector3.up * 0.5f;
			}
			if (Object.op_Implicit((Object)(object)deathHead))
			{
				return ((Component)deathHead).transform.position + Vector3.up * 0.5f;
			}
			return ((Component)player).transform.position + Vector3.up * 0.75f;
		}

		private static int GetWeaponKey(PhysGrabObject physical)
		{
			PhotonView component = ((Component)physical).GetComponent<PhotonView>();
			if (!Object.op_Implicit((Object)(object)component) || component.ViewID == 0)
			{
				return ((Object)physical).GetInstanceID();
			}
			return component.ViewID;
		}

		private static string ItemName(ItemEquippable item)
		{
			if (!Object.op_Implicit((Object)(object)item))
			{
				return "unknown";
			}
			ItemAttributes component = ((Component)item).GetComponent<ItemAttributes>();
			if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)component.item))
			{
				return ((Object)((Component)item).gameObject).name;
			}
			return component.item.itemName;
		}

		private static void CancelInventorySwap(string playerId, PendingInventorySwap pending, string reason)
		{
			PendingInventorySwaps.Remove(playerId);
			Plugin.Log.LogInfo((object)("[BRE] inventory swap cancelled: player=" + playerId + ", item=" + ItemName(pending?.HeldItem?.Item) + ", reason=" + reason));
		}

		public static void Reset()
		{
			LastWeaponByPlayer.Clear();
			LastOwnerByWeapon.Clear();
			DeathCandidates.Clear();
			PendingReturns.Clear();
			PendingForcedDropRecoveries.Clear();
			PendingInventorySwaps.Clear();
			LastProtectionLogAt.Clear();
		}
	}
}
namespace BetterReviveExperience.Patches
{
	internal static class PlayerAvatarUpdatePatch
	{
		private static void Postfix(PlayerAvatar __instance)
		{
			WeaponProtectionController.ObservePlayer(__instance);
			WeaponProtectionController.ProcessPendingInventorySwap(__instance);
			WeaponProtectionController.ProcessForcedDropRecovery(__instance);
			ReviveController.OnPlayerAvatarUpdated(__instance);
			WeaponProtectionController.ProcessPendingReturn(__instance);
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")]
	internal static class PlayerDeathPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix(PlayerAvatar __instance)
		{
			WeaponProtectionController.CaptureBeforeDeath(__instance);
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(PlayerAvatar __instance)
		{
			ReviveController.OnPlayerDeath(__instance);
			WeaponProtectionController.ConfirmDeath(__instance);
		}
	}
	[HarmonyPatch(typeof(PlayerDeathHead), "Trigger")]
	internal static class DeathHeadTriggerPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerDeathHead __instance)
		{
			ReviveController.OnDeathHeadTriggered(__instance);
		}
	}
	[HarmonyPatch(typeof(PlayerDeathHead), "Update")]
	internal static class DeathHeadUpdatePatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerDeathHead __instance)
		{
			WeaponProtectionController.ProcessPendingReturn(__instance.playerAvatar, __instance);
			ReviveController.OnDeathHeadUpdated(__instance);
		}
	}
	internal static class ForcedGrabReleaseReceivePatch
	{
		private static bool Prefix(PhysGrabber __instance, bool physGrabEnded, float _disableTimer, int _releaseObjectViewID, PhotonMessageInfo _info)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			return WeaponProtectionController.AllowForcedRelease(__instance, physGrabEnded, _disableTimer, _releaseObjectViewID, _info);
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "ReviveRPC")]
	internal static class PlayerRevivePatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(PlayerAvatar __instance)
		{
			ReviveController.OnPlayerRevived(__instance);
		}
	}
	[HarmonyPatch(typeof(PhysGrabCart), "Update")]
	internal static class CartUpdatePatch
	{
		[HarmonyPostfix]
		private static void Postfix(PhysGrabCart __instance)
		{
			ReviveController.OnCartUpdated(__instance);
		}
	}
	[HarmonyPatch(typeof(ItemEquippable), "RPC_CompleteUnequip")]
	internal static class ForcedUnequipPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool Prefix(ItemEquippable __instance, int physGrabberPhotonViewID, bool isForceUnequip)
		{
			WeaponProtectionController.CaptureInventorySwap(__instance, physGrabberPhotonViewID, isForceUnequip);
			return ReviveController.AllowForcedUnequip(__instance, physGrabberPhotonViewID, isForceUnequip);
		}
	}
	[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
	internal static class LevelChangePatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			ReviveController.Reset(refundPending: true);
			WeaponProtectionController.Reset();
			Plugin.Debug("[BRE] level change: state reset");
		}
	}
	[HarmonyPatch(typeof(RoundDirector), "Start")]
	internal static class RoundStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			ReviveController.Reset();
			WeaponProtectionController.Reset();
			Plugin.Debug("[BRE] round start: state reset");
		}
	}
	[HarmonyPatch(typeof(MainMenuOpen), "Start")]
	internal static class MainMenuPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			ReviveController.Reset(refundPending: true);
			WeaponProtectionController.Reset();
			Plugin.Debug("[BRE] main menu: state reset");
		}
	}
}