Decompiled source of LetMeSoloThem v0.3.1

LetMeSoloThem.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LetMeSoloThem.Hud;
using LetMeSoloThem.Patches;
using LetMeSoloThem.State;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;
using UnityEngine.SceneManagement;

[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("LetMeSoloThem")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.1.0")]
[assembly: AssemblyInformationalVersion("0.3.1+7226dbe02fc0c470a5df0e76adca31ad83d84722")]
[assembly: AssemblyProduct("LetMeSoloThem")]
[assembly: AssemblyTitle("LetMeSoloThem")]
[assembly: AssemblyVersion("0.3.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 LetMeSoloThem
{
	[BepInPlugin("com.pogwas.letmesolothem", "Let me Solo Them", "0.3.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.pogwas.letmesolothem";

		public const string PluginName = "Let me Solo Them";

		public const string PluginVersion = "0.3.1";

		internal static Plugin Instance;

		internal static ManualLogSource Log;

		internal static ConfigEntry<float> SoloGraceFloor;

		internal static ConfigEntry<bool> SoloGraceOverrideMode;

		internal static ConfigEntry<bool> HudEnabled;

		internal static ConfigEntry<int> HudFontSize;

		internal static ConfigEntry<bool> ReviveEnabled;

		internal static ConfigEntry<int> ReviveHpPercent;

		internal static ConfigEntry<string> ReviveRespawnLocation;

		internal static ConfigEntry<bool> ReviveWorksInMultiplayer;

		internal static ConfigEntry<bool> ReviveFreeChassisOnLevelStart;

		internal static ConfigEntry<int> ReviveStartingLives;

		internal static ConfigEntry<int> ReviveLivesPerRound;

		internal static ConfigEntry<float> ReviveStuckAtZeroSeconds;

		internal static ConfigEntry<bool> SoloSwordEnabled;

		internal static ConfigEntry<int> SoloSwordDamagePercent;

		internal static ConfigEntry<string> SoloItemSpawnLocation;

		internal static ConfigEntry<bool> SoloTranqEnabled;

		internal static ConfigEntry<float> SoloTranqStunSeconds;

		internal static ConfigEntry<float> SoloTranqShootCooldownSeconds;

		internal static ConfigEntry<bool> SoloDamageEnabled;

		internal static ConfigEntry<float> SoloDamageSoloMult;

		internal static ConfigEntry<float> SoloDamageDuoMult;

		internal static ConfigEntry<float> SoloDamageTrioMult;

		internal static ConfigEntry<float> SoloDamageQuadMult;

		private Harmony _harmony;

		private static GameObject _hudGO;

		private void Awake()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Expected O, but got Unknown
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Expected O, but got Unknown
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Expected O, but got Unknown
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Expected O, but got Unknown
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Expected O, but got Unknown
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a3: Expected O, but got Unknown
			//IL_02e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ec: Expected O, but got Unknown
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0349: Expected O, but got Unknown
			//IL_037c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0386: Expected O, but got Unknown
			//IL_03d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e3: Expected O, but got Unknown
			//IL_0416: Unknown result type (might be due to invalid IL or missing references)
			//IL_0420: Expected O, but got Unknown
			//IL_0453: Unknown result type (might be due to invalid IL or missing references)
			//IL_045d: Expected O, but got Unknown
			//IL_0490: Unknown result type (might be due to invalid IL or missing references)
			//IL_049a: Expected O, but got Unknown
			//IL_04a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04af: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"Let me Solo Them v0.3.1 is loading...");
			SoloGraceFloor = ((BaseUnityPlugin)this).Config.Bind<float>("Spawn Grace", "FloorSeconds", 105f, new ConfigDescription("Solo grace-timer length in seconds. With OverrideMode=true (default): every solo level starts with EXACTLY this many seconds before enemies spawn. Set to 0 to remove the spawn-grace timer entirely (great for testing monsters). With OverrideMode=false: this value is used as a minimum floor only — vanilla wins if it rolls higher. No effect in multiplayer. Changes apply at the start of the next level.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f), Array.Empty<object>()));
			SoloGraceOverrideMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawn Grace", "OverrideMode", true, "When true (default): FloorSeconds is the EXACT grace-timer length applied to every solo level — set FloorSeconds=0 for instant monster spawn (testing), or 180 for a guaranteed 3-minute grace, etc. When false: FloorSeconds is just a minimum — vanilla rolls (12-180s) win if higher.");
			HudEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "Enabled", true, "Show the on-screen Solo Grace timer during solo runs.");
			HudFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("HUD", "FontSize", 20, new ConfigDescription("Pixel font size of the HUD text.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 60), Array.Empty<object>()));
			ReviveEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Self-Revive", "Enabled", true, "Master toggle for the Spare Chassis self-revive system. When false, the free-on-level-start grant is skipped and the death-intercept patch no-ops.");
			ReviveHpPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Self-Revive", "HpPercent", 50, new ConfigDescription("Percent of max HP the player has after self-revive.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			ReviveRespawnLocation = ((BaseUnityPlugin)this).Config.Bind<string>("Self-Revive", "RespawnLocation", "ExtractionPoint", new ConfigDescription("Where the player respawns after self-revive.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "ExtractionPoint", "Truck", "DeathLocation" }), Array.Empty<object>()));
			ReviveWorksInMultiplayer = ((BaseUnityPlugin)this).Config.Bind<bool>("Self-Revive", "WorksInMultiplayer", true, "When true (default) self-revive also triggers in MP if every other player is dead. When false, it only triggers in solo (Photon room player count <= 1).");
			ReviveFreeChassisOnLevelStart = ((BaseUnityPlugin)this).Config.Bind<bool>("Self-Revive", "FreeChassisOnLevelStart", true, "Master on/off for the free Spare Chassis grants (both StartingLives and LivesPerRound). When true (default) you get StartingLives chassis at the start of a run and LivesPerRound more at the start of each subsequent level. When false, no chassis are ever granted — disable replenishment without flipping the master Enabled toggle.");
			ReviveStartingLives = ((BaseUnityPlugin)this).Config.Bind<int>("Self-Revive", "StartingLives", 1, new ConfigDescription("How many Spare Chassis (extra lives) you start each new run with. Applied once, on the first level of a fresh expedition — it sets your chassis count to exactly this. 0 = start with none. 1 (default) = start with one. Range 0–99.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 99), Array.Empty<object>()));
			ReviveLivesPerRound = ((BaseUnityPlugin)this).Config.Bind<int>("Self-Revive", "LivesPerRound", 1, new ConfigDescription("Extra Spare Chassis granted at the start of each level AFTER the first — added on top of however many you've still got (carry-over). 1 (default) = +1 each level. 0 = no per-round replenishment (you just keep whatever's left of your StartingLives). Range 0–99.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 99), Array.Empty<object>()));
			ReviveStuckAtZeroSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Self-Revive", "StuckAtZeroSeconds", 0.5f, new ConfigDescription("Backup trigger: if your HP is at 0 but vanilla PlayerDeath never fires (e.g. self-destruct paths that bypass PlayerHealth.Hurt), force the death pipeline after this many seconds so the chassis can revive you. 0.5 (default) gives vanilla a half-second to fire normally before we step in. Set to 0 to disable the backup entirely (you'll stay stuck at 0 HP in those edge cases).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			SoloSwordEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Solo Sword", "Enabled", true, "When true (default), the local player is granted ONE sword with unlimited durability. It re-spawns at the start of each new level if the one you were carrying got destroyed in the level transition; if you still have it, no new one spawns. Only that specific sword instance has its durability sustained — other swords break normally. Toggle off to disable the grant entirely.");
			SoloSwordDamagePercent = ((BaseUnityPlugin)this).Config.Bind<int>("Solo Sword", "DamagePercent", 50, new ConfigDescription("Percent of the granted sword's original damage values (both playerDamage and enemyDamage on its HurtCollider). 50 (default) halves it as a balance for the unlimited durability. 100 = no reduction. Only affects the granted sword instance — other swords keep their full damage.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			SoloItemSpawnLocation = ((BaseUnityPlugin)this).Config.Bind<string>("Solo Sword", "SpawnLocation", "Player", new ConfigDescription("Where the granted Solo Sword and Solo Tranq spawn at level start. 'Player' (default) = right in front of you, where you can actually pick them up. 'ExtractionPoint' = at the level's extraction point (the old behavior — can be far from where you start the level, so you may never find them).", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "Player", "ExtractionPoint" }), Array.Empty<object>()));
			SoloTranqEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Solo Tranq", "Enabled", true, "When true (default), the local player is granted ONE Tranq Gun, spawned alongside the Solo Sword (same re-grant-per-level logic, same SpawnLocation). Stuns enemies on hit; pairs well with the sword for handling Critical-tier enemies (Robe, Clown, Huntsman) that one-shot you in melee. Toggle off to skip the grant.");
			SoloTranqStunSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Solo Tranq", "StunSeconds", 3f, new ConfigDescription("Stun duration applied by darts fired from the granted Tranq Gun. Vanilla = 18s. Default 3 = brief disable, encourages chained shots from the unlimited-ammo gun rather than one-shot lockdowns. Only affects darts from the granted gun — other tranqs in MP keep vanilla 18s.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 60f), Array.Empty<object>()));
			SoloTranqShootCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Solo Tranq", "ShootCooldownSeconds", 2f, new ConfigDescription("Cooldown between shots in seconds. Vanilla ItemGun.shootCooldown defaults to 1s (1 shot/sec). 2 (default) = 1 shot every 2s. Higher values slow fire rate further. Combined with unlimited ammo + short stun, this makes the tranq feel like a steady utility rather than a panic-spam tool.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
			SoloDamageEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Solo Damage", "Enabled", true, "Master toggle for player-incoming-damage scaling by player count. When false, vanilla damage applies to all damage paths (Hurt / HurtOther / HurtOtherRPC).");
			SoloDamageSoloMult = ((BaseUnityPlugin)this).Config.Bind<float>("Solo Damage", "SoloMultiplier", 0.5f, new ConfigDescription("Damage multiplier when 1 player is in the run (solo). 1.0 = vanilla, 0.5 (default) = take half damage, 0.0 = invuln. Applied as a Prefix on PlayerHealth.Hurt before health subtraction.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			SoloDamageDuoMult = ((BaseUnityPlugin)this).Config.Bind<float>("Solo Damage", "DuoMultiplier", 0.75f, new ConfigDescription("Damage multiplier when 2 players are in the run. 0.75 (default) = take 75% damage. 1.0 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			SoloDamageTrioMult = ((BaseUnityPlugin)this).Config.Bind<float>("Solo Damage", "TrioMultiplier", 0.9f, new ConfigDescription("Damage multiplier when 3 players are in the run. 0.9 (default) = mild discount. 1.0 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			SoloDamageQuadMult = ((BaseUnityPlugin)this).Config.Bind<float>("Solo Damage", "QuadMultiplier", 1f, new ConfigDescription("Damage multiplier when 4 or more players are in the run. 1.0 (default) = vanilla. Set above 1.0 to make full lobbies harder.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			_harmony = new Harmony("com.pogwas.letmesolothem");
			_harmony.PatchAll();
			SceneManager.sceneLoaded += OnSceneLoaded;
			Log.LogInfo((object)"Let me Solo Them loaded successfully. Solo runs incoming.");
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			Log.LogDebug((object)$"[HUD] sceneLoaded: '{((Scene)(ref scene)).name}' (mode={mode}). HUD alive={(Object)(object)_hudGO != (Object)null}");
			if ((Object)(object)_hudGO == (Object)null)
			{
				_hudGO = new GameObject("LetMeSoloThem.SoloGraceHud", new Type[1] { typeof(SoloGraceHud) });
				Object.DontDestroyOnLoad((Object)(object)_hudGO);
				ManualLogSource log = Log;
				string name = ((Scene)(ref scene)).name;
				Scene scene2 = _hudGO.scene;
				log.LogDebug((object)$"[HUD] (re)created after scene '{name}': scene='{((Scene)(ref scene2)).name}', activeInHierarchy={_hudGO.activeInHierarchy}");
			}
		}
	}
}
namespace LetMeSoloThem.State
{
	internal static class SpareChassisInventory
	{
		private static readonly Dictionary<string, int> _countsBySteamID = new Dictionary<string, int>();

		public static int Count(string steamID)
		{
			if (string.IsNullOrEmpty(steamID))
			{
				return 0;
			}
			if (!_countsBySteamID.TryGetValue(steamID, out var value))
			{
				return 0;
			}
			return value;
		}

		public static bool Has(string steamID)
		{
			return Count(steamID) > 0;
		}

		public static void Set(string steamID, int count)
		{
			if (!string.IsNullOrEmpty(steamID))
			{
				if (count <= 0)
				{
					_countsBySteamID.Remove(steamID);
				}
				else
				{
					_countsBySteamID[steamID] = count;
				}
			}
		}

		public static bool Consume(string steamID)
		{
			if (string.IsNullOrEmpty(steamID))
			{
				return false;
			}
			if (!_countsBySteamID.TryGetValue(steamID, out var value) || value <= 0)
			{
				return false;
			}
			value--;
			if (value <= 0)
			{
				_countsBySteamID.Remove(steamID);
			}
			else
			{
				_countsBySteamID[steamID] = value;
			}
			Plugin.Log.LogDebug((object)$"[Revive] Spare Chassis consumed (steamID={steamID}, remaining={value})");
			return true;
		}
	}
}
namespace LetMeSoloThem.Patches
{
	[HarmonyPatch(typeof(EnemyDirector), "Start")]
	public static class EnemyDirectorStartPatch
	{
		internal static readonly FieldRef<EnemyDirector, float> SpawnIdlePauseTimerRef = AccessTools.FieldRefAccess<EnemyDirector, float>("spawnIdlePauseTimer");

		[HarmonyPostfix]
		public static void Postfix(EnemyDirector __instance)
		{
			int num = ((PhotonNetwork.CurrentRoom == null) ? 1 : PhotonNetwork.CurrentRoom.PlayerCount);
			bool num2 = num <= 1;
			float num3 = SpawnIdlePauseTimerRef.Invoke(__instance);
			float value = Plugin.SoloGraceFloor.Value;
			bool value2 = Plugin.SoloGraceOverrideMode.Value;
			if (!num2)
			{
				Plugin.Log.LogInfo((object)$"MP run (PhotonRoom.PlayerCount={num}): vanilla spawnIdlePauseTimer={num3:F1}s — not boosting");
				return;
			}
			if (value2)
			{
				SpawnIdlePauseTimerRef.Invoke(__instance) = value;
				Plugin.Log.LogInfo((object)$"Solo grace OVERRIDDEN: vanilla {num3:F1}s → forced {value:F1}s (OverrideMode=true)");
				return;
			}
			float num4 = Mathf.Max(num3, value);
			if (num4 == num3)
			{
				Plugin.Log.LogInfo((object)$"Solo run, vanilla already adequate: {num3:F1}s ≥ floor {value}s (PhotonRoom.PlayerCount={num})");
				return;
			}
			SpawnIdlePauseTimerRef.Invoke(__instance) = num4;
			Plugin.Log.LogInfo((object)$"Solo grace rescued from slasher: {num3:F1}s → {num4:F1}s (PhotonRoom.PlayerCount={num})");
		}
	}
	[HarmonyPatch(typeof(EnemyDirector), "Update")]
	public static class EnemyDirectorMenuPausePatch
	{
		private static float _preUpdateValue;

		private static bool _preUpdateCaptured;

		[HarmonyPrefix]
		public static void Prefix(EnemyDirector __instance)
		{
			if (!ShouldFreeze())
			{
				_preUpdateCaptured = false;
				return;
			}
			_preUpdateValue = EnemyDirectorStartPatch.SpawnIdlePauseTimerRef.Invoke(__instance);
			_preUpdateCaptured = true;
		}

		[HarmonyPostfix]
		public static void Postfix(EnemyDirector __instance)
		{
			if (_preUpdateCaptured)
			{
				EnemyDirectorStartPatch.SpawnIdlePauseTimerRef.Invoke(__instance) = _preUpdateValue;
				_preUpdateCaptured = false;
			}
		}

		private static bool ShouldFreeze()
		{
			if (PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.PlayerCount > 1)
			{
				return false;
			}
			if (!SemiFunc.RunIsLevel())
			{
				return false;
			}
			return (Object)(object)MenuPageEsc.instance != (Object)null;
		}
	}
	internal static class RepoRefs
	{
		internal static readonly FieldRef<PlayerAvatar, bool> AvatarIsLocal = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isLocal");

		internal static readonly FieldRef<PlayerAvatar, bool> AvatarDeadSet = AccessTools.FieldRefAccess<PlayerAvatar, bool>("deadSet");

		internal static readonly FieldRef<PlayerAvatar, bool> AvatarIsDisabled = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isDisabled");

		internal static readonly FieldRef<PlayerHealth, int> HealthValue = AccessTools.FieldRefAccess<PlayerHealth, int>("health");

		internal static readonly FieldRef<PlayerHealth, int> HealthMax = AccessTools.FieldRefAccess<PlayerHealth, int>("maxHealth");

		internal static readonly FieldRef<RoundDirector, bool> RoundExtractionActive = AccessTools.FieldRefAccess<RoundDirector, bool>("extractionPointActive");

		internal static readonly FieldRef<RoundDirector, ExtractionPoint> RoundExtractionCurrent = AccessTools.FieldRefAccess<RoundDirector, ExtractionPoint>("extractionPointCurrent");

		internal static readonly FieldRef<PlayerAvatar, PlayerDeathHead> AvatarDeathHead = AccessTools.FieldRefAccess<PlayerAvatar, PlayerDeathHead>("playerDeathHead");

		internal static readonly FieldRef<PlayerAvatar, PlayerTumble> AvatarTumble = AccessTools.FieldRefAccess<PlayerAvatar, PlayerTumble>("tumble");

		internal static readonly FieldRef<PlayerAvatar, PlayerVoiceChat> AvatarVoiceChat = AccessTools.FieldRefAccess<PlayerAvatar, PlayerVoiceChat>("voiceChat");

		internal static readonly FieldRef<PlayerDeathHead, PhysGrabObject> DeathHeadPhysGrab = AccessTools.FieldRefAccess<PlayerDeathHead, PhysGrabObject>("physGrabObject");

		internal static readonly FieldRef<EnemyParent, Enemy> ParentEnemy = AccessTools.FieldRefAccess<EnemyParent, Enemy>("Enemy");

		internal static readonly FieldRef<EnemySlowMouthAttaching, EnemySlowMouth> AttachingSlowMouth = AccessTools.FieldRefAccess<EnemySlowMouthAttaching, EnemySlowMouth>("enemySlowMouth");

		internal static readonly FieldRef<EnemySlowMouthAttached, EnemySlowMouth> CameraVisualsSlowMouth = AccessTools.FieldRefAccess<EnemySlowMouthAttached, EnemySlowMouth>("enemySlowMouth");

		internal static readonly FieldRef<Enemy, int> EnemyTargetViewID = AccessTools.FieldRefAccess<Enemy, int>("TargetPlayerViewID");
	}
	public static class FreeChassisGranter
	{
		private static EnemyDirector _lastSeenDirector;

		public static void TryGrantOnTick()
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.ReviveEnabled.Value || !Plugin.ReviveFreeChassisOnLevelStart.Value || !SemiFunc.RunIsLevel() || (Object)(object)RoundDirector.instance == (Object)null || Object.FindObjectsOfType<ExtractionPoint>().Length == 0)
			{
				return;
			}
			EnemyDirector instance = EnemyDirector.instance;
			if ((Object)(object)instance == (Object)null || instance == _lastSeenDirector)
			{
				return;
			}
			PlayerController instance2 = PlayerController.instance;
			if ((Object)(object)instance2 == (Object)null || (Object)(object)instance2.playerAvatarScript == (Object)null)
			{
				return;
			}
			string text = SemiFunc.PlayerGetSteamID(instance2.playerAvatarScript);
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			int num = Object.FindObjectsOfType<ExtractionPoint>().Length;
			bool flag = (Object)(object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated;
			ManualLogSource log = Plugin.Log;
			object[] array = new object[5];
			Scene activeScene = SceneManager.GetActiveScene();
			array[0] = ((Scene)(ref activeScene)).name;
			array[1] = num;
			array[2] = flag;
			array[3] = (Object)(object)RoundDirector.instance != (Object)null;
			array[4] = ((Object)instance).GetInstanceID();
			log.LogDebug((object)string.Format("[Revive] grant context: scene='{0}', extPts={1}, levelGen={2}, roundDir={3}, edRef={4}", array));
			if ((Object)(object)RunManager.instance == (Object)null || RunManager.instance.levelsCompleted == 0)
			{
				int value = Plugin.ReviveStartingLives.Value;
				int num2 = SpareChassisInventory.Count(text);
				SpareChassisInventory.Set(text, value);
				Plugin.Log.LogDebug((object)$"[Revive] Spare Chassis (run start): {num2} → {value} (steamID={text})");
			}
			else
			{
				int value2 = Plugin.ReviveLivesPerRound.Value;
				if (value2 <= 0)
				{
					Plugin.Log.LogDebug((object)("[Revive] Spare Chassis (per-round) grant=0, carry-over only (steamID=" + text + ")"));
				}
				else
				{
					int num3 = SpareChassisInventory.Count(text);
					int num4 = num3 + value2;
					SpareChassisInventory.Set(text, num4);
					Plugin.Log.LogDebug((object)$"[Revive] Spare Chassis +{value2} (per-round, carry-over): {num3} → {num4} (steamID={text})");
				}
			}
			_lastSeenDirector = instance;
		}
	}
	public static class ZeroHpReviveTrigger
	{
		private static float _stuckTimer;

		public static void TryOnTick()
		{
			if (!Plugin.ReviveEnabled.Value)
			{
				_stuckTimer = 0f;
				return;
			}
			if (Plugin.ReviveStuckAtZeroSeconds.Value <= 0f)
			{
				_stuckTimer = 0f;
				return;
			}
			if (!SemiFunc.RunIsLevel())
			{
				_stuckTimer = 0f;
				return;
			}
			PlayerController instance = PlayerController.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerAvatarScript == (Object)null)
			{
				_stuckTimer = 0f;
				return;
			}
			PlayerAvatar playerAvatarScript = instance.playerAvatarScript;
			if (!RepoRefs.AvatarIsLocal.Invoke(playerAvatarScript))
			{
				_stuckTimer = 0f;
				return;
			}
			if (RepoRefs.AvatarDeadSet.Invoke(playerAvatarScript))
			{
				_stuckTimer = 0f;
				return;
			}
			PlayerHealth playerHealth = playerAvatarScript.playerHealth;
			if ((Object)(object)playerHealth == (Object)null)
			{
				_stuckTimer = 0f;
				return;
			}
			int num = RepoRefs.HealthValue.Invoke(playerHealth);
			if (num > 0)
			{
				_stuckTimer = 0f;
				return;
			}
			if (!SpareChassisInventory.Has(SemiFunc.PlayerGetSteamID(playerAvatarScript)))
			{
				_stuckTimer = 0f;
				return;
			}
			_stuckTimer += Time.deltaTime;
			if (_stuckTimer < Plugin.ReviveStuckAtZeroSeconds.Value)
			{
				return;
			}
			Plugin.Log.LogWarning((object)$"[Revive] HP={num} stuck at zero for {_stuckTimer:F2}s without vanilla PlayerDeath; forcing death pipeline so chassis can trigger");
			_stuckTimer = 0f;
			try
			{
				playerAvatarScript.PlayerDeath(-1);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[Revive] Forced PlayerDeath threw: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathDone")]
	public static class PlayerDeathDonePatch
	{
		[HarmonyPostfix]
		public static void Postfix(PlayerAvatar __instance)
		{
			if (Plugin.ReviveEnabled.Value && !((Object)(object)__instance == (Object)null) && RepoRefs.AvatarIsLocal.Invoke(__instance))
			{
				string steamID = SemiFunc.PlayerGetSteamID(__instance);
				if (!SpareChassisInventory.Has(steamID))
				{
					Plugin.Log.LogDebug((object)"[Revive] Local player died without Spare Chassis — vanilla death proceeds");
				}
				else if (!ShouldTrigger())
				{
					Plugin.Log.LogDebug((object)"[Revive] Trigger conditions not met (MP teammate alive). Chassis preserved.");
				}
				else if (SpareChassisInventory.Consume(steamID))
				{
					Plugin.Log.LogDebug((object)"[Revive] Triggering Spare Chassis self-revive (sync mode)");
					TryRescueSync(__instance);
				}
			}
		}

		private static void TryRescueSync(PlayerAvatar avatar)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Vector3 position = ((Component)avatar).transform.position;
				Plugin.Log.LogDebug((object)$"[Revive] sync step 1: deathPos={position}, computing respawn position");
				Vector3 val = ResolveRespawnPosition(avatar);
				Quaternion rotation = ((Component)avatar).transform.rotation;
				PlayerDeathHead val2 = RepoRefs.AvatarDeathHead.Invoke(avatar);
				if ((Object)(object)val2 == (Object)null)
				{
					Plugin.Log.LogError((object)"[Revive] sync ABORT: playerDeathHead is null");
					return;
				}
				PhysGrabObject val3 = RepoRefs.DeathHeadPhysGrab.Invoke(val2);
				if ((Object)(object)val3 == (Object)null)
				{
					Plugin.Log.LogError((object)"[Revive] sync ABORT: deathHead.physGrabObject is null");
					return;
				}
				Plugin.Log.LogDebug((object)$"[Revive] sync step 2: teleporting death head to {val}");
				val3.Teleport(val, rotation);
				Plugin.Log.LogDebug((object)"[Revive] sync step 3: custom revive (bypass vanilla to avoid singleton NREs)");
				DoCustomRevive(avatar, val, rotation);
				DetachNearbyEnemies(position, val, 20f);
				ForceReleaseSpewer(avatar);
				Plugin.Log.LogDebug((object)"[Revive] sync step 5: applying HP top-up + i-frames");
				ApplyHeal(avatar);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[Revive] sync rescue EXCEPTION: " + ex.GetType().Name + ": " + ex.Message + "\n" + ex.StackTrace));
			}
		}

		private static void DoCustomRevive(PlayerAvatar avatar, Vector3 position, Quaternion rotation)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			TrySafe("avatar.gameObject.SetActive(true)", delegate
			{
				((Component)avatar).gameObject.SetActive(true);
			});
			TrySafe("set avatar transform", delegate
			{
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				((Component)avatar).transform.position = position;
				((Component)avatar).transform.rotation = rotation;
			});
			TrySafe("clear isDisabled + deadSet", delegate
			{
				RepoRefs.AvatarIsDisabled.Invoke(avatar) = false;
				RepoRefs.AvatarDeadSet.Invoke(avatar) = false;
			});
			TrySafe("playerAvatarVisuals", delegate
			{
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)avatar.playerAvatarVisuals != (Object)null)
				{
					((Component)avatar.playerAvatarVisuals).gameObject.SetActive(true);
					((Component)avatar.playerAvatarVisuals).transform.position = position;
					avatar.playerAvatarVisuals.Revive();
				}
			});
			TrySafe("playerDeathHead.Reset", delegate
			{
				PlayerDeathHead val2 = RepoRefs.AvatarDeathHead.Invoke(avatar);
				if ((Object)(object)val2 != (Object)null)
				{
					val2.Reset();
				}
			});
			TrySafe("playerDeathEffects.Reset", delegate
			{
				if ((Object)(object)avatar.playerDeathEffects != (Object)null)
				{
					avatar.playerDeathEffects.Reset();
				}
			});
			TrySafe("playerReviveEffects.Trigger", delegate
			{
				if ((Object)(object)avatar.playerReviveEffects != (Object)null)
				{
					avatar.playerReviveEffects.Trigger();
				}
			});
			TrySafe("voiceChat.ToggleMixer(false)", delegate
			{
				PlayerVoiceChat val = RepoRefs.AvatarVoiceChat.Invoke(avatar);
				Plugin.Log.LogDebug((object)("[ReviveAudio] voiceChat field: " + (((Object)(object)val == (Object)null) ? "NULL (expected in solo)" : "ok")));
				if ((Object)(object)val != (Object)null)
				{
					val.ToggleMixer(false, false);
					Plugin.Log.LogDebug((object)"[ReviveAudio] voiceChat.ToggleMixer(false) returned");
				}
			});
			TrySafe("AudioManager.SetSoundSnapshot(On)", delegate
			{
				if ((Object)(object)AudioManager.instance != (Object)null)
				{
					AudioManager.instance.SetSoundSnapshot((SoundSnapshot)1, 0.5f);
					Plugin.Log.LogDebug((object)"[ReviveAudio] AudioManager.SetSoundSnapshot(On, 0.5f) called directly (solo-safe path)");
				}
				else
				{
					Plugin.Log.LogWarning((object)"[ReviveAudio] AudioManager.instance is null — cannot restore snapshot");
				}
			});
			TrySafe("HUD.Show", delegate
			{
				if ((Object)(object)HUD.instance != (Object)null)
				{
					HUD.instance.Show();
					Plugin.Log.LogDebug((object)"[Revive] HUD.Show() called — restored game HUD parent GameObject");
				}
				else
				{
					Plugin.Log.LogWarning((object)"[Revive] HUD.instance is null — cannot restore HUD");
				}
			});
			TrySafe("playerHealth.HealOther(1)", delegate
			{
				if ((Object)(object)avatar.playerHealth != (Object)null)
				{
					avatar.playerHealth.HealOther(1, true);
				}
			});
			TrySafe("playerTransform + parent active", delegate
			{
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)avatar.playerTransform != (Object)null)
				{
					avatar.playerTransform.position = position;
					if ((Object)(object)avatar.playerTransform.parent != (Object)null)
					{
						((Component)avatar.playerTransform.parent).gameObject.SetActive(true);
					}
				}
			});
			TrySafe("CameraPosition", delegate
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)CameraPosition.instance != (Object)null)
				{
					((Component)CameraPosition.instance).transform.position = position;
				}
			});
			TrySafe("CameraAim", delegate
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)CameraAim.Instance != (Object)null)
				{
					CameraAim.Instance.SetPlayerAim(Quaternion.Euler(0f, ((Quaternion)(ref rotation)).eulerAngles.y, 0f), true);
					CameraAim.Instance.OverrideNoSmooth(0.25f);
				}
			});
			TrySafe("GameDirector.Revive", delegate
			{
				if ((Object)(object)GameDirector.instance != (Object)null)
				{
					GameDirector.instance.Revive();
				}
			});
			TrySafe("SpectateCamera.StopSpectate", delegate
			{
				SpectateCamera instance2 = SpectateCamera.instance;
				Plugin.Log.LogDebug((object)("[ReviveAudio] SpectateCamera.instance: " + (((Object)(object)instance2 == (Object)null) ? "NULL (was death pipeline run?)" : "ok")));
				if ((Object)(object)instance2 != (Object)null)
				{
					instance2.StopSpectate();
					Plugin.Log.LogDebug((object)"[ReviveAudio] SpectateCamera.StopSpectate returned (should have reset AudioListener target to MainCamera)");
				}
			});
			TrySafe("PlayerController.Revive", delegate
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)PlayerController.instance != (Object)null)
				{
					PlayerController.instance.Revive(((Quaternion)(ref rotation)).eulerAngles);
				}
			});
			TrySafe("CameraGlitch.PlayLongHeal", delegate
			{
				if ((Object)(object)CameraGlitch.Instance != (Object)null)
				{
					CameraGlitch.Instance.PlayLongHeal();
				}
			});
			Plugin.Log.LogDebug((object)"[Revive] custom revive done");
			try
			{
				AudioListenerFollow instance = AudioListenerFollow.instance;
				string text = (((Object)(object)instance == (Object)null) ? "<AudioListenerFollow.instance is null>" : ((!((Object)(object)instance.TargetPositionTransform == (Object)null)) ? ((Object)instance.TargetPositionTransform).name : "<TargetPositionTransform null>"));
				Plugin.Log.LogDebug((object)("[ReviveAudio] post-revive AudioListener target: '" + text + "' (expected 'Main Camera' or similar)"));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[ReviveAudio] post-revive listener log threw: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void DetachNearbyEnemies(Vector3 deathPos, Vector3 respawnPos, float radius)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			TrySafe("DetachNearbyEnemies", delegate
			{
				//IL_030d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0312: Unknown result type (might be due to invalid IL or missing references)
				//IL_0314: Unknown result type (might be due to invalid IL or missing references)
				//IL_0316: Unknown result type (might be due to invalid IL or missing references)
				//IL_0336: Unknown result type (might be due to invalid IL or missing references)
				//IL_0340: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
				//IL_04c4: Unknown result type (might be due to invalid IL or missing references)
				//IL_04d2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00de: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
				//IL_015e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0161: Unknown result type (might be due to invalid IL or missing references)
				//IL_0166: Unknown result type (might be due to invalid IL or missing references)
				//IL_016b: Unknown result type (might be due to invalid IL or missing references)
				//IL_016f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0174: Unknown result type (might be due to invalid IL or missing references)
				//IL_0176: Unknown result type (might be due to invalid IL or missing references)
				//IL_0178: Unknown result type (might be due to invalid IL or missing references)
				//IL_018e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0193: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
				//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
				//IL_0184: Unknown result type (might be due to invalid IL or missing references)
				//IL_0189: Unknown result type (might be due to invalid IL or missing references)
				//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
				//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
				//IL_038e: Unknown result type (might be due to invalid IL or missing references)
				//IL_039f: Unknown result type (might be due to invalid IL or missing references)
				//IL_03a4: Unknown result type (might be due to invalid IL or missing references)
				//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
				//IL_03a8: Unknown result type (might be due to invalid IL or missing references)
				//IL_03c8: Unknown result type (might be due to invalid IL or missing references)
				//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
				EnemyDirector instance = EnemyDirector.instance;
				if (!((Object)(object)instance == (Object)null) && instance.enemiesSpawned != null)
				{
					int num = -1;
					PlayerController instance2 = PlayerController.instance;
					if ((Object)(object)instance2 != (Object)null && (Object)(object)instance2.playerAvatarScript != (Object)null && (Object)(object)instance2.playerAvatarScript.photonView != (Object)null)
					{
						num = instance2.playerAvatarScript.photonView.ViewID;
					}
					int num2 = 0;
					int num3 = 0;
					foreach (EnemyParent item in instance.enemiesSpawned)
					{
						if (!((Object)(object)item == (Object)null))
						{
							num2++;
							Enemy enemy = RepoRefs.ParentEnemy.Invoke(item);
							Vector3 val = (((Object)(object)enemy != (Object)null) ? ((Component)enemy).transform.position : ((Component)item).transform.position);
							float num4 = Vector3.Distance(val, deathPos);
							float num5 = Vector3.Distance(val, respawnPos);
							bool flag = num4 <= radius || num5 <= radius;
							bool flag2 = (Object)(object)enemy != (Object)null && num > 0 && RepoRefs.EnemyTargetViewID.Invoke(enemy) == num && enemy.CheckChase();
							if (flag || flag2)
							{
								Vector3 val2 = val - respawnPos;
								Vector3 val3 = ((Vector3)(ref val2)).normalized;
								if (val3 == Vector3.zero)
								{
									val3 = Vector3.right;
								}
								Vector3 pushTarget = respawnPos + val3 * (radius * 1.5f);
								string text = ((!string.IsNullOrEmpty(item.enemyName)) ? item.enemyName : "?");
								string arg = ((!flag2) ? "radius" : (flag ? "chase+radius" : "chase"));
								Vector3 val4 = val;
								if ((Object)(object)enemy != (Object)null)
								{
									TrySafe("enemy.Freeze", delegate
									{
										enemy.Freeze(1f);
									});
									TrySafe("enemy.DisableChase", delegate
									{
										enemy.DisableChase(2f);
									});
									LevelPoint val5 = null;
									try
									{
										val5 = enemy.TeleportToPoint(100f, 200f);
									}
									catch (Exception ex)
									{
										Plugin.Log.LogWarning((object)("[Revive] TeleportToPoint threw: " + ex.GetType().Name + ": " + ex.Message));
									}
									if ((Object)(object)val5 == (Object)null)
									{
										try
										{
											val5 = enemy.TeleportToPoint(30f, 100f);
										}
										catch (Exception ex2)
										{
											Plugin.Log.LogWarning((object)("[Revive] TeleportToPoint(30,100) threw: " + ex2.GetType().Name + ": " + ex2.Message));
										}
									}
									string text2;
									if ((Object)(object)val5 == (Object)null)
									{
										TrySafe("enemy.EnemyTeleported (fallback)", delegate
										{
											//IL_0007: Unknown result type (might be due to invalid IL or missing references)
											enemy.EnemyTeleported(pushTarget);
										});
										text2 = "fallback-push";
									}
									else
									{
										text2 = "level-point";
									}
									Vector3 position = ((Component)enemy).transform.position;
									float num6 = Vector3.Distance(val4, position);
									Plugin.Log.LogDebug((object)($"[Revive]   {text} pre={val4} post={position} moved={num6:F1}m path={text2} " + $"reason={arg} distDeath={num4:F1} distRespawn={num5:F1}"));
								}
								else
								{
									((Component)item).transform.position = pushTarget;
									Vector3 position2 = ((Component)item).transform.position;
									float num7 = Vector3.Distance(val4, position2);
									Plugin.Log.LogDebug((object)($"[Revive]   {text} pre={val4} post={position2} moved={num7:F1}m path=parent-only " + $"reason={arg} distDeath={num4:F1} distRespawn={num5:F1}"));
								}
								num3++;
							}
						}
					}
					PlayerController pc = PlayerController.instance;
					if ((Object)(object)pc != (Object)null && (Object)(object)pc.playerAvatarScript != (Object)null && (Object)(object)RepoRefs.AvatarTumble.Invoke(pc.playerAvatarScript) != (Object)null)
					{
						TrySafe("player tumble release", delegate
						{
							RepoRefs.AvatarTumble.Invoke(pc.playerAvatarScript).TumbleRequest(false, false);
						});
					}
					Plugin.Log.LogDebug((object)$"[Revive] DetachNearbyEnemies: deaggro'd {num3}/{num2} enemy(ies) within {radius:F1}m (death={deathPos}, respawn={respawnPos})");
				}
			});
		}

		private static void ForceReleaseSpewer(PlayerAvatar localAvatar)
		{
			TrySafe("ForceReleaseSpewer", delegate
			{
				if (!((Object)(object)localAvatar == (Object)null))
				{
					int num = 0;
					int num2 = 0;
					HashSet<EnemySlowMouth> hashSet = new HashSet<EnemySlowMouth>();
					if ((Object)(object)localAvatar.localCamera != (Object)null)
					{
						EnemySlowMouthAttached[] componentsInChildren = ((Component)localAvatar.localCamera).GetComponentsInChildren<EnemySlowMouthAttached>(true);
						foreach (EnemySlowMouthAttached val in componentsInChildren)
						{
							if (!((Object)(object)val == (Object)null))
							{
								EnemySlowMouth val2 = RepoRefs.CameraVisualsSlowMouth.Invoke(val);
								if ((Object)(object)val2 != (Object)null && FlipSlowMouthIfStuck(val2))
								{
									hashSet.Add(val2);
									num++;
								}
								Object.Destroy((Object)(object)((Component)val).gameObject);
								num2++;
							}
						}
					}
					EnemySlowMouthAttaching[] array = Object.FindObjectsOfType<EnemySlowMouthAttaching>(true);
					int num3 = 0;
					int num4 = 0;
					EnemySlowMouthAttaching[] array2 = array;
					foreach (EnemySlowMouthAttaching val3 in array2)
					{
						if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3.targetPlayerAvatar != (Object)(object)localAvatar))
						{
							num3++;
							EnemySlowMouth val4 = RepoRefs.AttachingSlowMouth.Invoke(val3);
							if (!((Object)(object)val4 == (Object)null) && !hashSet.Contains(val4) && FlipSlowMouthIfStuck(val4))
							{
								hashSet.Add(val4);
								num4++;
							}
						}
					}
					Plugin.Log.LogDebug((object)($"[Revive] ForceReleaseSpewer: jaws destroyed={num2}, released via jaw={num}; " + $"scene Attaching={array.Length}, matching local={num3}, released via attaching={num4}"));
				}
			});
		}

		private static bool FlipSlowMouthIfStuck(EnemySlowMouth slowMouth)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Invalid comparison between Unknown and I4
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			State currentState = slowMouth.currentState;
			if ((int)currentState == 9 || (int)currentState == 10 || (int)currentState == 11 || (int)currentState == 16 || (int)currentState == 13 || (int)currentState == 14)
			{
				slowMouth.UpdateState((State)6);
				return true;
			}
			return false;
		}

		private static void TrySafe(string label, Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Revive] custom step '" + label + "' failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal static void ApplyHeal(PlayerAvatar avatar)
		{
			try
			{
				PlayerHealth playerHealth = avatar.playerHealth;
				if ((Object)(object)playerHealth == (Object)null)
				{
					Plugin.Log.LogError((object)"[Revive] ApplyHeal: playerHealth null");
					return;
				}
				int num = RepoRefs.HealthMax.Invoke(playerHealth);
				int num2 = RepoRefs.HealthValue.Invoke(playerHealth);
				int num3 = Mathf.Max(1, num * Plugin.ReviveHpPercent.Value / 100) - num2;
				if (num3 > 0)
				{
					playerHealth.Heal(num3, false);
				}
				playerHealth.InvincibleSet(2f);
				Plugin.Log.LogDebug((object)$"[Revive] Self-revive COMPLETE: HP={RepoRefs.HealthValue.Invoke(playerHealth)}/{num}, 2s i-frames");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[Revive] ApplyHeal EXCEPTION: " + ex.GetType().Name + ": " + ex.Message + "\n" + ex.StackTrace));
			}
		}

		internal static bool ShouldTrigger()
		{
			if (PhotonNetwork.CurrentRoom == null || PhotonNetwork.CurrentRoom.PlayerCount <= 1)
			{
				return true;
			}
			if (!Plugin.ReviveWorksInMultiplayer.Value)
			{
				return false;
			}
			return AllOtherPlayersDead();
		}

		private static bool AllOtherPlayersDead()
		{
			List<PlayerAvatar> list = SemiFunc.PlayerGetList();
			if (list == null)
			{
				return true;
			}
			foreach (PlayerAvatar item in list)
			{
				if (!((Object)(object)item == (Object)null) && !RepoRefs.AvatarIsLocal.Invoke(item) && !RepoRefs.AvatarDeadSet.Invoke(item))
				{
					return false;
				}
			}
			return true;
		}

		private static Vector3 ResolveRespawnPosition(PlayerAvatar avatar)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			switch (Plugin.ReviveRespawnLocation.Value)
			{
			case "Truck":
				return TruckRespawnPosition();
			case "DeathLocation":
				return ((Component)avatar).transform.position;
			default:
			{
				RoundDirector instance = RoundDirector.instance;
				if ((Object)(object)instance != (Object)null && RepoRefs.RoundExtractionActive.Invoke(instance))
				{
					ExtractionPoint val = RepoRefs.RoundExtractionCurrent.Invoke(instance);
					if ((Object)(object)val != (Object)null && (Object)(object)val.safetySpawn != (Object)null)
					{
						return val.safetySpawn.position;
					}
				}
				Plugin.Log.LogDebug((object)"[Revive] No active extraction point, falling back to truck");
				return TruckRespawnPosition();
			}
			}
		}

		private static Vector3 TruckRespawnPosition()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)TruckSafetySpawnPoint.instance != (Object)null)
			{
				return ((Component)TruckSafetySpawnPoint.instance).transform.position;
			}
			if ((Object)(object)TruckHealer.instance != (Object)null)
			{
				return ((Component)TruckHealer.instance).transform.position;
			}
			Plugin.Log.LogError((object)"[Revive] No truck respawn anchor found, using Vector3.zero");
			return Vector3.zero;
		}
	}
	[HarmonyPatch(typeof(Inventory), "ForceUnequip")]
	public static class InventoryForceUnequipPatch
	{
		[HarmonyPrefix]
		public static bool Prefix()
		{
			if (!Plugin.ReviveEnabled.Value)
			{
				return true;
			}
			PlayerController instance = PlayerController.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerAvatarScript == (Object)null)
			{
				return true;
			}
			PlayerAvatar playerAvatarScript = instance.playerAvatarScript;
			if (!RepoRefs.AvatarIsLocal.Invoke(playerAvatarScript))
			{
				return true;
			}
			if (!SpareChassisInventory.Has(SemiFunc.PlayerGetSteamID(playerAvatarScript)))
			{
				return true;
			}
			if (!PlayerDeathDonePatch.ShouldTrigger())
			{
				return true;
			}
			Plugin.Log.LogDebug((object)"[Revive] Inventory.ForceUnequip skipped — chassis revive incoming, keeping hotbar items");
			return false;
		}
	}
	internal static class PendingHeal
	{
		private const int MaxRetries = 300;

		private static PlayerAvatar _avatar;

		private static int _retries;

		public static void Schedule(PlayerAvatar a)
		{
			_avatar = a;
			_retries = 0;
		}

		public static void TryOnTick()
		{
			if ((Object)(object)_avatar == (Object)null)
			{
				return;
			}
			try
			{
				if (++_retries > 300)
				{
					Plugin.Log.LogError((object)$"[Revive] Pending heal gave up after {300} ticks (avatar.isDisabled never cleared)");
					_avatar = null;
				}
				else if (!RepoRefs.AvatarIsDisabled.Invoke(_avatar))
				{
					Plugin.Log.LogDebug((object)$"[Revive] Pending heal: avatar.isDisabled cleared after {_retries} ticks, applying heal");
					PlayerDeathDonePatch.ApplyHeal(_avatar);
					_avatar = null;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[Revive] PendingHeal EXCEPTION: " + ex.GetType().Name + ": " + ex.Message));
				_avatar = null;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerHealth), "Hurt")]
	public static class SoloDamageMultiplierPatch
	{
		[HarmonyPrefix]
		public static void Prefix(ref int damage)
		{
			if (Plugin.SoloDamageEnabled.Value && damage > 0)
			{
				int num = SemiFunc.PlayerGetList()?.Count ?? 1;
				if (num < 1)
				{
					num = 1;
				}
				float num2 = num switch
				{
					1 => Plugin.SoloDamageSoloMult.Value, 
					2 => Plugin.SoloDamageDuoMult.Value, 
					3 => Plugin.SoloDamageTrioMult.Value, 
					_ => Plugin.SoloDamageQuadMult.Value, 
				};
				if (!Mathf.Approximately(num2, 1f))
				{
					int num3 = damage;
					damage = Mathf.Max(0, Mathf.RoundToInt((float)num3 * num2));
					Plugin.Log.LogDebug((object)$"[SoloDamage] players={num} mult={num2:F2} damage {num3} → {damage}");
				}
			}
		}
	}
	internal static class SoloGrantHelper
	{
		private const float ExtractionPointWaitTimeout = 3f;

		private const float PlayerSettleSeconds = 2f;

		internal static bool TryGetSpawnTarget(PlayerAvatar avatar, ref float firstWaitTime, Vector3 offset, out Vector3 pos, out Quaternion rot, out string spawnLoc)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: 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_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.SoloItemSpawnLocation.Value == "Player")
			{
				if (firstWaitTime < 0f)
				{
					firstWaitTime = Time.realtimeSinceStartup;
				}
				if (Time.realtimeSinceStartup - firstWaitTime < 2f)
				{
					pos = default(Vector3);
					rot = default(Quaternion);
					spawnLoc = null;
					return false;
				}
				Transform transform = ((Component)avatar).transform;
				pos = transform.position + transform.forward * 1f + Vector3.up * 0.5f + offset;
				rot = transform.rotation;
				spawnLoc = $"player position {transform.position}";
				return true;
			}
			ExtractionPoint val = null;
			ExtractionPoint[] array = Object.FindObjectsOfType<ExtractionPoint>();
			foreach (ExtractionPoint val2 in array)
			{
				if ((Object)(object)val2 != (Object)null && (Object)(object)val2.safetySpawn != (Object)null)
				{
					val = val2;
					break;
				}
			}
			if ((Object)(object)val != (Object)null)
			{
				pos = val.safetySpawn.position + Vector3.up * 0.5f + offset;
				rot = val.safetySpawn.rotation;
				spawnLoc = $"extraction point {val.safetySpawn.position}";
				return true;
			}
			if (firstWaitTime < 0f)
			{
				firstWaitTime = Time.realtimeSinceStartup;
			}
			float num = Time.realtimeSinceStartup - firstWaitTime;
			if (num < 3f)
			{
				pos = default(Vector3);
				rot = default(Quaternion);
				spawnLoc = null;
				return false;
			}
			Transform transform2 = ((Component)avatar).transform;
			pos = transform2.position + transform2.forward * 1f + Vector3.up * 0.5f + offset;
			rot = transform2.rotation;
			spawnLoc = $"player position {transform2.position} (waited {num:F1}s, no extraction point)";
			return true;
		}

		internal static Item FindItemByKey(string key)
		{
			if ((Object)(object)StatsManager.instance == (Object)null || StatsManager.instance.itemDictionary == null)
			{
				return null;
			}
			if (StatsManager.instance.itemDictionary.Count == 0)
			{
				return null;
			}
			string text = key.ToLower();
			foreach (Item value in StatsManager.instance.itemDictionary.Values)
			{
				if (!((Object)(object)value == (Object)null))
				{
					string text2 = ((Object)value).name ?? "";
					string text3 = value.itemName ?? "";
					if (text2.Replace("Item ", "").ToLower() == text || text3.ToLower() == text || text2.ToLower().Contains(text) || text3.ToLower().Contains(text))
					{
						return value;
					}
				}
			}
			return null;
		}

		internal static GameObject SpawnItem(Item item, Vector3 pos, Quaternion rot, string tag)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = ((GameManager.instance.gameMode != 0) ? PhotonNetwork.InstantiateRoomObject(item.prefab.ResourcePath, pos, rot, (byte)0, (object[])null) : Object.Instantiate<GameObject>(item.prefab.Prefab, pos, rot));
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)("[" + tag + "] Spawn returned null — will retry next tick"));
				return null;
			}
			if (!val.activeSelf)
			{
				val.SetActive(true);
				Plugin.Log.LogInfo((object)("[" + tag + "] Spawned object was inactive — set active"));
			}
			ManualLogSource log = Plugin.Log;
			object[] obj = new object[5] { tag, val.activeSelf, val.activeInHierarchy, null, null };
			Scene scene = val.scene;
			obj[3] = ((Scene)(ref scene)).name;
			obj[4] = val.transform.position;
			log.LogInfo((object)string.Format("[{0}] Diagnostic: activeSelf={1}, activeInHierarchy={2}, scene='{3}', pos={4}", obj));
			return val;
		}

		internal static void AttachBlueGlow(GameObject parent, float intensity, float range)
		{
			//IL_0005: 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)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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)
			try
			{
				GameObject val = new GameObject("SoloItemGlow");
				val.transform.SetParent(parent.transform, false);
				val.transform.localPosition = Vector3.zero;
				Light obj = val.AddComponent<Light>();
				obj.type = (LightType)2;
				obj.color = new Color(0.25f, 0.6f, 1f);
				obj.intensity = intensity;
				obj.range = range;
				obj.shadows = (LightShadows)0;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SoloGrant] Glow attach threw: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
	public static class SoloSwordGranter
	{
		private static bool _permanentGiveup;

		private static GameObject _grantedSwordGO;

		private static Item _cachedSwordItem;

		private static float _firstWaitTime = -1f;

		private static EnemyDirector _lastSeenDirector;

		private const float ExtractionPointWaitTimeout = 3f;

		private const float PlayerSettleSeconds = 2f;

		public static bool IsOurSword(GameObject go)
		{
			if ((Object)(object)_grantedSwordGO != (Object)null && (Object)(object)go != (Object)null)
			{
				return (Object)(object)_grantedSwordGO == (Object)(object)go;
			}
			return false;
		}

		public static void TryGrantOnTick()
		{
			//IL_064c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02df: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_0307: Unknown result type (might be due to invalid IL or missing references)
			//IL_030a: Unknown result type (might be due to invalid IL or missing references)
			//IL_030f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0317: Unknown result type (might be due to invalid IL or missing references)
			//IL_0381: Unknown result type (might be due to invalid IL or missing references)
			//IL_0386: Unknown result type (might be due to invalid IL or missing references)
			//IL_0390: Unknown result type (might be due to invalid IL or missing references)
			//IL_0395: Unknown result type (might be due to invalid IL or missing references)
			//IL_039a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_047f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0481: Unknown result type (might be due to invalid IL or missing references)
			//IL_0467: Unknown result type (might be due to invalid IL or missing references)
			//IL_0469: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0400: Unknown result type (might be due to invalid IL or missing references)
			//IL_040a: Unknown result type (might be due to invalid IL or missing references)
			//IL_040f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0414: Unknown result type (might be due to invalid IL or missing references)
			//IL_041e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0423: Unknown result type (might be due to invalid IL or missing references)
			//IL_0428: Unknown result type (might be due to invalid IL or missing references)
			//IL_042b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0430: Unknown result type (might be due to invalid IL or missing references)
			//IL_0438: Unknown result type (might be due to invalid IL or missing references)
			//IL_0510: Unknown result type (might be due to invalid IL or missing references)
			//IL_0515: Unknown result type (might be due to invalid IL or missing references)
			//IL_0528: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.SoloSwordEnabled.Value || _permanentGiveup || !SemiFunc.RunIsLevel() || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated)
			{
				return;
			}
			EnemyDirector instance = EnemyDirector.instance;
			if ((Object)(object)instance != (Object)null && instance != _lastSeenDirector)
			{
				_firstWaitTime = -1f;
				_lastSeenDirector = instance;
			}
			if ((Object)(object)_grantedSwordGO != (Object)null)
			{
				return;
			}
			PlayerController instance2 = PlayerController.instance;
			if ((Object)(object)instance2 == (Object)null || (Object)(object)instance2.playerAvatarScript == (Object)null || (Object)(object)StatsManager.instance == (Object)null || StatsManager.instance.itemDictionary == null)
			{
				return;
			}
			if ((Object)(object)_cachedSwordItem == (Object)null)
			{
				if (StatsManager.instance.itemDictionary.Count == 0)
				{
					return;
				}
				foreach (Item value2 in StatsManager.instance.itemDictionary.Values)
				{
					if (!((Object)(object)value2 == (Object)null))
					{
						string text = ((Object)value2).name ?? "";
						string text2 = value2.itemName ?? "";
						if (text.Replace("Item ", "").ToLower() == "sword" || text2.ToLower() == "sword" || text.ToLower().Contains("sword") || text2.ToLower().Contains("sword"))
						{
							_cachedSwordItem = value2;
							Plugin.Log.LogInfo((object)("[SoloSword] Matched sword item — name='" + text + "', itemName='" + text2 + "'"));
							break;
						}
					}
				}
				if ((Object)(object)_cachedSwordItem == (Object)null)
				{
					List<string> list = new List<string>();
					foreach (Item value3 in StatsManager.instance.itemDictionary.Values)
					{
						if ((Object)(object)value3 != (Object)null)
						{
							list.Add(((Object)value3).name + "|" + value3.itemName);
						}
					}
					Plugin.Log.LogWarning((object)string.Format("[SoloSword] No sword found among {0} items. Names (name|itemName): {1}", list.Count, string.Join(", ", list)));
					_permanentGiveup = true;
					return;
				}
			}
			Item cachedSwordItem = _cachedSwordItem;
			Transform transform = ((Component)instance2.playerAvatarScript).transform;
			Vector3 val;
			Quaternion rotation;
			string arg;
			if (Plugin.SoloItemSpawnLocation.Value == "Player")
			{
				if (_firstWaitTime < 0f)
				{
					_firstWaitTime = Time.realtimeSinceStartup;
				}
				if (Time.realtimeSinceStartup - _firstWaitTime < 2f)
				{
					return;
				}
				val = transform.position + transform.forward * 1f + Vector3.up * 0.5f;
				rotation = transform.rotation;
				arg = $"player position {transform.position}";
			}
			else
			{
				ExtractionPoint val2 = null;
				ExtractionPoint[] array = Object.FindObjectsOfType<ExtractionPoint>();
				foreach (ExtractionPoint val3 in array)
				{
					if ((Object)(object)val3 != (Object)null && (Object)(object)val3.safetySpawn != (Object)null)
					{
						val2 = val3;
						break;
					}
				}
				if ((Object)(object)val2 != (Object)null)
				{
					val = val2.safetySpawn.position + Vector3.up * 0.5f;
					rotation = val2.safetySpawn.rotation;
					arg = $"extraction point {val2.safetySpawn.position}";
				}
				else
				{
					if (_firstWaitTime < 0f)
					{
						_firstWaitTime = Time.realtimeSinceStartup;
					}
					float num = Time.realtimeSinceStartup - _firstWaitTime;
					if (num < 3f)
					{
						return;
					}
					val = transform.position + transform.forward * 1f + Vector3.up * 0.5f;
					rotation = transform.rotation;
					arg = $"player position {transform.position} (waited {num:F1}s, no extraction point)";
				}
			}
			GameObject val4 = ((GameManager.instance.gameMode != 0) ? PhotonNetwork.InstantiateRoomObject(cachedSwordItem.prefab.ResourcePath, val, rotation, (byte)0, (object[])null) : Object.Instantiate<GameObject>(cachedSwordItem.prefab.Prefab, val, rotation));
			if ((Object)(object)val4 == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[SoloSword] Spawn returned null — will retry next tick");
				return;
			}
			_grantedSwordGO = val4;
			if (!val4.activeSelf)
			{
				val4.SetActive(true);
				Plugin.Log.LogInfo((object)"[SoloSword] Spawned sword was inactive — set active");
			}
			SoloGrantHelper.AttachBlueGlow(val4, 5f, 6f);
			ManualLogSource log = Plugin.Log;
			object[] obj = new object[4] { val4.activeSelf, val4.activeInHierarchy, null, null };
			Scene scene = val4.scene;
			obj[2] = ((Scene)(ref scene)).name;
			obj[3] = val4.transform.position;
			log.LogInfo((object)string.Format("[SoloSword] Diagnostic: activeSelf={0}, activeInHierarchy={1}, scene='{2}', pos={3}", obj));
			try
			{
				HurtCollider componentInChildren = val4.GetComponentInChildren<HurtCollider>();
				if ((Object)(object)componentInChildren != (Object)null)
				{
					int value = Plugin.SoloSwordDamagePercent.Value;
					int playerDamage = componentInChildren.playerDamage;
					int enemyDamage = componentInChildren.enemyDamage;
					componentInChildren.playerDamage = ((playerDamage != 0) ? Mathf.Max(1, playerDamage * value / 100) : 0);
					componentInChildren.enemyDamage = ((enemyDamage != 0) ? Mathf.Max(1, enemyDamage * value / 100) : 0);
					Plugin.Log.LogInfo((object)$"[SoloSword] Reduced HurtCollider damage to {value}%: player {playerDamage}→{componentInChildren.playerDamage}, enemy {enemyDamage}→{componentInChildren.enemyDamage}");
				}
				else
				{
					Plugin.Log.LogWarning((object)"[SoloSword] No HurtCollider found on spawned sword — damage reduction skipped");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SoloSword] Damage reduction threw: " + ex.GetType().Name + ": " + ex.Message));
			}
			Plugin.Log.LogInfo((object)$"[SoloSword] Granted unlimited-durability sword at {val} (spawn={arg}). Pick it up to equip.");
		}
	}
	[HarmonyPatch(typeof(ItemMelee), "FixedUpdate")]
	public static class ItemMeleeFixedUpdatePatch
	{
		[HarmonyPostfix]
		public static void Postfix(ItemMelee __instance)
		{
			if (Plugin.SoloSwordEnabled.Value && SoloSwordGranter.IsOurSword(((Component)__instance).gameObject))
			{
				ItemBattery component = ((Component)__instance).GetComponent<ItemBattery>();
				if ((Object)(object)component != (Object)null && component.batteryLife < 100f)
				{
					component.batteryLife = 100f;
				}
			}
		}
	}
	public static class SoloTranqGranter
	{
		private static bool _permanentGiveup;

		private static GameObject _grantedTranqGO;

		private static Item _cachedTranqItem;

		private static float _firstWaitTime = -1f;

		private static EnemyDirector _lastSeenDirector;

		public static bool IsOurTranq(GameObject go)
		{
			if ((Object)(object)_grantedTranqGO != (Object)null && (Object)(object)go != (Object)null)
			{
				return (Object)(object)_grantedTranqGO == (Object)(object)go;
			}
			return false;
		}

		public static void TryGrantOnTick()
		{
			//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.SoloTranqEnabled.Value || _permanentGiveup || !SemiFunc.RunIsLevel() || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated)
			{
				return;
			}
			EnemyDirector instance = EnemyDirector.instance;
			if ((Object)(object)instance != (Object)null && instance != _lastSeenDirector)
			{
				_firstWaitTime = -1f;
				_lastSeenDirector = instance;
			}
			if ((Object)(object)_grantedTranqGO != (Object)null)
			{
				return;
			}
			PlayerController instance2 = PlayerController.instance;
			if ((Object)(object)instance2 == (Object)null || (Object)(object)instance2.playerAvatarScript == (Object)null)
			{
				return;
			}
			if ((Object)(object)_cachedTranqItem == (Object)null)
			{
				_cachedTranqItem = SoloGrantHelper.FindItemByKey("tranq");
				if ((Object)(object)_cachedTranqItem == (Object)null)
				{
					if ((Object)(object)StatsManager.instance != (Object)null && StatsManager.instance.itemDictionary != null && StatsManager.instance.itemDictionary.Count > 0)
					{
						Plugin.Log.LogWarning((object)"[SoloTranq] No tranq gun found in itemDictionary; skipping grant");
						_permanentGiveup = true;
					}
					return;
				}
				Plugin.Log.LogInfo((object)("[SoloTranq] Matched tranq item — name='" + ((Object)_cachedTranqItem).name + "', itemName='" + _cachedTranqItem.itemName + "'"));
			}
			if (!SoloGrantHelper.TryGetSpawnTarget(instance2.playerAvatarScript, ref _firstWaitTime, Vector3.right * 0.8f, out var pos, out var rot, out var spawnLoc))
			{
				return;
			}
			GameObject val = SoloGrantHelper.SpawnItem(_cachedTranqItem, pos, rot, "SoloTranq");
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			_grantedTranqGO = val;
			SoloGrantHelper.AttachBlueGlow(val, 2f, 3f);
			try
			{
				ItemGun component = val.GetComponent<ItemGun>();
				if ((Object)(object)component != (Object)null)
				{
					float shootCooldown = component.shootCooldown;
					component.shootCooldown = Plugin.SoloTranqShootCooldownSeconds.Value;
					Plugin.Log.LogInfo((object)$"[SoloTranq] shootCooldown {shootCooldown:F2}s → {component.shootCooldown:F2}s");
					if ((Object)(object)component.bulletPrefab != (Object)null)
					{
						HurtCollider[] componentsInChildren = component.bulletPrefab.GetComponentsInChildren<HurtCollider>(true);
						int num = 0;
						HurtCollider[] array = componentsInChildren;
						foreach (HurtCollider val2 in array)
						{
							if ((Object)(object)val2 != (Object)null)
							{
								val2.enemyStunTime = Plugin.SoloTranqStunSeconds.Value;
								num++;
							}
						}
						Plugin.Log.LogInfo((object)$"[SoloTranq] Patched {num} HurtCollider(s) on bullet prefab to enemyStunTime={Plugin.SoloTranqStunSeconds.Value:F1}s");
					}
					else
					{
						Plugin.Log.LogWarning((object)"[SoloTranq] gun.bulletPrefab is null — stun override skipped");
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SoloTranq] gun-config override threw: " + ex.GetType().Name + ": " + ex.Message));
			}
			Plugin.Log.LogInfo((object)$"[SoloTranq] Granted Tranq Gun at {pos} (spawn={spawnLoc}, stun={Plugin.SoloTranqStunSeconds.Value:F1}s). Pick it up to equip.");
		}
	}
	[HarmonyPatch(typeof(ItemGun), "ShootBulletRPC")]
	public static class ItemGunShootBulletPatch
	{
		private static readonly FieldRef<ItemGun, HurtCollider> ItemGunHurtColliderRef = AccessTools.FieldRefAccess<ItemGun, HurtCollider>("hurtCollider");

		private static readonly FieldRef<ItemBattery, int> BatteryLifeIntRef = AccessTools.FieldRefAccess<ItemBattery, int>("batteryLifeInt");

		private static int _shotCount;

		[HarmonyPostfix]
		public static void Postfix(ItemGun __instance)
		{
			if (!Plugin.SoloTranqEnabled.Value || !SoloTranqGranter.IsOurTranq(((Component)__instance).gameObject))
			{
				return;
			}
			float value = Plugin.SoloTranqStunSeconds.Value;
			int num = 0;
			HurtCollider val = ItemGunHurtColliderRef.Invoke(__instance);
			if ((Object)(object)val != (Object)null)
			{
				HurtCollider[] componentsInChildren = ((Component)((Component)val).transform.root).GetComponentsInChildren<HurtCollider>(true);
				foreach (HurtCollider val2 in componentsInChildren)
				{
					if ((Object)(object)val2 != (Object)null)
					{
						val2.enemyStunTime = value;
						num++;
					}
				}
			}
			_shotCount++;
			ItemBattery component = ((Component)__instance).GetComponent<ItemBattery>();
			float num2 = (((Object)(object)component != (Object)null) ? component.batteryLife : (-1f));
			int num3 = (((Object)(object)component != (Object)null) ? BatteryLifeIntRef.Invoke(component) : (-1));
			if ((Object)(object)component != (Object)null)
			{
				component.batteryLife = 100f;
				BatteryLifeIntRef.Invoke(component) = component.batteryBars;
			}
			float num4 = (((Object)(object)component != (Object)null) ? component.batteryLife : (-1f));
			int num5 = (((Object)(object)component != (Object)null) ? BatteryLifeIntRef.Invoke(component) : (-1));
			Plugin.Log.LogInfo((object)$"[SoloTranq] Shot #{_shotCount} fired (stun={value:F1}s patched on {num} HurtCollider(s), battery={num2:F1}/{num3}→{num4:F1}/{num5})");
		}
	}
}
namespace LetMeSoloThem.Hud
{
	public class SoloGraceHud : MonoBehaviour
	{
		private const float FadeOutSeconds = 3f;

		private const float ChassisRevivingSeconds = 2f;

		private const float ChassisUsedFadeSeconds = 5f;

		private GUIStyle _style;

		private float _currentTimer;

		private float _previousTimer;

		private float _fadeOutRemaining;

		private bool _shouldShowGrace;

		private float _diagLogTimer;

		private bool _chassisWasDrawing;

		private int _previousChassisCount;

		private float _chassisRevivingRemaining;

		private float _chassisUsedFadeRemaining;

		private void OnDestroy()
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogDebug((object)"[HUD] OnDestroy fired (Plugin will recreate on next sceneLoaded)");
			}
		}

		private void Update()
		{
			_diagLogTimer -= Time.deltaTime;
			bool flag = _diagLogTimer <= 0f;
			if (flag)
			{
				_diagLogTimer = 5f;
			}
			try
			{
				UpdateBody(flag);
				FreeChassisGranter.TryGrantOnTick();
				ZeroHpReviveTrigger.TryOnTick();
				SoloSwordGranter.TryGrantOnTick();
				SoloTranqGranter.TryGrantOnTick();
				PendingHeal.TryOnTick();
				TrackChassisTransition();
				if (flag)
				{
					ChassisDiagnostic();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[HUD] Update threw: " + ex.GetType().Name + ": " + ex.Message + "\n" + ex.StackTrace));
			}
		}

		private void TrackChassisTransition()
		{
			bool flag = false;
			if (_chassisRevivingRemaining > 0f)
			{
				_chassisRevivingRemaining -= Time.deltaTime;
				if (_chassisRevivingRemaining <= 0f)
				{
					_chassisRevivingRemaining = 0f;
					flag = true;
				}
			}
			if (_chassisUsedFadeRemaining > 0f)
			{
				_chassisUsedFadeRemaining -= Time.deltaTime;
				if (_chassisUsedFadeRemaining < 0f)
				{
					_chassisUsedFadeRemaining = 0f;
				}
			}
			if (!SemiFunc.RunIsLevel())
			{
				_previousChassisCount = 0;
				return;
			}
			PlayerController instance = PlayerController.instance;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.playerAvatarScript == (Object)null))
			{
				int num = SpareChassisInventory.Count(SemiFunc.PlayerGetSteamID(instance.playerAvatarScript));
				if (num < _previousChassisCount && _chassisRevivingRemaining <= 0f)
				{
					_chassisRevivingRemaining = 2f;
					_chassisUsedFadeRemaining = 0f;
					Plugin.Log.LogDebug((object)$"[Chassis HUD] chassis used (count {_previousChassisCount}→{num}) — showing 'Reviving' for 2s");
				}
				if (flag && num == 0)
				{
					_chassisUsedFadeRemaining = 5f;
					Plugin.Log.LogDebug((object)"[Chassis HUD] 'Reviving' done, no chassis left — starting 'Used' fade");
				}
				_previousChassisCount = num;
			}
		}

		private void ChassisDiagnostic()
		{
			if (!Plugin.ReviveEnabled.Value)
			{
				Plugin.Log.LogDebug((object)"[Chassis HUD] state: ReviveEnabled=false (label won't draw)");
				return;
			}
			if (!SemiFunc.RunIsLevel())
			{
				Plugin.Log.LogDebug((object)"[Chassis HUD] state: not in level");
				return;
			}
			PlayerController instance = PlayerController.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerAvatarScript == (Object)null)
			{
				Plugin.Log.LogDebug((object)"[Chassis HUD] state: PlayerController/avatar not ready");
				return;
			}
			string text = SemiFunc.PlayerGetSteamID(instance.playerAvatarScript);
			int num = SpareChassisInventory.Count(text);
			Plugin.Log.LogDebug((object)$"[Chassis HUD] state: steamID='{text}', count={num}, hudEnabled={Plugin.HudEnabled.Value}, willDraw={num > 0 && Plugin.HudEnabled.Value}");
		}

		private void UpdateBody(bool shouldLog)
		{
			if (!SemiFunc.RunIsLevel())
			{
				_shouldShowGrace = _fadeOutRemaining > 0f;
				_previousTimer = (_currentTimer = 0f);
				TickFade();
				if (shouldLog)
				{
					Plugin.Log.LogDebug((object)"[HUD] gated: SemiFunc.RunIsLevel()=false");
				}
				return;
			}
			int count = SemiFunc.PlayerGetList().Count;
			if (count != 1)
			{
				_shouldShowGrace = false;
				_fadeOutRemaining = 0f;
				if (shouldLog)
				{
					Plugin.Log.LogDebug((object)$"[HUD] gated grace: not-solo, playerCount={count}");
				}
				return;
			}
			EnemyDirector instance = EnemyDirector.instance;
			if ((Object)(object)instance == (Object)null)
			{
				_shouldShowGrace = false;
				_fadeOutRemaining = 0f;
				if (shouldLog)
				{
					Plugin.Log.LogDebug((object)"[HUD] gated grace: EnemyDirector.instance=null");
				}
				return;
			}
			_previousTimer = _currentTimer;
			_currentTimer = EnemyDirectorStartPatch.SpawnIdlePauseTimerRef.Invoke(instance);
			if (_previousTimer > 0f && _currentTimer <= 0f)
			{
				_fadeOutRemaining = 3f;
			}
			TickFade();
			_shouldShowGrace = _currentTimer > 0f || _fadeOutRemaining > 0f;
			if (shouldLog)
			{
				Plugin.Log.LogDebug((object)$"[HUD] in-level: playerCount={count}, timer={_currentTimer:F1}, showGrace={_shouldShowGrace}");
			}
		}

		private void TickFade()
		{
			if (_fadeOutRemaining > 0f)
			{
				_fadeOutRemaining -= Time.deltaTime;
				if (_fadeOutRemaining < 0f)
				{
					_fadeOutRemaining = 0f;
				}
			}
		}

		private void OnGUI()
		{
			if (Plugin.HudEnabled.Value)
			{
				EnsureStyle();
				if (_shouldShowGrace)
				{
					DrawGraceTimer();
				}
				DrawChassisLabel();
			}
		}

		private void EnsureStyle()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			int value = Plugin.HudFontSize.Value;
			if (_style == null || _style.fontSize != value)
			{
				_style = new GUIStyle(GUI.skin.label)
				{
					fontSize = value,
					alignment = (TextAnchor)4,
					fontStyle = (FontStyle)1
				};
			}
		}

		private void DrawGraceTimer()
		{
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			string text;
			Color val = default(Color);
			if (_currentTimer > 0f)
			{
				int num = (int)(_currentTimer / 60f);
				int num2 = (int)(_currentTimer % 60f);
				text = $"Solo grace: {num}:{num2:D2}";
				if (_currentTimer > 30f)
				{
					((Color)(ref val))..ctor(0.4f, 1f, 0.4f, 1f);
				}
				else if (_currentTimer > 10f)
				{
					((Color)(ref val))..ctor(1f, 0.9f, 0.3f, 1f);
				}
				else
				{
					((Color)(ref val))..ctor(1f, 0.4f, 0.4f, 1f);
				}
			}
			else
			{
				float num3 = Mathf.Clamp01(_fadeOutRemaining / 3f);
				text = "Solo grace ended";
				((Color)(ref val))..ctor(1f, 0.3f, 0.3f, num3);
			}
			Rect val2 = default(Rect);
			((Rect)(ref val2))..ctor((float)Screen.width / 2f - 150f, 20f, 300f, 30f);
			_style.normal.textColor = new Color(0f, 0f, 0f, val.a * 0.85f);
			GUI.Label(new Rect(((Rect)(ref val2)).x + 1f, ((Rect)(ref val2)).y + 1f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), text, _style);
			_style.normal.textColor = val;
			GUI.Label(val2, text, _style);
		}

		private void DrawChassisLabel()
		{
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.ReviveEnabled.Value)
			{
				OnChassisGate();
				return;
			}
			if (!SemiFunc.RunIsLevel())
			{
				OnChassisGate();
				return;
			}
			PlayerController instance = PlayerController.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerAvatarScript == (Object)null)
			{
				OnChassisGate();
				return;
			}
			string text = SemiFunc.PlayerGetSteamID(instance.playerAvatarScript);
			int num = SpareChassisInventory.Count(text);
			string text2;
			Color val = default(Color);
			if (_chassisRevivingRemaining > 0f)
			{
				text2 = "Spare Chassis: Reviving";
				((Color)(ref val))..ctor(0.4f, 1f, 0.4f, 1f);
			}
			else if (num > 0)
			{
				text2 = ((num > 1) ? $"Spare Chassis: Ready ({num})" : "Spare Chassis: Ready");
				((Color)(ref val))..ctor(0.55f, 0.85f, 1f, 1f);
				if (!_chassisWasDrawing)
				{
					_chassisWasDrawing = true;
					Plugin.Log.LogDebug((object)$"[Chassis HUD] drawing label START 'Ready' count={num} (steamID='{text}')");
				}
			}
			else
			{
				if (!(_chassisUsedFadeRemaining > 0f))
				{
					OnChassisGate();
					return;
				}
				text2 = "Spare Chassis: Used";
				float num2 = Mathf.Clamp01(_chassisUsedFadeRemaining / 5f);
				((Color)(ref val))..ctor(1f, 0.4f, 0.4f, num2);
			}
			Rect val2 = default(Rect);
			((Rect)(ref val2))..ctor((float)Screen.width / 2f - 150f, 55f, 300f, 30f);
			_style.normal.textColor = new Color(0f, 0f, 0f, val.a * 0.85f);
			GUI.Label(new Rect(((Rect)(ref val2)).x + 1f, ((Rect)(ref val2)).y + 1f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), text2, _style);
			_style.normal.textColor = val;
			GUI.Label(val2, text2, _style);
		}

		private void OnChassisGate()
		{
			if (_chassisWasDrawing)
			{
				_chassisWasDrawing = false;
				Plugin.Log.LogDebug((object)"[Chassis HUD] drawing label STOP");
			}
		}
	}
}