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.7.0.0")] [assembly: AssemblyInformationalVersion("0.7.0+3affdbf86ba7fe5a68db016b1666621701e9535b")] [assembly: AssemblyProduct("LetMeSoloThem")] [assembly: AssemblyTitle("LetMeSoloThem")] [assembly: AssemblyVersion("0.7.0.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.7.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.pogwas.letmesolothem"; public const string PluginName = "Let me Solo Them"; public const string PluginVersion = "0.7.0"; 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; internal static ConfigEntry SoloStrengthEnabled; internal static ConfigEntry SoloStrengthStartingStrength; internal static ConfigEntry SoloStrengthPerRound; internal static ConfigEntry SoloStrengthWorksInMultiplayer; internal static ConfigEntry SoloEnemyEnabled; internal static ConfigEntry SoloEnemyDetectionSolo; internal static ConfigEntry SoloEnemyDetectionDuo; internal static ConfigEntry SoloEnemyDetectionTrio; internal static ConfigEntry SoloEnemyDetectionQuad; internal static ConfigEntry SoloCarryEscapeEnabled; internal static ConfigEntry SoloCarryEscapeWorksInMultiplayer; internal static ConfigEntry SoloCarryEscapeStrugglePresses; internal static ConfigEntry SoloCarryEscapeAttackPressDamage; internal static ConfigEntry SoloCarryEscapeDeaggroFreezeSeconds; internal static ConfigEntry SoloCarryEscapeDeaggroChaseDisableSeconds; internal static ConfigEntry SoloCarryEscapeStruggleInputDebounceSeconds; internal static ConfigEntry SoloExtractionEnabled; internal static ConfigEntry SoloExtractionWorksInMultiplayer; internal static ConfigEntry SoloCartGuardEnabled; internal static ConfigEntry SoloCartGuardOnlyWhenAway; internal static ConfigEntry SoloCartGuardCartOnly; internal static ConfigEntry SoloCartGuardWorksInMultiplayer; internal static ConfigEntry SoloCartGuardLingerSeconds; internal static ConfigEntry SoloCartGuardCartTouchDistance; 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_04e2: Unknown result type (might be due to invalid IL or missing references) //IL_04ec: Expected O, but got Unknown //IL_0514: Unknown result type (might be due to invalid IL or missing references) //IL_051e: Expected O, but got Unknown //IL_0591: Unknown result type (might be due to invalid IL or missing references) //IL_059b: Expected O, but got Unknown //IL_05ce: Unknown result type (might be due to invalid IL or missing references) //IL_05d8: Expected O, but got Unknown //IL_060b: Unknown result type (might be due to invalid IL or missing references) //IL_0615: Expected O, but got Unknown //IL_0648: Unknown result type (might be due to invalid IL or missing references) //IL_0652: Expected O, but got Unknown //IL_06ba: Unknown result type (might be due to invalid IL or missing references) //IL_06c4: Expected O, but got Unknown //IL_06ef: Unknown result type (might be due to invalid IL or missing references) //IL_06f9: Expected O, but got Unknown //IL_072c: Unknown result type (might be due to invalid IL or missing references) //IL_0736: Expected O, but got Unknown //IL_0769: Unknown result type (might be due to invalid IL or missing references) //IL_0773: Expected O, but got Unknown //IL_07a6: Unknown result type (might be due to invalid IL or missing references) //IL_07b0: Expected O, but got Unknown //IL_08a3: Unknown result type (might be due to invalid IL or missing references) //IL_08ad: Expected O, but got Unknown //IL_08e0: Unknown result type (might be due to invalid IL or missing references) //IL_08ea: Expected O, but got Unknown //IL_08f5: Unknown result type (might be due to invalid IL or missing references) //IL_08ff: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"Let me Solo Them v0.7.0 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())); SoloStrengthEnabled = ((BaseUnityPlugin)this).Config.Bind("Solo Strength", "Enabled", true, "Master toggle for the Solo Strength grant system. When false, no Strength upgrade levels are granted on level start."); SoloStrengthStartingStrength = ((BaseUnityPlugin)this).Config.Bind("Solo Strength", "StartingStrength", 3, new ConfigDescription("Strength upgrade levels granted ONCE on the first level of each new run. Each level = +0.2 grab strength (vanilla math). 3 (default) = +0.6 grab strength bonus at run start. 0 = disable the run-start grant. Range 0-10.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 10), Array.Empty())); SoloStrengthPerRound = ((BaseUnityPlugin)this).Config.Bind("Solo Strength", "StrengthPerRound", 0, new ConfigDescription("Strength upgrade levels granted on EACH subsequent level start (after level 1). 0 (default) = front-loaded grant only, no per-round drip. 1+ = steady accumulation across the run. Range 0-10.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 10), Array.Empty())); SoloStrengthWorksInMultiplayer = ((BaseUnityPlugin)this).Config.Bind("Solo Strength", "WorksInMultiplayer", false, "When false (default), the grant only fires in true solo (Photon room player count <= 1). When true, the host (master client) also gets the grant in MP lobbies. Default false because Strength as a personal-stat buff doesn't fit the mod's solo-rebalance theme when teammates can share carrying duty."); SoloEnemyEnabled = ((BaseUnityPlugin)this).Config.Bind("Solo Enemy Awareness", "Enabled", true, "Master toggle for Solo Enemy Awareness. When false, enemy detection is left at vanilla values."); SoloEnemyDetectionSolo = ((BaseUnityPlugin)this).Config.Bind("Solo Enemy Awareness", "DetectionSolo", 0.5f, new ConfigDescription("Detection intensity when 1 player is in the run. 1.0 = vanilla; lower = enemies detect you slower and from shorter range (vision-cone range scaled down, more consecutive sightings needed before they aggro); 0.0 = enemies effectively cannot spot you via their vision cone. 0.5 (default) is roughly half as easily detected. Point-blank close-range detection is deliberately left intact.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); SoloEnemyDetectionDuo = ((BaseUnityPlugin)this).Config.Bind("Solo Enemy Awareness", "DetectionDuo", 0.75f, new ConfigDescription("Detection intensity when 2 players are in the run. 0.75 (default) = mild dampening. 1.0 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); SoloEnemyDetectionTrio = ((BaseUnityPlugin)this).Config.Bind("Solo Enemy Awareness", "DetectionTrio", 0.9f, new ConfigDescription("Detection intensity when 3 players are in the run. 0.9 (default) = slight dampening. 1.0 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); SoloEnemyDetectionQuad = ((BaseUnityPlugin)this).Config.Bind("Solo Enemy Awareness", "DetectionQuad", 1f, new ConfigDescription("Detection intensity when 4 or more players are in the run. 1.0 (default) = exact vanilla — the feature no-ops in full lobbies.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); SoloCarryEscapeEnabled = ((BaseUnityPlugin)this).Config.Bind("Solo Carry Escape", "Enabled", true, "Master toggle for Solo Carry Escape. When false, enemies that carry/lockdown the player (Hidden, Oogly, Spinny, HeartHugger gas, Spewer) keep their full vanilla carry duration."); SoloCarryEscapeWorksInMultiplayer = ((BaseUnityPlugin)this).Config.Bind("Solo Carry Escape", "WorksInMultiplayer", false, "When false (default): only fires in solo or when all other players are dead — preserves MP rescue gameplay. When true: fires in all lobbies."); SoloCarryEscapeStrugglePresses = ((BaseUnityPlugin)this).Config.Bind("Solo Carry Escape", "EscapeStrugglePresses", 8, new ConfigDescription("How many struggle inputs (Space, WASD, or mouse click) needed to break free. Lower = easier escape. There is no passive timer — you must actively struggle to escape a carry.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), Array.Empty())); SoloCarryEscapeAttackPressDamage = ((BaseUnityPlugin)this).Config.Bind("Solo Carry Escape", "AttackPressDamage", 5, new ConfigDescription("Damage dealt to the carrying enemy per mouse-click press during carry. 0 = disabled (attack-press counts as struggle but deals no damage). Tuned so mashing to escape (EscapeStrugglePresses clicks) chips the carrier without auto-killing a healthy one. Damage applies via the vanilla EnemyHealth.Hurt path.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 999), Array.Empty())); SoloCarryEscapeDeaggroFreezeSeconds = ((BaseUnityPlugin)this).Config.Bind("Solo Carry Escape", "DeaggroFreezeSeconds", 2f, new ConfigDescription("After escape, the carrier is frozen in place for this many seconds (vanilla Enemy.Freeze). 0 = no freeze.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); SoloCarryEscapeDeaggroChaseDisableSeconds = ((BaseUnityPlugin)this).Config.Bind("Solo Carry Escape", "DeaggroChaseDisableSeconds", 8f, new ConfigDescription("After escape, the carrier's chase AI is disabled for this many seconds (vanilla Enemy.DisableChase). 0 = no chase suppression — carrier may immediately re-engage. Default 8s gives you time to relocate.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); SoloCarryEscapeStruggleInputDebounceSeconds = ((BaseUnityPlugin)this).Config.Bind("Solo Carry Escape", "StruggleInputDebounceSeconds", 0.1f, new ConfigDescription("Minimum seconds between counted struggle presses. Prevents auto-fire keyboards from completing the escape in one frame.", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 1f), Array.Empty())); SoloExtractionEnabled = ((BaseUnityPlugin)this).Config.Bind("Solo Extraction Relief", "Enabled", true, "Master toggle for Solo Extraction Relief. When true (default), suppress the repeating 'pathfind directly to YOUR room' broadcasts that vanilla fires at every nearby enemy after the FINAL extraction — so enemies stop being omnisciently herded onto a lone player during the escape to the truck. The initial ~10s lure that pulls enemies toward the truck/start room is left intact, and enemies still react to your NOISE (gunshots, dropped/bumped loot), so the escape stays tense — it just isn't omniscient. False = full vanilla pinging."); SoloExtractionWorksInMultiplayer = ((BaseUnityPlugin)this).Config.Bind("Solo Extraction Relief", "WorksInMultiplayer", false, "When false (default), relief only applies in true solo (player count <= 1). When true, the host (master client) also gets relief in MP lobbies."); SoloCartGuardEnabled = ((BaseUnityPlugin)this).Config.Bind("Solo Cart Guard", "Enabled", true, "Master toggle for Solo Cart Guard. When true (default), enemies cannot destroy or chip the value of your valuables while the guard conditions are met (see OnlyWhenAway). Your own drops, throws, and bumps are unaffected. False = full vanilla."); SoloCartGuardOnlyWhenAway = ((BaseUnityPlugin)this).Config.Bind("Solo Cart Guard", "OnlyWhenAway", true, "When true (default), loot is only protected from enemies while you are AWAY from your cart — when you're at the cart (within CartTouchDistance) you defend it yourself, so protection powers down and turns off. When false, loot is protected from enemies the whole time you're solo, regardless of where you are."); SoloCartGuardCartOnly = ((BaseUnityPlugin)this).Config.Bind("Solo Cart Guard", "CartOnly", false, "When false (default), ALL your valuables are protected (cart loot AND dropped/staged valuables anywhere). When true, only valuables currently loaded in a cart are protected; loose loot lying in a room is left vanilla."); SoloCartGuardWorksInMultiplayer = ((BaseUnityPlugin)this).Config.Bind("Solo Cart Guard", "WorksInMultiplayer", false, "When false (default), the guard only applies in true solo (player count <= 1). When true, the host (master client) also gets it in MP lobbies (the 'present' check keys off the host's position)."); SoloCartGuardLingerSeconds = ((BaseUnityPlugin)this).Config.Bind("Solo Cart Guard", "LingerSeconds", 3f, new ConfigDescription("Only used when OnlyWhenAway=true. When you arrive back at the cart, protection stays ON for this many seconds (shown as a 'Powering Down' countdown) before it turns off and hands defense back to you. 0 = protection turns off the instant you reach the cart.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 30f), Array.Empty())); SoloCartGuardCartTouchDistance = ((BaseUnityPlugin)this).Config.Bind("Solo Cart Guard", "CartTouchDistance", 5f, new ConfigDescription("Radius (meters) around the cart that counts as being 'at the cart'. Two roles: (1) the guard stays OFF at level start until you first come this close to a cart (then it arms for the level); (2) while you're within this radius of a cart you're considered present, so protection powers down and turns off. Larger = a wider zone around the cart counts as 'present'.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 15f), 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"); internal static readonly FieldRef HiddenPlayerTarget = AccessTools.FieldRefAccess("playerTarget"); internal static readonly FieldRef OoglyGrabbedPlayer = AccessTools.FieldRefAccess("grabbedPlayer"); internal static readonly FieldRef SpinnyPlayerTarget = AccessTools.FieldRefAccess("playerTarget"); internal static readonly FieldRef SlowMouthPlayerTarget = AccessTools.FieldRefAccess("playerTarget"); internal static readonly FieldRef EnemyHealthRef = AccessTools.FieldRefAccess("Health"); internal static readonly FieldRef> GasCheckerPlayersColliding = AccessTools.FieldRefAccess>("playersColliding"); internal static readonly FieldRef GasCheckerHeartHugger = AccessTools.FieldRefAccess("enemyHeartHugger"); internal static readonly FieldRef TumbleIsTumbling = AccessTools.FieldRefAccess("isTumbling"); } 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 4: 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 enum EnemyKind { None, Hidden, Oogly, Spinny, HeartHuggerGas, Upscream, Spewer } [HarmonyPatch(typeof(PlayerTumble), "TumbleRequest")] public static class PlayerTumbleRequestPatch { [HarmonyPostfix] public static void Postfix(PlayerTumble __instance, bool _isTumbling, bool _playerInput) { try { if (Plugin.SoloCarryEscapeEnabled.Value && !(!_isTumbling || _playerInput) && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.playerAvatar == (Object)null) && RepoRefs.AvatarIsLocal.Invoke(__instance.playerAvatar) && ShouldTrigger()) { CarryEscapeTracker.OnCarryStart(__instance.playerAvatar); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[CarryEscape] Postfix threw: " + ex.GetType().Name + ": " + ex.Message)); } } internal static bool ShouldTrigger() { if (PhotonNetwork.CurrentRoom == null || PhotonNetwork.CurrentRoom.PlayerCount <= 1) { return true; } if (Plugin.SoloCarryEscapeWorksInMultiplayer.Value) { return true; } 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; } } internal static class CarryEscapeTracker { private static Enemy _currentEnemy; private static EnemyKind _currentKind = EnemyKind.None; private static PlayerAvatar _localPlayer; private static int _strugglePresses; private static float _lastInputTime; private static float _lastNoCarrierLogTime = -999f; private static bool IsArmed => _currentKind != EnemyKind.None; public static void OnCarryStart(PlayerAvatar local) { try { var (val, enemyKind) = EnemyKindIdentifier.IdentifyCarrier(local); if (enemyKind == EnemyKind.None || (Object)(object)val == (Object)null) { if (Time.time - _lastNoCarrierLogTime >= 1f) { Plugin.Log.LogDebug((object)"[CarryEscape] OnCarryStart fired but no carry enemy found (likely fall or PhysGrabber tumble) — ignoring."); _lastNoCarrierLogTime = Time.time; } } else if (!IsArmed || val != _currentEnemy) { Arm(val, enemyKind, local); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[CarryEscape] OnCarryStart threw: " + ex.GetType().Name + ": " + ex.Message)); ClearState(); } } public static void TryDetectGasCarry() { if (IsArmed || !Plugin.SoloCarryEscapeEnabled.Value) { return; } try { PlayerController instance = PlayerController.instance; PlayerAvatar val = (((Object)(object)instance != (Object)null) ? instance.playerAvatarScript : null); if ((Object)(object)val == (Object)null || !RepoRefs.AvatarIsLocal.Invoke(val)) { return; } PlayerTumble val2 = RepoRefs.AvatarTumble.Invoke(val); if ((Object)(object)val2 == (Object)null || !RepoRefs.TumbleIsTumbling.Invoke(val2) || !PlayerTumbleRequestPatch.ShouldTrigger()) { return; } EnemyHeartHuggerGasChecker[] array = Object.FindObjectsOfType(); foreach (EnemyHeartHuggerGasChecker val3 in array) { if ((Object)(object)val3 == (Object)null) { continue; } List list = RepoRefs.GasCheckerPlayersColliding.Invoke(val3); EnemyHeartHugger val4 = RepoRefs.GasCheckerHeartHugger.Invoke(val3); if (list != null && !((Object)(object)val4 == (Object)null) && list.Contains(val)) { Enemy componentInParent = ((Component)val4).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null)) { Arm(componentInParent, EnemyKind.HeartHuggerGas, val); break; } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[CarryEscape] TryDetectGasCarry threw: " + ex.GetType().Name + ": " + ex.Message)); } } private static void Arm(Enemy enemy, EnemyKind kind, PlayerAvatar local) { _currentEnemy = enemy; _currentKind = kind; _localPlayer = local; _strugglePresses = 0; _lastInputTime = 0f; Plugin.Log.LogDebug((object)$"[CarryEscape] armed: enemy={kind}, threshold={Plugin.SoloCarryEscapeStrugglePresses.Value} presses (struggle to escape)"); } public static void TryOnTick() { if (!IsArmed || !Plugin.SoloCarryEscapeEnabled.Value) { return; } try { PlayerTumble val = (((Object)(object)_localPlayer != (Object)null) ? RepoRefs.AvatarTumble.Invoke(_localPlayer) : null); if ((Object)(object)_localPlayer == (Object)null || (Object)(object)val == (Object)null || !RepoRefs.TumbleIsTumbling.Invoke(val)) { Plugin.Log.LogDebug((object)"[CarryEscape] tumble ended naturally — clearing state."); ClearState(); return; } InputProbeResult inputProbeResult = InputProbe.Sample(ref _lastInputTime); if (inputProbeResult.Struggled) { _strugglePresses++; Plugin.Log.LogDebug((object)$"[CarryEscape] struggle press #{_strugglePresses}/{Plugin.SoloCarryEscapeStrugglePresses.Value} (wasAttack={inputProbeResult.WasAttack})"); } if (inputProbeResult.WasAttack && Plugin.SoloCarryEscapeAttackPressDamage.Value > 0) { ApplyAttackDamage(); } if (_strugglePresses >= Plugin.SoloCarryEscapeStrugglePresses.Value) { ResolveEscape(); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[CarryEscape] TryOnTick threw: " + ex.GetType().Name + ": " + ex.Message)); ClearState(); } } private static void ApplyAttackDamage() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_currentEnemy == (Object)null) { return; } try { EnemyHealth val = RepoRefs.EnemyHealthRef.Invoke(_currentEnemy); if (!((Object)(object)val == (Object)null)) { val.Hurt(Plugin.SoloCarryEscapeAttackPressDamage.Value, Vector3.zero); Plugin.Log.LogDebug((object)$"[CarryEscape] applied {Plugin.SoloCarryEscapeAttackPressDamage.Value} damage to {_currentKind}"); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[CarryEscape] ApplyAttackDamage threw: " + ex.GetType().Name + ": " + ex.Message)); } } private static void ResolveEscape() { EnemyKind currentKind = _currentKind; int strugglePresses = _strugglePresses; try { ForceLeaveHelpers.ForceLeave(_currentEnemy, _currentKind); if ((Object)(object)_localPlayer != (Object)null) { PlayerTumble val = RepoRefs.AvatarTumble.Invoke(_localPlayer); if ((Object)(object)val != (Object)null) { val.TumbleOverrideTime(0f); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[CarryEscape] ResolveEscape force-leave threw: " + ex.GetType().Name + ": " + ex.Message)); } ClearState(); Plugin.Log.LogDebug((object)$"[CarryEscape] escaped: reason=StruggleThreshold, enemy={currentKind}, presses={strugglePresses}"); } private static void ClearState() { _currentEnemy = null; _currentKind = EnemyKind.None; _localPlayer = null; _strugglePresses = 0; _lastInputTime = 0f; } } internal static class EnemyKindIdentifier { public static (Enemy enemy, EnemyKind kind) IdentifyCarrier(PlayerAvatar local) { if ((Object)(object)local == (Object)null) { return (null, EnemyKind.None); } EnemyHidden[] array = Object.FindObjectsOfType(); foreach (EnemyHidden val in array) { if ((Object)(object)val == (Object)null) { continue; } try { if ((Object)(object)RepoRefs.HiddenPlayerTarget.Invoke(val) == (Object)(object)local) { Enemy val2 = GetEnemyParent((Component)(object)val); if ((Object)(object)val2 != (Object)null) { return (val2, EnemyKind.Hidden); } } } catch { } } EnemyOogly[] array2 = Object.FindObjectsOfType(); foreach (EnemyOogly val3 in array2) { if ((Object)(object)val3 == (Object)null) { continue; } try { if ((Object)(object)RepoRefs.OoglyGrabbedPlayer.Invoke(val3) == (Object)(object)local) { Enemy val4 = GetEnemyParent((Component)(object)val3); if ((Object)(object)val4 != (Object)null) { return (val4, EnemyKind.Oogly); } } } catch { } } EnemySpinny[] array3 = Object.FindObjectsOfType(); foreach (EnemySpinny val5 in array3) { if ((Object)(object)val5 == (Object)null) { continue; } try { if ((Object)(object)RepoRefs.SpinnyPlayerTarget.Invoke(val5) == (Object)(object)local) { Enemy val6 = GetEnemyParent((Component)(object)val5); if ((Object)(object)val6 != (Object)null) { return (val6, EnemyKind.Spinny); } } } catch { } } EnemyHeartHuggerGasChecker[] array4 = Object.FindObjectsOfType(); foreach (EnemyHeartHuggerGasChecker val7 in array4) { if ((Object)(object)val7 == (Object)null) { continue; } try { List list = RepoRefs.GasCheckerPlayersColliding.Invoke(val7); EnemyHeartHugger val8 = RepoRefs.GasCheckerHeartHugger.Invoke(val7); if (list != null && list.Contains(local) && (Object)(object)val8 != (Object)null) { Enemy val9 = GetEnemyParent((Component)(object)val8); if ((Object)(object)val9 != (Object)null) { return (val9, EnemyKind.HeartHuggerGas); } } } catch { } } EnemySlowMouth[] array5 = Object.FindObjectsOfType(); foreach (EnemySlowMouth val10 in array5) { if ((Object)(object)val10 == (Object)null) { continue; } try { if ((Object)(object)RepoRefs.SlowMouthPlayerTarget.Invoke(val10) == (Object)(object)local) { Enemy val11 = GetEnemyParent((Component)(object)val10); if ((Object)(object)val11 != (Object)null) { return (val11, EnemyKind.Spewer); } } } catch { } } return (null, EnemyKind.None); static Enemy GetEnemyParent(Component c) { if (!((Object)(object)c == (Object)null)) { return c.GetComponentInParent(); } return null; } } } internal static class ForceLeaveHelpers { public static void ForceLeave(Enemy enemy, EnemyKind kind) { if ((Object)(object)enemy == (Object)null) { return; } try { switch (kind) { case EnemyKind.Hidden: ForceHiddenLeave(enemy); break; case EnemyKind.Oogly: ForceOoglyLeave(enemy); break; case EnemyKind.Spinny: ForceSpinnyLeave(enemy); break; case EnemyKind.HeartHuggerGas: ForceHeartHuggerGasLeave(enemy); break; case EnemyKind.Upscream: ForceUpscreamLeave(enemy); break; case EnemyKind.Spewer: ForceSpewerLeave(enemy); break; } float value = Plugin.SoloCarryEscapeDeaggroFreezeSeconds.Value; float value2 = Plugin.SoloCarryEscapeDeaggroChaseDisableSeconds.Value; if (value > 0f) { enemy.Freeze(value); } if (value2 > 0f) { enemy.DisableChase(value2); } } catch (Exception ex) { Plugin.Log.LogWarning((object)$"[CarryEscape] ForceLeave({kind}) threw: {ex.GetType().Name}: {ex.Message}"); } } private static void ForceHiddenLeave(Enemy enemy) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) EnemyHidden componentInChildren = ((Component)enemy).GetComponentInChildren(); if ((Object)(object)componentInChildren == (Object)null) { Plugin.Log.LogDebug((object)"[CarryEscape] Hidden component not found on enemy"); return; } componentInChildren.currentState = (State)10; RepoRefs.HiddenPlayerTarget.Invoke(componentInChildren) = null; } private static void ForceOoglyLeave(Enemy enemy) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) EnemyOogly componentInChildren = ((Component)enemy).GetComponentInChildren(); if ((Object)(object)componentInChildren == (Object)null) { Plugin.Log.LogDebug((object)"[CarryEscape] Oogly component not found on enemy"); return; } componentInChildren.currentState = (State)2; RepoRefs.OoglyGrabbedPlayer.Invoke(componentInChildren) = null; } private static void ForceSpinnyLeave(Enemy enemy) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) EnemySpinny componentInChildren = ((Component)enemy).GetComponentInChildren(); if ((Object)(object)componentInChildren == (Object)null) { Plugin.Log.LogDebug((object)"[CarryEscape] Spinny component not found on enemy"); return; } componentInChildren.currentState = (State)6; RepoRefs.SpinnyPlayerTarget.Invoke(componentInChildren) = null; } private static void ForceHeartHuggerGasLeave(Enemy enemy) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) EnemyHeartHugger componentInChildren = ((Component)enemy).GetComponentInChildren(); if ((Object)(object)componentInChildren == (Object)null) { Plugin.Log.LogDebug((object)"[CarryEscape] HeartHugger component not found on enemy"); } else { componentInChildren.currentState = (State)11; } } private static void ForceUpscreamLeave(Enemy _enemy) { Plugin.Log.LogDebug((object)"[CarryEscape] ForceUpscreamLeave called — no force-state available (animation-driven tumble); deaggro pass will still apply."); } private static void ForceSpewerLeave(Enemy enemy) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) EnemySlowMouth componentInChildren = ((Component)enemy).GetComponentInChildren(); if ((Object)(object)componentInChildren == (Object)null) { Plugin.Log.LogDebug((object)"[CarryEscape] SlowMouth component not found on enemy"); return; } componentInChildren.currentState = (State)6; RepoRefs.SlowMouthPlayerTarget.Invoke(componentInChildren) = null; } } internal struct InputProbeResult { public bool Struggled; public bool WasAttack; } internal static class InputProbe { public static InputProbeResult Sample(ref float lastInputTime) { InputProbeResult result = default(InputProbeResult); float value = Plugin.SoloCarryEscapeStruggleInputDebounceSeconds.Value; if (Time.time - lastInputTime < value) { return result; } if (Input.GetMouseButtonDown(0)) { result.Struggled = true; result.WasAttack = true; lastInputTime = Time.time; return result; } if (Input.GetKeyDown((KeyCode)32) || Input.GetKeyDown((KeyCode)119) || Input.GetKeyDown((KeyCode)97) || Input.GetKeyDown((KeyCode)115) || Input.GetKeyDown((KeyCode)100)) { result.Struggled = true; lastInputTime = Time.time; } return result; } } [HarmonyPatch] public static class SoloCartGuardPatch { private static readonly FieldRef ValuableObjectRef = AccessTools.FieldRefAccess("valuableObject"); private static readonly FieldRef CurrentCartRef = AccessTools.FieldRefAccess("currentCart"); private static bool _suppressLogged; internal static bool SuppressionPulse; private const float NearScanInterval = 0.25f; private const float NearHysteresis = 0.5f; private static bool _protectByDistance = true; private static float _lingerRemaining; private static bool _present; private static bool _hasLeftSinceArm; private static float _nearScanTimer; private static bool _cartTouchedThisLevel; internal static bool ProtectingByDistance => _protectByDistance; internal static bool PresentAtCart => _present; internal static float LingerRemaining => _lingerRemaining; internal static void Tick(float dt) { try { if (!Plugin.SoloCartGuardEnabled.Value || !SemiFunc.RunIsLevel() || !IsSoloOrAuthorizedHost()) { _cartTouchedThisLevel = false; _protectByDistance = true; _lingerRemaining = Plugin.SoloCartGuardLingerSeconds.Value; _present = false; _hasLeftSinceArm = false; return; } _nearScanTimer -= dt; bool flag = _nearScanTimer <= 0f; if (flag) { _nearScanTimer = 0.25f; } if (!_cartTouchedThisLevel && flag && LocalPlayerTouchingCart()) { _cartTouchedThisLevel = true; } if (!Plugin.SoloCartGuardOnlyWhenAway.Value) { _protectByDistance = true; _lingerRemaining = Plugin.SoloCartGuardLingerSeconds.Value; _present = false; return; } if (flag) { float num = NearestCartDistanceSq(); if (num != float.MaxValue) { float value = Plugin.SoloCartGuardCartTouchDistance.Value; float num2 = value * value; float num3 = value + 0.5f; float num4 = num3 * num3; if (_present) { if (num > num4) { _present = false; } } else if (num <= num2) { _present = true; } } } if (_cartTouchedThisLevel && !_present) { _hasLeftSinceArm = true; } if (_present && _hasLeftSinceArm) { if (_lingerRemaining > 0f) { _lingerRemaining -= dt; _protectByDistance = true; } else { _protectByDistance = false; } } else if (_present) { _protectByDistance = false; } else { _protectByDistance = true; _lingerRemaining = Plugin.SoloCartGuardLingerSeconds.Value; } } catch { _protectByDistance = true; } } private static bool IsSoloOrAuthorizedHost() { if (Plugin.SoloCartGuardWorksInMultiplayer.Value) { return SemiFunc.IsMasterClientOrSingleplayer(); } return (SemiFunc.PlayerGetList()?.Count ?? 1) <= 1; } internal static void OnGuardArmed() { _suppressLogged = false; } private static float NearestCartDistanceSq() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) try { PlayerController instance = PlayerController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerAvatarScript == (Object)null) { return float.MaxValue; } Vector3 position = ((Component)instance.playerAvatarScript).transform.position; float num = float.MaxValue; PhysGrabCart[] array = Object.FindObjectsOfType(); foreach (PhysGrabCart val in array) { if (!((Object)(object)val == (Object)null)) { Vector3 val2 = ((Component)val).transform.position - position; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; } } } return num; } catch { return float.MaxValue; } } private static bool LocalPlayerTouchingCart() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) try { PlayerController instance = PlayerController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerAvatarScript == (Object)null) { return false; } Vector3 position = ((Component)instance.playerAvatarScript).transform.position; float value = Plugin.SoloCartGuardCartTouchDistance.Value; float num = value * value; PhysGrabCart[] array = Object.FindObjectsOfType(); foreach (PhysGrabCart val in array) { if (!((Object)(object)val == (Object)null)) { Vector3 val2 = ((Component)val).transform.position - position; if (((Vector3)(ref val2)).sqrMagnitude <= num) { return true; } } } return false; } catch { return false; } } private static bool GuardConditions(PhysGrabObjectImpactDetector detector) { if (!Plugin.SoloCartGuardEnabled.Value) { return false; } if (!_cartTouchedThisLevel) { return false; } if ((Object)(object)detector == (Object)null) { return false; } if ((Object)(object)ValuableObjectRef.Invoke(detector) == (Object)null) { return false; } if (!IsSoloOrAuthorizedHost()) { return false; } if (Plugin.SoloCartGuardCartOnly.Value && (Object)(object)CurrentCartRef.Invoke(detector) == (Object)null) { return false; } if (Plugin.SoloCartGuardOnlyWhenAway.Value && !_protectByDistance) { return false; } return true; } internal static bool GuardArmedForHud() { try { if (!Plugin.SoloCartGuardEnabled.Value) { return false; } if (!SemiFunc.RunIsLevel()) { return false; } if (!IsSoloOrAuthorizedHost()) { return false; } return _cartTouchedThisLevel; } catch { return false; } } [HarmonyPatch(typeof(HurtCollider), "PhysObjectHurt")] [HarmonyPrefix] public static bool PhysObjectHurtPrefix(HurtCollider __instance, PhysGrabObject physGrabObject) { try { if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.enemyHost == (Object)null) { return true; } if ((Object)(object)physGrabObject == (Object)null) { return true; } PhysGrabObjectImpactDetector component = ((Component)physGrabObject).GetComponent(); if ((Object)(object)component == (Object)null) { return true; } if (!GuardConditions(component)) { return true; } SuppressionPulse = true; if (!_suppressLogged) { _suppressLogged = true; Plugin.Log.LogDebug((object)"[SoloCartGuard] suppressing enemy attack on valuable (no damage) — first this arm"); } return false; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SoloCartGuard] PhysObjectHurt prefix threw: " + ex.GetType().Name + ": " + ex.Message)); return true; } } [HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "Break")] [HarmonyPrefix] public static bool BreakPrefix(PhysGrabObjectImpactDetector __instance) { try { if (!GuardConditions(__instance)) { return true; } PhysGrabObject component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null || component.enemyInteractTimer <= 0f) { return true; } SuppressionPulse = true; if (!_suppressLogged) { _suppressLogged = true; Plugin.Log.LogDebug((object)"[SoloCartGuard] suppressing enemy-caused valuable break (first this arm)"); } return false; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SoloCartGuard] Break prefix threw: " + ex.GetType().Name + ": " + ex.Message)); return true; } } } [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}"); } } } } [HarmonyPatch(typeof(Enemy), "Awake")] public static class SoloEnemyAwarenessPatch { private const float MultEpsilon = 0.001f; private const int InverseZeroSentinel = 9999; [HarmonyPostfix] public static void Postfix(Enemy __instance) { try { if (!Plugin.SoloEnemyEnabled.Value || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } int num = SemiFunc.PlayerGetList()?.Count ?? 1; if (num < 1) { num = 1; } float num2 = num switch { 1 => Plugin.SoloEnemyDetectionSolo.Value, 2 => Plugin.SoloEnemyDetectionDuo.Value, 3 => Plugin.SoloEnemyDetectionTrio.Value, _ => Plugin.SoloEnemyDetectionQuad.Value, }; if (!Mathf.Approximately(num2, 1f)) { EnemyVision component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null)) { float visionDistance = component.VisionDistance; int visionsToTrigger = component.VisionsToTrigger; int visionsToTriggerCrouch = component.VisionsToTriggerCrouch; int visionsToTriggerCrawl = component.VisionsToTriggerCrawl; component.VisionDistance = visionDistance * num2; component.VisionsToTrigger = ScaleInverse(visionsToTrigger, num2); component.VisionsToTriggerCrouch = ScaleInverse(visionsToTriggerCrouch, num2); component.VisionsToTriggerCrawl = ScaleInverse(visionsToTriggerCrawl, num2); Transform parent = ((Component)__instance).transform.parent; string arg = (((Object)(object)parent != (Object)null) ? ((Object)parent).name : ((Object)((Component)__instance).gameObject).name); Plugin.Log.LogDebug((object)($"[SoloEnemyAwareness] {arg} players={num} det={num2:F2} | " + $"VisionDistance {visionDistance:F1}->{component.VisionDistance:F1}, " + $"VisionsToTrigger {visionsToTrigger}->{component.VisionsToTrigger} " + $"{visionsToTriggerCrouch}->{component.VisionsToTriggerCrouch} {visionsToTriggerCrawl}->{component.VisionsToTriggerCrawl}")); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SoloEnemyAwareness] Postfix threw: " + ex.GetType().Name + ": " + ex.Message)); } } private static int ScaleInverse(int original, float mult) { if (mult <= 0.001f) { return 9999; } return Mathf.Max(1, Mathf.RoundToInt((float)original / mult)); } } [HarmonyPatch] public static class SoloExtractionReliefPatch { private static readonly FieldRef AllExtractionsDoneRef = AccessTools.FieldRefAccess("allExtractionPointsCompleted"); private static readonly FieldRef ExtractionStateRef = AccessTools.FieldRefAccess("extractionsDoneState"); private const float ExtractionPingRangeMax = 150f; private static bool _pingSuppressLogged; internal static void OnReliefArmed() { _pingSuppressLogged = false; } internal static bool ReliefActive() { try { if (!Plugin.SoloExtractionEnabled.Value) { return false; } RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance == (Object)null || !AllExtractionsDoneRef.Invoke(instance)) { return false; } if (Plugin.SoloExtractionWorksInMultiplayer.Value) { return SemiFunc.IsMasterClientOrSingleplayer(); } return (SemiFunc.PlayerGetList()?.Count ?? 1) <= 1; } catch { return false; } } [HarmonyPatch(typeof(EnemyDirector), "SetInvestigate")] [HarmonyPrefix] public static bool SetInvestigatePrefix(EnemyDirector __instance, float radius, bool pathfindOnly) { try { if ((Object)(object)__instance == (Object)null) { return true; } if (!pathfindOnly) { return true; } if (radius > 150f) { return true; } if (!ReliefActive()) { return true; } if ((int)ExtractionStateRef.Invoke(__instance) != 1) { return true; } if (!_pingSuppressLogged) { _pingSuppressLogged = true; Plugin.Log.LogDebug((object)"[SoloExtraction] suppressing PlayerRoom investigate ping(s) — first this arm"); } return false; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SoloExtraction] SetInvestigate prefix threw: " + ex.GetType().Name + ": " + ex.Message)); return true; } } } public static class SoloStrengthGranter { internal const int NoLevelGrantedYet = -1; internal static int _lastGrantedLevel = -1; public static void TryGrantOnTick() { if (!Plugin.SoloStrengthEnabled.Value || !SemiFunc.RunIsLevel() || (Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } if (!Plugin.SoloStrengthWorksInMultiplayer.Value) { List list = SemiFunc.PlayerGetList(); if (list != null && list.Count > 1) { return; } } if ((Object)(object)RunManager.instance == (Object)null) { return; } int levelsCompleted = RunManager.instance.levelsCompleted; if (levelsCompleted == _lastGrantedLevel) { return; } bool flag = levelsCompleted == 0; int num = (flag ? Plugin.SoloStrengthStartingStrength.Value : Plugin.SoloStrengthPerRound.Value); if (num <= 0) { _lastGrantedLevel = levelsCompleted; Plugin.Log.LogDebug((object)$"[SoloStrength] Skipped grant (amount=0, runStart={flag})"); return; } PlayerController instance = PlayerController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.playerAvatarScript == (Object)null) { return; } string text = SemiFunc.PlayerGetSteamID(instance.playerAvatarScript); if (string.IsNullOrEmpty(text) || (Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { return; } try { if (!StatsManager.instance.playerUpgradeStrength.ContainsKey(text)) { StatsManager.instance.playerUpgradeStrength[text] = 0; } PunManager.instance.UpgradePlayerGrabStrength(text, num); _lastGrantedLevel = levelsCompleted; int num2 = StatsManager.instance.playerUpgradeStrength[text]; Plugin.Log.LogDebug((object)$"[SoloStrength] Granted +{num} Strength to {text} (runStart={flag}, newTotal={num2})"); } catch (Exception ex) { _lastGrantedLevel = levelsCompleted; Plugin.Log.LogWarning((object)("[SoloStrength] Grant threw: " + ex.GetType().Name + ": " + ex.Message)); return; } try { if ((Object)(object)StatsUI.instance != (Object)null) { StatsUI.instance.Fetch(); StatsUI.instance.ShowStats(); } else { Plugin.Log.LogWarning((object)"[SoloStrength] StatsUI.instance null — grant applied but no panel shown"); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[SoloStrength] StatsUI display threw: " + ex2.GetType().Name + ": " + ex2.Message)); } } } [HarmonyPatch(typeof(RunManager), "ResetProgress")] internal static class RunManagerResetProgressPatch { [HarmonyPostfix] private static void Postfix() { SoloStrengthGranter._lastGrantedLevel = -1; Plugin.Log.LogDebug((object)"[SoloStrength] ResetProgress: cleared _lastGrantedLevel"); } } 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(((PrefabRef)(object)item.prefab).ResourcePath, pos, rot, (byte)0, (object[])null) : Object.Instantiate(((PrefabRef)(object)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.LogDebug((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; 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_02a9: 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_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016a: 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) { return; } if ((Object)(object)_cachedSwordItem == (Object)null) { _cachedSwordItem = SoloGrantHelper.FindItemByKey("sword"); if ((Object)(object)_cachedSwordItem == (Object)null) { if ((Object)(object)StatsManager.instance != (Object)null && StatsManager.instance.itemDictionary != null && StatsManager.instance.itemDictionary.Count > 0) { Plugin.Log.LogWarning((object)"[SoloSword] No sword found in itemDictionary; skipping grant"); _permanentGiveup = true; } return; } Plugin.Log.LogInfo((object)("[SoloSword] Matched sword item — name='" + ((Object)_cachedSwordItem).name + "', itemName='" + _cachedSwordItem.itemName + "'")); } if (!SoloGrantHelper.TryGetSpawnTarget(instance2.playerAvatarScript, ref _firstWaitTime, Vector3.zero, out var pos, out var rot, out var spawnLoc)) { return; } GameObject val = SoloGrantHelper.SpawnItem(_cachedSwordItem, pos, rot, "SoloSword"); if ((Object)(object)val == (Object)null) { return; } _grantedSwordGO = val; SoloGrantHelper.AttachBlueGlow(val, 5f, 6f); try { HurtCollider componentInChildren = val.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 {pos} (spawn={spawnLoc}). 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.LogDebug((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 const float ReliefFadeSeconds = 3f; private bool _reliefActive; private float _reliefFadeRemaining; private bool _cartGuardArmed; private const float CartGuardFlashSeconds = 1.5f; private const float CartGuardPowerUpSeconds = 1f; private const float CartGuardOffShowSeconds = 2f; private float _cartGuardFlashRemaining; private float _cartGuardPowerUpRemaining; private float _cartGuardOffShowRemaining; private bool _cartGuardWasPresent; private bool _cartGuardWasPoweringDown; 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(); SoloStrengthGranter.TryGrantOnTick(); CarryEscapeTracker.TryDetectGasCarry(); CarryEscapeTracker.TryOnTick(); TrackChassisTransition(); TrackReliefTransition(); SoloCartGuardPatch.Tick(Time.deltaTime); TrackCartGuardTransition(); 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 TrackReliefTransition() { bool flag = SoloExtractionReliefPatch.ReliefActive(); if (flag && !_reliefActive) { SoloExtractionReliefPatch.OnReliefArmed(); Plugin.Log.LogDebug((object)"[SoloExtraction] ARMED — final-extraction relief active (suppressing PlayerRoom pings)"); } else if (!flag && _reliefActive) { _reliefFadeRemaining = 3f; Plugin.Log.LogDebug((object)"[SoloExtraction] DISARMED — relief ended (left level / extraction state reset)"); } _reliefActive = flag; if (_reliefFadeRemaining > 0f) { _reliefFadeRemaining -= Time.deltaTime; if (_reliefFadeRemaining < 0f) { _reliefFadeRemaining = 0f; } } } private void TrackCartGuardTransition() { bool flag = SoloCartGuardPatch.GuardArmedForHud(); if (flag && !_cartGuardArmed) { SoloCartGuardPatch.OnGuardArmed(); Plugin.Log.LogDebug((object)"[SoloCartGuard] ARMED — reached cart, guard active this level"); } else if (!flag && _cartGuardArmed) { Plugin.Log.LogDebug((object)"[SoloCartGuard] DISARMED — cart guard inactive (left level / not solo)"); } _cartGuardArmed = flag; if (SoloCartGuardPatch.SuppressionPulse) { SoloCartGuardPatch.SuppressionPulse = false; _cartGuardFlashRemaining = 1.5f; } if (_cartGuardFlashRemaining > 0f) { _cartGuardFlashRemaining -= Time.deltaTime; if (_cartGuardFlashRemaining < 0f) { _cartGuardFlashRemaining = 0f; } } bool presentAtCart = SoloCartGuardPatch.PresentAtCart; if (flag && _cartGuardWasPresent && !presentAtCart) { _cartGuardPowerUpRemaining = 1f; } _cartGuardWasPresent = presentAtCart; if (_cartGuardPowerUpRemaining > 0f) { _cartGuardPowerUpRemaining -= Time.deltaTime; if (_cartGuardPowerUpRemaining < 0f) { _cartGuardPowerUpRemaining = 0f; } } bool cartGuardWasPoweringDown = flag && presentAtCart && SoloCartGuardPatch.ProtectingByDistance && Plugin.SoloCartGuardOnlyWhenAway.Value; if (flag && presentAtCart && !SoloCartGuardPatch.ProtectingByDistance && Plugin.SoloCartGuardOnlyWhenAway.Value && _cartGuardWasPoweringDown) { _cartGuardOffShowRemaining = 2f; } _cartGuardWasPoweringDown = cartGuardWasPoweringDown; if (_cartGuardOffShowRemaining > 0f) { _cartGuardOffShowRemaining -= Time.deltaTime; if (_cartGuardOffShowRemaining < 0f) { _cartGuardOffShowRemaining = 0f; } } } 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 num = SemiFunc.PlayerGetList()?.Count ?? 1; if (num != 1) { _shouldShowGrace = false; _fadeOutRemaining = 0f; if (shouldLog) { Plugin.Log.LogDebug((object)$"[HUD] gated grace: not-solo, playerCount={num}"); } 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={num}, 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(); DrawReliefLabel(); DrawCartGuardLabel(); } } 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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, wordWrap = false }; } } 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 DrawReliefLabel() { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0106: 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) string text; Color val = default(Color); if (_reliefActive) { text = "Extraction Relief: Active"; ((Color)(ref val))..ctor(0.55f, 0.85f, 1f, 1f); } else { if (!(_reliefFadeRemaining > 0f)) { return; } text = "Extraction Relief: Active"; float num = Mathf.Clamp01(_reliefFadeRemaining / 3f); ((Color)(ref val))..ctor(0.55f, 0.85f, 1f, num); } Rect val2 = default(Rect); ((Rect)(ref val2))..ctor((float)Screen.width / 2f - 150f, 90f, 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 DrawCartGuardLabel() { //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) if (!_cartGuardArmed) { return; } bool flag = Plugin.SoloCartGuardOnlyWhenAway.Value && SoloCartGuardPatch.PresentAtCart; string text; Color val = default(Color); if (_cartGuardFlashRemaining > 0f) { text = "Cart Guard: Blocked!"; float num = Mathf.Clamp01(_cartGuardFlashRemaining / 1.5f); ((Color)(ref val))..ctor(0.45f, 1f, 0.45f, num); } else if (flag && SoloCartGuardPatch.ProtectingByDistance) { int num2 = Mathf.CeilToInt(SoloCartGuardPatch.LingerRemaining); if (num2 < 0) { num2 = 0; } text = $"Cart Guard: Powering Down: {num2}s"; ((Color)(ref val))..ctor(1f, 0.6f, 0.2f, 1f); } else if (flag) { if (_cartGuardOffShowRemaining <= 0f) { return; } float num3 = Mathf.Clamp01(_cartGuardOffShowRemaining / 2f); text = "Cart Guard: Off"; ((Color)(ref val))..ctor(1f, 0.45f, 0.45f, num3); } else if (_cartGuardPowerUpRemaining > 0f) { text = "Cart Guard: Powering Up"; ((Color)(ref val))..ctor(0.45f, 1f, 0.55f, 1f); } else { text = "Cart Guard: Active"; ((Color)(ref val))..ctor(0.55f, 0.85f, 1f, 1f); } Rect val2 = default(Rect); ((Rect)(ref val2))..ctor((float)Screen.width / 2f - 260f, 120f, 520f, 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 OnChassisGate() { if (_chassisWasDrawing) { _chassisWasDrawing = false; Plugin.Log.LogDebug((object)"[Chassis HUD] drawing label STOP"); } } } }