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 SoloGraceFloor; internal static ConfigEntry SoloGraceOverrideMode; internal static ConfigEntry HudEnabled; internal static ConfigEntry HudFontSize; internal static ConfigEntry ReviveEnabled; internal static ConfigEntry ReviveHpPercent; internal static ConfigEntry ReviveRespawnLocation; internal static ConfigEntry ReviveWorksInMultiplayer; internal static ConfigEntry ReviveFreeChassisOnLevelStart; internal static ConfigEntry ReviveStartingLives; internal static ConfigEntry ReviveLivesPerRound; internal static ConfigEntry ReviveStuckAtZeroSeconds; internal static ConfigEntry SoloSwordEnabled; internal static ConfigEntry SoloSwordDamagePercent; internal static ConfigEntry SoloItemSpawnLocation; internal static ConfigEntry SoloTranqEnabled; internal static ConfigEntry SoloTranqStunSeconds; internal static ConfigEntry SoloTranqShootCooldownSeconds; internal static ConfigEntry SoloDamageEnabled; internal static ConfigEntry SoloDamageSoloMult; internal static ConfigEntry SoloDamageDuoMult; internal static ConfigEntry SoloDamageTrioMult; internal static ConfigEntry 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("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(0f, 600f), Array.Empty())); SoloGraceOverrideMode = ((BaseUnityPlugin)this).Config.Bind("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("HUD", "Enabled", true, "Show the on-screen Solo Grace timer during solo runs."); HudFontSize = ((BaseUnityPlugin)this).Config.Bind("HUD", "FontSize", 20, new ConfigDescription("Pixel font size of the HUD text.", (AcceptableValueBase)(object)new AcceptableValueRange(8, 60), Array.Empty())); ReviveEnabled = ((BaseUnityPlugin)this).Config.Bind("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("Self-Revive", "HpPercent", 50, new ConfigDescription("Percent of max HP the player has after self-revive.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); ReviveRespawnLocation = ((BaseUnityPlugin)this).Config.Bind("Self-Revive", "RespawnLocation", "ExtractionPoint", new ConfigDescription("Where the player respawns after self-revive.", (AcceptableValueBase)(object)new AcceptableValueList(new string[3] { "ExtractionPoint", "Truck", "DeathLocation" }), Array.Empty())); ReviveWorksInMultiplayer = ((BaseUnityPlugin)this).Config.Bind("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("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("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(0, 99), Array.Empty())); ReviveLivesPerRound = ((BaseUnityPlugin)this).Config.Bind("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(0, 99), Array.Empty())); ReviveStuckAtZeroSeconds = ((BaseUnityPlugin)this).Config.Bind("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(0f, 5f), Array.Empty())); SoloSwordEnabled = ((BaseUnityPlugin)this).Config.Bind("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("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(1, 100), Array.Empty())); SoloItemSpawnLocation = ((BaseUnityPlugin)this).Config.Bind("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(new string[2] { "Player", "ExtractionPoint" }), Array.Empty())); SoloTranqEnabled = ((BaseUnityPlugin)this).Config.Bind("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("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(0.5f, 60f), Array.Empty())); SoloTranqShootCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind("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(0.1f, 10f), Array.Empty())); SoloDamageEnabled = ((BaseUnityPlugin)this).Config.Bind("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("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(0f, 2f), Array.Empty())); SoloDamageDuoMult = ((BaseUnityPlugin)this).Config.Bind("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(0f, 2f), Array.Empty())); SoloDamageTrioMult = ((BaseUnityPlugin)this).Config.Bind("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(0f, 2f), Array.Empty())); SoloDamageQuadMult = ((BaseUnityPlugin)this).Config.Bind("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(0f, 2f), Array.Empty())); _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 _countsBySteamID = new Dictionary(); 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 SpawnIdlePauseTimerRef = AccessTools.FieldRefAccess("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 AvatarIsLocal = AccessTools.FieldRefAccess("isLocal"); internal static readonly FieldRef AvatarDeadSet = AccessTools.FieldRefAccess("deadSet"); internal static readonly FieldRef AvatarIsDisabled = AccessTools.FieldRefAccess("isDisabled"); internal static readonly FieldRef HealthValue = AccessTools.FieldRefAccess("health"); internal static readonly FieldRef HealthMax = AccessTools.FieldRefAccess("maxHealth"); internal static readonly FieldRef RoundExtractionActive = AccessTools.FieldRefAccess("extractionPointActive"); internal static readonly FieldRef RoundExtractionCurrent = AccessTools.FieldRefAccess("extractionPointCurrent"); internal static readonly FieldRef AvatarDeathHead = AccessTools.FieldRefAccess("playerDeathHead"); internal static readonly FieldRef AvatarTumble = AccessTools.FieldRefAccess("tumble"); internal static readonly FieldRef AvatarVoiceChat = AccessTools.FieldRefAccess("voiceChat"); internal static readonly FieldRef DeathHeadPhysGrab = AccessTools.FieldRefAccess("physGrabObject"); internal static readonly FieldRef ParentEnemy = AccessTools.FieldRefAccess("Enemy"); internal static readonly FieldRef AttachingSlowMouth = AccessTools.FieldRefAccess("enemySlowMouth"); internal static readonly FieldRef CameraVisualsSlowMouth = AccessTools.FieldRefAccess("enemySlowMouth"); internal static readonly FieldRef EnemyTargetViewID = AccessTools.FieldRefAccess("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().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().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) ? "" : ((!((Object)(object)instance.TargetPositionTransform == (Object)null)) ? ((Object)instance.TargetPositionTransform).name : "")); 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 hashSet = new HashSet(); if ((Object)(object)localAvatar.localCamera != (Object)null) { EnemySlowMouthAttached[] componentsInChildren = ((Component)localAvatar.localCamera).GetComponentsInChildren(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(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 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(); 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(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(); 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 list = new List(); 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(); 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(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(); 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(); 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(); 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(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 ItemGunHurtColliderRef = AccessTools.FieldRefAccess("hurtCollider"); private static readonly FieldRef BatteryLifeIntRef = AccessTools.FieldRefAccess("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(true); foreach (HurtCollider val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null) { val2.enemyStunTime = value; num++; } } } _shotCount++; ItemBattery component = ((Component)__instance).GetComponent(); 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"); } } } }