Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LetMeSoloThem v0.3.1
LetMeSoloThem.dll
Decompiled a day agousing 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"); } } } }