using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using AIGraph; using AK; using Agents; using AmorLib.Networking.StateReplicators; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using Enemies; using FX_EffectSystem; using GTFO.API; using GTFO.API.Resources; using Gear; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem.Collections.Generic; using KillableSpitters.Events; using KillableSpitters.Patches.Spitters; using Microsoft.CodeAnalysis; using Player; using PlayerCoverage; using SNetwork; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("KillableSpitters")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+6f6378c3b2735eb344f1582b3da6a9af834acf55")] [assembly: AssemblyProduct("KillableSpitters")] [assembly: AssemblyTitle("KillableSpitters")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace KillableSpitters { [BepInPlugin("the_tavern-KillableSpitters", "KillableSpitters", "0.5.0")] [BepInProcess("GTFO.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BasePlugin { public const string Version = "0.5.0"; public const string Name = "the_tavern-KillableSpitters"; public static ManualLogSource Logger { get; private set; } = new ManualLogSource("KillableSpitters"); public static float Config_SpitterHealth { get; set; } public static float Config_SpitterGlueKillSeconds { get; set; } public override void Load() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //IL_003a: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_006a: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) Logger = ((BasePlugin)this).Log; ConfigEntry val = ((BasePlugin)this).Config.Bind(new ConfigDefinition("General", "SpitterHealth"), 30f, new ConfigDescription("Bullet health pool for killable spitters. Only the lobby host's value applies.", (AcceptableValueBase)null, Array.Empty())); ConfigEntry obj = ((BasePlugin)this).Config.Bind(new ConfigDefinition("General", "SpitterGlueKillSeconds"), 0.7f, new ConfigDescription("Seconds after being C-foamed before a spitter dies (with the full death explosion). 0 or less keeps the vanilla freeze-only behavior. Values beyond the vanilla 240s freeze fire after the foam has worn off. Only the lobby host's value applies.", (AcceptableValueBase)null, Array.Empty())); Config_SpitterHealth = val.Value; Config_SpitterGlueKillSeconds = obj.Value; ((BasePlugin)this).Config.Save(); GameDataAPI.OnGameDataInitialized += SpitterKillManager.Setup; Patch_SpitterDamage.LogDamageForwarderFoldState(); new Harmony("the_tavern-KillableSpitters").PatchAll(); } } } namespace KillableSpitters.Patches { [HarmonyPatch] internal static class Fix_SpitterBotAggro { private static bool _broken; [HarmonyPatch(typeof(InfectionSpitter), "ManagerUpdate")] [HarmonyPrefix] public static bool Pre_ManagerUpdate(InfectionSpitter __instance, AIG_CourseNode courseNode, ref bool __result) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown if (SpitterKillManager.ShouldBlockManagerUpdate(__instance)) { __result = false; return false; } if (_broken) { return true; } try { __result = ManagerUpdateNoBots(__instance, courseNode); return false; } catch (Exception ex) { _broken = true; ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(74, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterBotAggro] Reimplementation failed, reverting to vanilla behavior: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); return true; } } private static bool ManagerUpdateNoBots(InfectionSpitter s, AIG_CourseNode courseNode) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Invalid comparison between Unknown and I4 //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Invalid comparison between Unknown and I4 //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_00da: 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_00e5: 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_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0145: 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_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) float num = Clock.Time - s.m_lastUpdate; s.m_lastUpdate = Clock.Time; s.m_freezeTime = s.m_lastUpdate + 30f; bool result = false; bool flag = (int)s.m_currentState == 2; bool flag2 = false; float num2 = 8.5f; float num3 = 0f; Il2CppReferenceArray coverageDatas = courseNode.m_playerCoverage.m_coverageDatas; Enumerator enumerator = PlayerManager.PlayerAgentsInLevel.GetEnumerator(); float num5 = default(float); while (enumerator.MoveNext()) { PlayerAgent current = enumerator.Current; if ((Object)(object)((current != null) ? current.Owner : null) == (Object)null || current.Owner.IsBot) { continue; } int playerSlotIndex = current.PlayerSlotIndex; if (playerSlotIndex < 0 || playerSlotIndex >= ((Il2CppArrayBase)(object)coverageDatas).Length || ((Il2CppArrayBase)(object)coverageDatas)[playerSlotIndex].m_nodeDistanceUnblocked >= 2 || !((Agent)current).Alive) { continue; } result = true; Vector3 val = ((Agent)current).EyePosition - s.m_position; float magnitude = ((Vector3)(ref val)).magnitude; ((Vector3)(ref val)).Normalize(); float num4 = 0f; bool flag3 = false; if (((Agent)current).HasDetectionMod(ref num5) && magnitude < num5) { num4 = ((Agent)current).GetDetectionMod(val, magnitude); flag3 = num4 > 0.001f; } bool flag4 = !flag && magnitude < num2; if ((flag4 || flag3) && Vector3.Dot(s.m_fwd, val) >= -0.5f && !Physics.Linecast(s.m_position, ((Agent)current).EyePosition, LayerManager.MASK_WORLD)) { if (flag4) { Vector3 horizontalVelocity = current.Locomotion.HorizontalVelocity; num3 = ((Vector3)(ref horizontalVelocity)).magnitude; num2 = magnitude; flag2 = true; } s.m_lightMeasure += num4 * num * 5f; } } if (s.m_lightMeasure > 0.5f) { if ((int)s.m_currentState != 2) { s.TryPlaySound(EVENTS.INFECTION_SPITTER_SCARED, false, 1f); s.SetTimedRetract(30f, s.m_lightRetractSpeed); } else if (s.m_stayInTimer < 30f) { s.m_stayInTimer = 30f; } } if ((int)s.m_currentState == 2) { s.m_timeToExplode = 3f; s.StopPurring(); s.m_stayInTimer -= num; if (s.m_stayInTimer < 0f) { s.m_stayInTimer = 0f; s.m_currentState = (eSpitterState)1; } else if (s.m_stayInTimer < 1f) { s.TryPlaySound(EVENTS.INFECTION_SPITTER_PRIMED, true, 2f); } } else if (flag2) { if (!s.m_isSoundOut) { s.TryPlaySound(EVENTS.INFECTION_SPITTER_OUT, true, 1f); s.m_isSoundOut = true; } if (num2 < 6f) { float num6 = 1f - num2 / 6f; float num7 = ((num3 > 5f) ? 3f : 1f); s.m_timeToExplode -= num * (num6 * num6 * 6.5f) * num7; s.UpdateTargetingRetraction(); if (!s.m_purrLoopPlaying) { s.TryPlaySound(EVENTS.INFECTION_SPITTER_PURR_LOOP, false, 1f); s.m_purrLoopPlaying = true; } if (SNet.IsMaster && !s.m_isExploding && (num2 < 1f || s.m_timeToExplode <= 0f)) { s.SendExplode(); } } else { s.RestoreTargetTimer(num); } s.m_currentState = (eSpitterState)1; } else { s.RestoreTargetTimer(num); if ((int)s.m_currentState != 0) { if (s.m_hasReachedTarget && s.m_targetRetract > 0.99f) { ((Behaviour)s).enabled = false; s.m_currentState = (eSpitterState)0; s.CleanupSound(); } else { if (s.m_isSoundOut) { s.m_isSoundOut = false; s.TryPlaySound(EVENTS.INFECTION_SPITTER_IN, true, 1f); } s.SetRetractTarget(0.5f, s.m_retractSpeed); } } } s.m_lightMeasure = Mathf.Clamp01(s.m_lightMeasure - num * 0.25f); return result; } } } namespace KillableSpitters.Patches.Spitters { [HarmonyPatch] internal static class Patch_SpitterDamage { [HarmonyPatch(typeof(BulletWeapon), "BulletHit")] [HarmonyPostfix] public static void Post_BulletHit(WeaponHitData weaponRayData, float additionalDis, bool __result) { //IL_0009: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) try { if (!__result) { return; } RaycastHit rayHit = weaponRayData.rayHit; Collider collider = ((RaycastHit)(ref rayHit)).collider; if ((Object)(object)collider == (Object)null) { return; } InfectionSpitterDamage component = ((Component)collider).GetComponent(); if ((Object)(object)component == (Object)null) { return; } InfectionSpitter spitter = component.m_spitter; if (!((Object)(object)spitter == (Object)null)) { rayHit = weaponRayData.rayHit; float num = ((RaycastHit)(ref rayHit)).distance + additionalDis; float num2 = weaponRayData.damage; Vector2 damageFalloff = weaponRayData.damageFalloff; if (num > damageFalloff.x) { num2 *= Math.Max(1f - (num - damageFalloff.x) / (damageFalloff.y - damageFalloff.x), BulletWeapon.s_falloffMin); } if (Weapon.SuperWeapons) { num2 *= 100f; } SpitterKillManager.ReportBulletDamage(spitter, num2); } } catch (Exception ex) { SpitterKillManager.Break(ex); } } [HarmonyPatch(typeof(InfectionSpitter), "OnIncomingDamage")] [HarmonyPrefix] public static bool Pre_OnIncomingDamage(InfectionSpitter __instance) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 try { if (SpitterKillManager.IsDeadOrDying(__instance.m_spitterIndex)) { return false; } if (__instance.m_isExploding) { return false; } if ((int)__instance.m_currentState == 2) { __instance.SendSlowExplode(); } else { __instance.SendExplode(); } return false; } catch (Exception ex) { SpitterKillManager.Break(ex); return true; } } [HarmonyPatch(typeof(InfectionSpitter), "DoGetGlued")] [HarmonyPostfix] public static void Post_DoGetGlued(InfectionSpitter __instance) { SpitterKillManager.OnSpitterGlued(__instance); } [HarmonyPatch(typeof(InfectionSpitter), "OnIncomingGlue")] [HarmonyPrefix] public static bool Pre_OnIncomingGlue(InfectionSpitter __instance) { try { return !SpitterKillManager.IsDeadOrDying(__instance.m_spitterIndex); } catch (Exception ex) { SpitterKillManager.Break(ex); return true; } } [HarmonyPatch(typeof(InfectionSpitter), "ReceiveDamage")] [HarmonyPrefix] public static bool Pre_ReceiveDamage(InfectionSpitterDamage data) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { return !SpitterKillManager.IsDeadOrDying(data.spitterIndex); } catch (Exception ex) { SpitterKillManager.Break(ex); return true; } } [HarmonyPatch(typeof(InfectionSpitter), "Update")] [HarmonyPostfix] public static void Post_Update(InfectionSpitter __instance) { SpitterKillManager.OnSpitterUpdatePostfix(__instance); } internal static void LogDamageForwarderFoldState() { //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown bool flag = default(bool); try { string[] array = new string[4] { "BulletDamage", "MeleeDamage", "FireDamage", "ExplosionDamage" }; Dictionary dictionary = new Dictionary(); FieldInfo[] fields = typeof(InfectionSpitterDamage).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo field in fields) { if (field.FieldType != typeof(IntPtr)) { continue; } string text = Array.Find(array, (string f) => field.Name.StartsWith("NativeMethodInfoPtr_" + f + "_", StringComparison.Ordinal)); if (text != null) { IntPtr intPtr = (IntPtr)field.GetValue(null); if (intPtr != IntPtr.Zero) { dictionary[text] = Marshal.ReadIntPtr(intPtr); } } } if (dictionary.Count < array.Length) { ManualLogSource logger = Plugin.Logger; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(65, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterKill] Damage-forwarder fold check incomplete ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(dictionary.Count); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(array.Length); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" resolved)"); } logger.LogWarning(val); } else if (dictionary.Values.Distinct().Count() == 1) { Plugin.Logger.LogDebug((object)("[SpitterKill] InfectionSpitterDamage damage forwarders share one native body " + $"(ICF-folded at 0x{dictionary["BulletDamage"]:X}, expected — none of them is patched)")); } else { Plugin.Logger.LogWarning((object)("[SpitterKill] Damage forwarders are NOT folded on this build — the ICF assumption behind the BulletHit tap no longer holds: " + string.Join(", ", dictionary.Select((KeyValuePair kv) => $"{kv.Key}=0x{kv.Value:X}")))); } } catch (Exception ex) { ManualLogSource logger2 = Plugin.Logger; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(68, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterKill] Damage-forwarder fold check failed (diagnostic only): "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } logger2.LogWarning(val); } } } public static class SpitterDeathFx { private const uint FlyerEnemyId = 42u; private const string DeathFxPrefabName = "FXC_FlyerDeath"; private static bool _broken; private static bool _warnedNoPool; private static FX_Pool? _pool; public static void PlayAt(Vector3 position) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_000e: 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_0040: Unknown result type (might be due to invalid IL or missing references) if (_broken) { return; } try { CellSound.Post(EVENTS.FLYER_DIE, position); FX_Pool val = ResolvePool(); if ((Object)(object)val == (Object)null) { WarnNoPoolOnce(); return; } FX_EffectBase_Poolable val2 = val.AquireEffect(); if (!((Object)(object)val2 == (Object)null)) { ((FX_EffectBase)val2).Play((FX_Trigger)null, position, Quaternion.identity); } } catch (ObjectCollectedException) { _pool = null; } catch (Exception ex2) { _broken = true; ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(41, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[SpitterKill] Spitter death FX disabled: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(ex2); } logger.LogWarning(val3); } } private static FX_Pool? ResolvePool() { if ((Object)(object)_pool != (Object)null) { return _pool; } _pool = FX_Manager.GetPreloadedEffectPool("FXC_FlyerDeath"); if ((Object)(object)_pool != (Object)null) { return _pool; } GameObject val = GetDeathFxPrefab(); if ((Object)(object)val == (Object)null) { val = Resources.Load("FXC_FlyerDeath"); } if ((Object)(object)val != (Object)null) { _pool = FX_Manager.GetEffectPool(val); } return _pool; } private static GameObject? GetDeathFxPrefab() { GameObject enemyPrefab = EnemyPrefabManager.GetEnemyPrefab(42u); if ((Object)(object)enemyPrefab == (Object)null) { return null; } FlyerAnimationController componentInChildren = enemyPrefab.GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { return null; } return componentInChildren.m_deathFX; } private static void WarnNoPoolOnce() { if (!_warnedNoPool) { _warnedNoPool = true; Plugin.Logger.LogWarning((object)"[SpitterKill] Could not resolve the flyer death FX pool (FXC_FlyerDeath); spitter removals play sound only"); } } } public struct SpitterDeathState { public const int MaskBytes = 240; public const int SpittersPerShard = 1920; public byte ShardIndex; private unsafe fixed byte _deadBits[240]; public static (int shard, int local) MapIndex(ushort spitterIndex) { return (shard: spitterIndex / 1920, local: spitterIndex % 1920); } public unsafe bool IsDead(int localIndex) { if (localIndex < 0 || localIndex >= 1920) { return false; } fixed (byte* deadBits = _deadBits) { return (deadBits[localIndex >> 3] & (1 << (localIndex & 7))) != 0; } } public unsafe void SetDead(int localIndex) { if (localIndex >= 0 && localIndex < 1920) { fixed (byte* deadBits = _deadBits) { byte* num = deadBits + (localIndex >> 3); *num |= (byte)(1 << (localIndex & 7)); } } } public unsafe void ClearDead(int localIndex) { if (localIndex >= 0 && localIndex < 1920) { fixed (byte* deadBits = _deadBits) { byte* num = deadBits + (localIndex >> 3); *num &= (byte)(~(1 << (localIndex & 7))); } } } public unsafe byte GetMaskByte(int byteIndex) { if (byteIndex < 0 || byteIndex >= 240) { return 0; } fixed (byte* deadBits = _deadBits) { return deadBits[byteIndex]; } } } public static class SpitterKillManager { private const string DamageEventName = "killable_spitters_damage"; private const string HealthEventName = "killable_spitters_health"; private const uint REPLICATOR_BASE_ID = 1263751252u; private const int SHARD_COUNT = 4; public const int SpitterCapacity = 7680; private const float FinalizeGraceSeconds = 1.5f; private const float FinalizeDeadlineSeconds = 5f; private const float PopAdoptWindowSeconds = 1.5f; private const float MaxReportedDamagePerHit = 100f; private static bool _broken; private static bool _setupDone; private static bool _warnedCapacity; private static readonly Dictionary _healthByIndex = new Dictionary(); private static readonly Dictionary _healthRel = new Dictionary(); private static readonly HashSet _dead = new HashSet(); private static readonly Dictionary _dyingFinalizeAt = new Dictionary(); private static readonly Dictionary _dyingDeadline = new Dictionary(); private static readonly HashSet _finalized = new HashSet(); private static readonly HashSet _explodingNow = new HashSet(); private static readonly Dictionary _lastPopCompletedAt = new Dictionary(); private static readonly HashSet _pendingDeathFx = new HashSet(); private static readonly HashSet _modelHidden = new HashSet(); private static readonly Dictionary _gluedAt = new Dictionary(); private static readonly StateReplicator?[] _replicators = new StateReplicator[4]; private static readonly SpitterDeathState[] _currentStates = new SpitterDeathState[4]; private static readonly Action?[] _handlers = new Action[4]; public static void Setup() { if (!_setupDone) { _setupDone = true; NetworkAPI.RegisterEvent("killable_spitters_damage", (Action)OnDamageEventReceived); NetworkAPI.RegisterEvent("killable_spitters_health", (Action)OnHealthEventReceived); LevelAPI.OnBuildDone += OnBuildDone; LevelAPI.OnLevelCleanup += OnLevelCleanup; LevelAPI.OnEnterLevel += ReconcileDeadSpitters; } } public static bool IsDeadOrDying(ushort spitterIndex) { if (_dead.Count != 0) { return _dead.Contains(spitterIndex); } return false; } public static void ReportBulletDamage(InfectionSpitter spitter, float dam) { if (_broken || (Object)(object)spitter == (Object)null || float.IsNaN(dam) || dam <= 0f) { return; } ushort spitterIndex = spitter.m_spitterIndex; if (!IsDeadOrDying(spitterIndex)) { if (spitterIndex >= 7680) { WarnCapacityOnce(spitterIndex); } else if (SNet.IsMaster) { AccumulateDamage(spitterIndex, Math.Min(dam, 100f)); } else if (SNet.HasMaster) { NetworkAPI.InvokeEvent("killable_spitters_damage", new SpitterDamageEvent { SpitterIndex = spitterIndex, Damage = dam }, SNet.Master, (SNet_ChannelType)2); } } } public static bool ShouldBlockManagerUpdate(InfectionSpitter spitter) { if (_broken) { return false; } try { if ((Object)(object)spitter == (Object)null) { return false; } ushort spitterIndex = spitter.m_spitterIndex; TickGlueKill(spitterIndex); if (_dead.Count == 0 || !_dead.Contains(spitterIndex)) { return false; } if (_dyingFinalizeAt.TryGetValue(spitterIndex, out var value)) { float value2; float num = (_dyingDeadline.TryGetValue(spitterIndex, out value2) ? value2 : float.MaxValue); if (Clock.Time >= num || (value != float.MaxValue && Clock.Time >= value)) { FinalizeSpitter(spitterIndex, spitter); } } return true; } catch (Exception ex) { Break(ex); return false; } } public static void OnSpitterUpdatePostfix(InfectionSpitter spitter) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) if (_broken || (Object)(object)spitter == (Object)null) { return; } try { ushort spitterIndex = spitter.m_spitterIndex; SpitterVisuals.TickRestores(); if (spitter.m_isExploding) { _explodingNow.Add(spitterIndex); float value; if (_dyingFinalizeAt.ContainsKey(spitterIndex)) { SpitterVisuals.ApplyDeathRamp(spitter); } else if (_healthRel.TryGetValue(spitterIndex, out value)) { SpitterVisuals.ApplyDamageRamp(spitter, value); } } else if (_explodingNow.Remove(spitterIndex)) { _lastPopCompletedAt[spitterIndex] = Clock.Time; float value2; if (_dyingFinalizeAt.ContainsKey(spitterIndex)) { SpitterVisuals.TintPop(spitterIndex, spitter, SpitterVisuals.DeathPopColor, recolorLiveParticles: false, untilFinalize: true); if (_pendingDeathFx.Remove(spitterIndex)) { SpitterDeathFx.PlayAt(spitter.m_position); HideSpitterModel(spitterIndex, spitter); } } else if (_healthRel.TryGetValue(spitterIndex, out value2)) { SpitterVisuals.TintPop(spitterIndex, spitter, SpitterVisuals.GetPopColor(value2), recolorLiveParticles: false, untilFinalize: false); SpitterVisuals.ApplyDamageTint(spitter, value2); } } TickGlueKill(spitterIndex); if (_dyingFinalizeAt.Count == 0 || !_dyingFinalizeAt.TryGetValue(spitterIndex, out var value3)) { return; } float value4; float num = (_dyingDeadline.TryGetValue(spitterIndex, out value4) ? value4 : float.MaxValue); if (value3 == float.MaxValue) { if (!spitter.m_isExploding) { _dyingFinalizeAt[spitterIndex] = Clock.Time + 1.5f; } else if (Clock.Time >= num) { FinalizeSpitter(spitterIndex, spitter); } } else if (Clock.Time >= value3 || Clock.Time >= num) { FinalizeSpitter(spitterIndex, spitter); } } catch (Exception ex) { Break(ex); } } public static void OnSpitterGlued(InfectionSpitter spitter) { if (_broken || (Object)(object)spitter == (Object)null) { return; } try { if (SNet.IsMaster && !(Plugin.Config_SpitterGlueKillSeconds <= 0f)) { ushort spitterIndex = spitter.m_spitterIndex; if (spitterIndex >= 7680) { WarnCapacityOnce(spitterIndex); } else if (!IsDeadOrDying(spitterIndex)) { _gluedAt.TryAdd(spitterIndex, Clock.Time); } } } catch (Exception ex) { Break(ex); } } private static void TickGlueKill(ushort index) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown if (_gluedAt.Count == 0 || !SNet.IsMaster || !_gluedAt.TryGetValue(index, out var value)) { return; } if (IsDeadOrDying(index)) { _gluedAt.Remove(index); } else if (!(Clock.Time - value < Plugin.Config_SpitterGlueKillSeconds)) { _gluedAt.Remove(index); ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExDebugLogInterpolatedStringHandler val = new BepInExDebugLogInterpolatedStringHandler(46, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterKill] Spitter "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(index); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" killed by C-foam (host)"); } logger.LogDebug(val); HostMarkDead(index); } } public static void Break(Exception ex) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (_broken) { return; } _broken = true; ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(69, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterKill] Unexpected error, disabling killable spitters locally: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); try { SpitterVisuals.RestoreAll(); } catch (Exception ex2) { ManualLogSource logger2 = Plugin.Logger; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(46, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[SpitterKill] FX restore during Break failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex2.Message); } logger2.LogWarning(val2); } try { foreach (ushort item in _modelHidden.ToList()) { InfectionSpitter val3 = TryGetSpitter(item); if ((Object)(object)val3 != (Object)null) { ShowSpitterModel(item, val3); } } _modelHidden.Clear(); } catch (Exception ex3) { ManualLogSource logger3 = Plugin.Logger; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(49, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[SpitterKill] Model restore during Break failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ex3.Message); } logger3.LogWarning(val2); } } private static void OnDamageEventReceived(ulong senderPlayer, SpitterDamageEvent data) { if (_broken || !SNet.IsMaster) { return; } try { if (!float.IsNaN(data.Damage) && !(data.Damage <= 0f)) { if (data.SpitterIndex >= 7680) { WarnCapacityOnce(data.SpitterIndex); } else { AccumulateDamage(data.SpitterIndex, Math.Min(data.Damage, 100f)); } } } catch (Exception ex) { Break(ex); } } private static void AccumulateDamage(ushort index, float dam) { if (!IsDeadOrDying(index)) { if (!_healthByIndex.TryGetValue(index, out var value)) { value = Math.Max(1f, Plugin.Config_SpitterHealth); } value -= dam; if (value > 0f) { _healthByIndex[index] = value; BroadcastHealth(index, value); } else { HostMarkDead(index); } } } private static void HostMarkDead(ushort index) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown _healthByIndex.Remove(index); _gluedAt.Remove(index); _dead.Add(index); var (num, dead) = SpitterDeathState.MapIndex(index); _currentStates[num].SetDead(dead); StateReplicator val = _replicators[num]; bool flag = default(bool); if (val != null && val.IsValid) { val.SetState(_currentStates[num]); } else { ManualLogSource logger = Plugin.Logger; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(73, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[SpitterKill] Replicator for shard "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(num); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" missing, spitter "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(index); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" death is local-only"); } logger.LogWarning(val2); } ManualLogSource logger2 = Plugin.Logger; BepInExDebugLogInterpolatedStringHandler val3 = new BepInExDebugLogInterpolatedStringHandler(36, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[SpitterKill] Spitter "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(index); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" killed (host)"); } logger2.LogDebug(val3); KillSpitter(index, silent: false); } private static void BroadcastHealth(ushort index, float health) { SpitterHealthEvent spitterHealthEvent = new SpitterHealthEvent { SpitterIndex = index, HealthRel = Math.Clamp(health / Math.Max(1f, Plugin.Config_SpitterHealth), 0f, 1f) }; NetworkAPI.InvokeEvent("killable_spitters_health", spitterHealthEvent, (SNet_ChannelType)2); OnHealthEventReceived(0uL, spitterHealthEvent); } private static void OnHealthEventReceived(ulong senderPlayer, SpitterHealthEvent data) { if (_broken) { return; } try { if (float.IsNaN(data.HealthRel)) { return; } if (data.SpitterIndex >= 7680) { WarnCapacityOnce(data.SpitterIndex); } else if (!IsDeadOrDying(data.SpitterIndex)) { float num = Math.Clamp(data.HealthRel, 0f, 1f); _healthRel[data.SpitterIndex] = num; InfectionSpitter val = TryGetSpitter(data.SpitterIndex); if ((Object)(object)val != (Object)null) { SpitterVisuals.ApplyDamageTint(val, num); } } } catch (Exception ex) { Break(ex); } } private static void OnDeathStateChanged(int shard, SpitterDeathState oldState, SpitterDeathState newState, bool isRecall) { if (_broken) { return; } try { _currentStates[shard] = newState; for (int i = 0; i < 240; i++) { byte maskByte = oldState.GetMaskByte(i); byte maskByte2 = newState.GetMaskByte(i); if (maskByte == maskByte2) { continue; } byte b = (byte)(maskByte2 & ~maskByte); byte b2 = (byte)(maskByte & ~maskByte2); for (int j = 0; j < 8; j++) { ushort num = (ushort)(shard * 1920 + i * 8 + j); int num2 = 1 << j; if ((b & num2) != 0) { _dead.Add(num); KillSpitter(num, isRecall); } else if ((b2 & num2) != 0) { ReviveSpitter(num); } } } } catch (Exception ex) { Break(ex); } } private static void KillSpitter(ushort index, bool silent) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Invalid comparison between Unknown and I4 //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) if (_finalized.Contains(index) || (_dyingFinalizeAt.ContainsKey(index) && !silent)) { return; } _dead.Add(index); InfectionSpitter val = TryGetSpitter(index); if ((Object)(object)val == (Object)null) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExDebugLogInterpolatedStringHandler val2 = new BepInExDebugLogInterpolatedStringHandler(60, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[SpitterKill] Spitter "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(index); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" not resolvable yet, deferring removal"); } logger.LogDebug(val2); return; } if (silent) { FinalizeSpitter(index, val); return; } float value; if (val.m_isExploding) { _dyingFinalizeAt[index] = float.MaxValue; _pendingDeathFx.Add(index); } else if (_lastPopCompletedAt.TryGetValue(index, out value) && Clock.Time - value <= 1.5f) { _dyingFinalizeAt[index] = Clock.Time + 1.5f; SpitterVisuals.TintPop(index, val, SpitterVisuals.DeathPopColor, recolorLiveParticles: true, untilFinalize: true); SpitterDeathFx.PlayAt(val.m_position); HideSpitterModel(index, val); } else if (val.m_isGlued) { SpitterDeathFx.PlayAt(val.m_position); HideSpitterModel(index, val); _dyingFinalizeAt[index] = Clock.Time + 1.5f; } else { val.DoExplode(((int)val.m_currentState == 2) ? 0.5f : 1f); _dyingFinalizeAt[index] = float.MaxValue; _pendingDeathFx.Add(index); } _dyingDeadline[index] = Clock.Time + 5f; } private static void HideSpitterModel(ushort index, InfectionSpitter spitter) { _modelHidden.Add(index); if ((Object)(object)spitter.m_renderer != (Object)null) { spitter.m_renderer.enabled = false; } if ((Object)(object)spitter.m_light != (Object)null) { ((Behaviour)spitter.m_light).enabled = false; } if ((Object)(object)spitter.m_damage != (Object)null && (Object)(object)((Component)spitter.m_damage).gameObject != (Object)(object)((Component)spitter).gameObject) { ((Component)spitter.m_damage).gameObject.SetActive(false); } } private static void ShowSpitterModel(ushort index, InfectionSpitter spitter) { _modelHidden.Remove(index); if ((Object)(object)spitter.m_renderer != (Object)null) { spitter.m_renderer.enabled = true; } if ((Object)(object)spitter.m_light != (Object)null) { ((Behaviour)spitter.m_light).enabled = true; } if ((Object)(object)spitter.m_damage != (Object)null && (Object)(object)((Component)spitter.m_damage).gameObject != (Object)(object)((Component)spitter).gameObject) { ((Component)spitter.m_damage).gameObject.SetActive(true); } } private static void FinalizeSpitter(ushort index, InfectionSpitter spitter) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown if (_finalized.Add(index)) { _dyingFinalizeAt.Remove(index); _dyingDeadline.Remove(index); _explodingNow.Remove(index); _lastPopCompletedAt.Remove(index); _modelHidden.Remove(index); _healthRel.Remove(index); SpitterVisuals.RestoreForSpitter(index); spitter.CleanupSound(); if (_pendingDeathFx.Remove(index)) { SpitterDeathFx.PlayAt(spitter.m_position); } ((Behaviour)spitter).enabled = false; ((Component)spitter).gameObject.SetActive(false); if ((Object)(object)spitter.m_damage != (Object)null && (Object)(object)((Component)spitter.m_damage).gameObject != (Object)(object)((Component)spitter).gameObject) { ((Component)spitter.m_damage).gameObject.SetActive(false); } ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExDebugLogInterpolatedStringHandler val = new BepInExDebugLogInterpolatedStringHandler(30, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterKill] Spitter "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(index); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" removed"); } logger.LogDebug(val); } } private static void ReviveSpitter(ushort index) { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown _dead.Remove(index); _finalized.Remove(index); _dyingFinalizeAt.Remove(index); _dyingDeadline.Remove(index); _explodingNow.Remove(index); _lastPopCompletedAt.Remove(index); _pendingDeathFx.Remove(index); _healthByIndex.Remove(index); _healthRel.Remove(index); _gluedAt.Remove(index); SpitterVisuals.RestoreForSpitter(index); InfectionSpitter val = TryGetSpitter(index); if (!((Object)(object)val == (Object)null)) { ((Component)val).gameObject.SetActive(true); ShowSpitterModel(index, val); SpitterVisuals.ResetBodyGlow(val); ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExDebugLogInterpolatedStringHandler val2 = new BepInExDebugLogInterpolatedStringHandler(50, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[SpitterKill] Spitter "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(index); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" revived (checkpoint recall)"); } logger.LogDebug(val2); } } private static void ReconcileDeadSpitters() { if (_broken || _dead.Count == 0) { return; } try { foreach (ushort item in _dead.ToList()) { if (!_finalized.Contains(item) && !_dyingFinalizeAt.ContainsKey(item)) { InfectionSpitter val = TryGetSpitter(item); if ((Object)(object)val != (Object)null) { FinalizeSpitter(item, val); } } } } catch (Exception ex) { Break(ex); } } private static InfectionSpitter? TryGetSpitter(ushort index) { List s_allSpitters = InfectionSpitter.s_allSpitters; if (s_allSpitters == null || index >= s_allSpitters.Count) { return null; } return s_allSpitters[(int)index]; } private static void OnBuildDone() { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown if (_broken) { return; } try { UnloadReplicators(); ClearRuntimeState(); if (!APIStatus.Network.Ready) { Plugin.Logger.LogError((object)"[SpitterKill] NetworkAPI not ready during OnBuildDone, spitters stay vanilla"); return; } bool flag = default(bool); for (int i = 0; i < 4; i++) { SpitterDeathState spitterDeathState = new SpitterDeathState { ShardIndex = (byte)i }; StateReplicator val = StateReplicator.Create((uint)(1263751252 + i), spitterDeathState, (LifeTimeType)1, (IStateReplicatorHolder)null); if (val == null) { ManualLogSource logger = Plugin.Logger; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(48, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[SpitterKill] Failed to create replicator shard "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(i); } logger.LogError(val2); } else { _currentStates[i] = spitterDeathState; int shard = i; Action action = delegate(SpitterDeathState oldState, SpitterDeathState newState, bool isRecall) { OnDeathStateChanged(shard, oldState, newState, isRecall); }; val.OnStateChanged += action; _handlers[i] = action; _replicators[i] = val; } } ManualLogSource logger2 = Plugin.Logger; BepInExDebugLogInterpolatedStringHandler val3 = new BepInExDebugLogInterpolatedStringHandler(49, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("[SpitterKill] Ready ("); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(4); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" shards, "); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("health="); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(Plugin.Config_SpitterHealth); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(", IsMaster="); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(SNet.IsMaster); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(")"); } logger2.LogDebug(val3); } catch (Exception ex) { Break(ex); } } private static void OnLevelCleanup() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown try { UnloadReplicators(); ClearRuntimeState(); } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(29, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterKill] Cleanup error: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); } } private static void UnloadReplicators() { for (int i = 0; i < 4; i++) { StateReplicator val = _replicators[i]; if (val != null) { if (_handlers[i] != null) { val.OnStateChanged -= _handlers[i]; } val.Unload(); _replicators[i] = null; _handlers[i] = null; } } } private static void ClearRuntimeState() { _healthByIndex.Clear(); _healthRel.Clear(); _dead.Clear(); _dyingFinalizeAt.Clear(); _dyingDeadline.Clear(); _finalized.Clear(); _explodingNow.Clear(); _lastPopCompletedAt.Clear(); _pendingDeathFx.Clear(); _modelHidden.Clear(); _gluedAt.Clear(); _warnedCapacity = false; SpitterVisuals.RestoreAll(); for (int i = 0; i < 4; i++) { _currentStates[i] = new SpitterDeathState { ShardIndex = (byte)i }; } } private static void WarnCapacityOnce(ushort index) { if (!_warnedCapacity) { _warnedCapacity = true; Plugin.Logger.LogWarning((object)($"[SpitterKill] Spitter index {index} exceeds capacity {7680}; " + "spitters beyond capacity stay vanilla (unkillable)")); } } } public static class SpitterVisuals { private readonly struct OriginalStartColor { public readonly ParticleSystem Ps; public readonly MainModule Main; public readonly ParticleSystemGradientMode Mode; public readonly Color ColorMin; public readonly Color ColorMax; public readonly Gradient? GradientMin; public readonly Gradient? GradientMax; public OriginalStartColor(ParticleSystem ps, MainModule main, MinMaxGradient captured) { //IL_0010: 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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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) Ps = ps; Main = main; Mode = captured.m_Mode; ColorMin = captured.m_ColorMin; ColorMax = captured.m_ColorMax; GradientMin = captured.m_GradientMin; GradientMax = captured.m_GradientMax; } } private const float FxRestoreSeconds = 3f; private const float SteadyGlowScale = 0.6f; private static readonly Color OrangeGlowBase = new Color(1f, 0.45f, 0f) * 0.5f; private static readonly Color RedGlowBase = new Color(1f, 0.06f, 0.04f) * 0.5f; private static readonly Color32 GreenPopColor = new Color32((byte)60, byte.MaxValue, (byte)200, byte.MaxValue); private static readonly Color32 OrangePopColor = new Color32(byte.MaxValue, (byte)150, (byte)30, byte.MaxValue); private static readonly Color32 RedPopColor = new Color32(byte.MaxValue, (byte)24, (byte)16, byte.MaxValue); private static bool _visualsBroken; private const int GcRaceAttempts = 3; private static MinMaxGradient? _sharedGradient; private static readonly Dictionary _tintedSystems = new Dictionary(); private static readonly Dictionary _pendingRestores = new Dictionary(); private static readonly Dictionary> _tintedBySpitter = new Dictionary>(); public static Color32 DeathPopColor => RedPopColor; public static Color DeathSteadyGlow => RedGlowBase * 0.6f; private static Color GetBaseGlow(float healthRel) { //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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0027: Unknown result type (might be due to invalid IL or missing references) healthRel = Mathf.Clamp01(healthRel); if (!(healthRel >= 0.5f)) { return Color.Lerp(OrangeGlowBase, RedGlowBase, (0.5f - healthRel) * 2f); } return Color.Lerp(InfectionSpitter.s_glowColor, OrangeGlowBase, (1f - healthRel) * 2f); } public static Color32 GetPopColor(float healthRel) { //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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0027: Unknown result type (might be due to invalid IL or missing references) healthRel = Mathf.Clamp01(healthRel); if (!(healthRel >= 0.5f)) { return Color32.Lerp(OrangePopColor, RedPopColor, (0.5f - healthRel) * 2f); } return Color32.Lerp(GreenPopColor, OrangePopColor, (1f - healthRel) * 2f); } public static void ApplyBodyGlow(InfectionSpitter spitter, Color color) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (_visualsBroken) { return; } try { if (!((Object)(object)spitter == (Object)null) && spitter.m_propBlock != null && !((Object)(object)spitter.m_renderer == (Object)null)) { spitter.m_propBlock.SetColor(InfectionSpitter.s_glowColorID, color); spitter.m_renderer.SetPropertyBlock(spitter.m_propBlock, 0); } } catch (ObjectCollectedException) { } catch (Exception ex2) { BreakVisuals(ex2); } } public static void ApplyDamageTint(InfectionSpitter spitter, float healthRel) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) ApplyBodyGlow(spitter, GetBaseGlow(healthRel) * 0.6f); } public static void ApplyDamageRamp(InfectionSpitter spitter, float healthRel) { //IL_000c: 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) if (!((Object)(object)spitter == (Object)null)) { ApplyBodyGlow(spitter, GetBaseGlow(healthRel) * spitter.m_explodeProgression); } } public static void ApplyDeathRamp(InfectionSpitter spitter) { //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) if (!((Object)(object)spitter == (Object)null)) { ApplyBodyGlow(spitter, RedGlowBase * spitter.m_explodeProgression); } } public static void ResetBodyGlow(InfectionSpitter spitter) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) ApplyBodyGlow(spitter, InfectionSpitter.s_startGlowColor); } public static void TintPop(ushort index, InfectionSpitter spitter, Color32 color, bool recolorLiveParticles, bool untilFinalize) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (_visualsBroken) { return; } for (int i = 1; i <= 3; i++) { try { TintPopCore(index, spitter, color, recolorLiveParticles, untilFinalize); return; } catch (ObjectCollectedException) { } catch (Exception ex2) { BreakVisuals(ex2); return; } } Plugin.Logger.LogDebug((object)"[SpitterKill] FX tint skipped (IL2CPP GC race persisted)"); } private static void TintPopCore(ushort index, InfectionSpitter spitter, Color32 color, bool recolorLiveParticles, bool untilFinalize) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) FX_EffectBase_Poolable val = ((spitter != null) ? spitter.m_fx : null); if ((Object)(object)val == (Object)null || val.m_inPool) { return; } Il2CppArrayBase componentsInChildren = ((Component)val).GetComponentsInChildren(true); if (componentsInChildren == null) { return; } foreach (ParticleSystem item in componentsInChildren) { if ((Object)(object)item == (Object)null) { continue; } int instanceID = ((Object)item).GetInstanceID(); if (!_tintedSystems.TryGetValue(instanceID, out var value)) { MainModule main = item.main; value = new OriginalStartColor(item, main, main.startColor); _tintedSystems[instanceID] = value; } WriteStartColor(value.Main, Color32.op_Implicit(color)); if (recolorLiveParticles) { RecolorLiveParticles(item, color); } if (untilFinalize) { _pendingRestores[instanceID] = float.MaxValue; if (!_tintedBySpitter.TryGetValue(index, out List value2)) { value2 = (_tintedBySpitter[index] = new List()); } if (!value2.Contains(instanceID)) { value2.Add(instanceID); } } else { _pendingRestores[instanceID] = Clock.Time + 3f; } } } private static void WriteStartColor(MainModule main, Color color) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) WriteStartColor(main, (ParticleSystemGradientMode)0, color, color, null, null); } private static void WriteStartColor(MainModule main, ParticleSystemGradientMode mode, Color colorMin, Color colorMax, Gradient? gradientMin, Gradient? gradientMax) { //IL_001b: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown object obj = _sharedGradient; if (obj == null) { MinMaxGradient val = new MinMaxGradient(Color.white); _sharedGradient = val; obj = (object)val; } MinMaxGradient val2 = (MinMaxGradient)obj; val2.m_Mode = mode; val2.m_GradientMin = gradientMin; val2.m_GradientMax = gradientMax; val2.m_ColorMin = colorMin; val2.m_ColorMax = colorMax; main.startColor = val2; } private static void RecolorLiveParticles(ParticleSystem ps, Color32 color) { //IL_0022: 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_002b: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) int particleCount = ps.particleCount; if (particleCount > 0) { Il2CppStructArray val = new Il2CppStructArray((long)particleCount); int particles = ps.GetParticles(val); for (int i = 0; i < particles; i++) { Particle val2 = ((Il2CppArrayBase)(object)val)[i]; ((Particle)(ref val2)).startColor = new Color32(color.r, color.g, color.b, byte.MaxValue); ((Il2CppArrayBase)(object)val)[i] = val2; } ps.SetParticles(val, particles); } } public static void TickRestores() { if (_pendingRestores.Count == 0) { return; } List list = null; foreach (var (item, num3) in _pendingRestores) { if (num3 != float.MaxValue && Clock.Time >= num3) { (list ?? (list = new List())).Add(item); } } if (list == null) { return; } foreach (int item2 in list) { RestoreSystem(item2); } } public static void RestoreForSpitter(ushort index) { if (!_tintedBySpitter.TryGetValue(index, out List value)) { return; } foreach (int item in value) { RestoreSystem(item); } _tintedBySpitter.Remove(index); } public static void RestoreAll() { foreach (int item in _tintedSystems.Keys.ToList()) { RestoreSystem(item); } _tintedSystems.Clear(); _pendingRestores.Clear(); _tintedBySpitter.Clear(); } private static void RestoreSystem(int id) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (_tintedSystems.TryGetValue(id, out var value)) { bool flag = default(bool); for (int i = 1; i <= 3; i++) { try { if ((Object)(object)value.Ps != (Object)null) { WriteStartColor(value.Main, value.Mode, value.ColorMin, value.ColorMax, value.GradientMin, value.GradientMax); } } catch (ObjectCollectedException) { if (i == 3) { Plugin.Logger.LogDebug((object)"[SpitterKill] FX tint restore skipped (IL2CPP GC race persisted)"); } continue; } catch (Exception ex2) { ManualLogSource logger = Plugin.Logger; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(41, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterKill] Failed to restore FX tint: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex2.Message); } logger.LogWarning(val); } break; } } _tintedSystems.Remove(id); _pendingRestores.Remove(id); } private static void BreakVisuals(Exception ex) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (!_visualsBroken) { _visualsBroken = true; ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(96, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SpitterKill] Damage-state visuals failed, disabling visuals locally (kill feature unaffected): "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogWarning(val); } } } } namespace KillableSpitters.Events { public struct SpitterDamageEvent { public ushort SpitterIndex { get; set; } public float Damage { get; set; } } public struct SpitterHealthEvent { public ushort SpitterIndex { get; set; } public float HealthRel { get; set; } } }