Decompiled source of KillableSpitters v0.5.0

KillableSpitters.dll

Decompiled a day ago
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<float> val = ((BasePlugin)this).Config.Bind<float>(new ConfigDefinition("General", "SpitterHealth"), 30f, new ConfigDescription("Bullet health pool for killable spitters. Only the lobby host's value applies.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEntry<float> obj = ((BasePlugin)this).Config.Bind<float>(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<object>()));
			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<Exception>(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<PlayerCoverageData> coverageDatas = courseNode.m_playerCoverage.m_coverageDatas;
			Enumerator<PlayerAgent> 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<PlayerCoverageData>)(object)coverageDatas).Length || ((Il2CppArrayBase<PlayerCoverageData>)(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<InfectionSpitterDamage>();
				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<string, IntPtr> dictionary = new Dictionary<string, IntPtr>();
				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<int>(dictionary.Count);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(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<string, IntPtr> 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<string>(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<Exception>(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<GameObject>("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<FlyerAnimationController>(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<ushort, float> _healthByIndex = new Dictionary<ushort, float>();

		private static readonly Dictionary<ushort, float> _healthRel = new Dictionary<ushort, float>();

		private static readonly HashSet<ushort> _dead = new HashSet<ushort>();

		private static readonly Dictionary<ushort, float> _dyingFinalizeAt = new Dictionary<ushort, float>();

		private static readonly Dictionary<ushort, float> _dyingDeadline = new Dictionary<ushort, float>();

		private static readonly HashSet<ushort> _finalized = new HashSet<ushort>();

		private static readonly HashSet<ushort> _explodingNow = new HashSet<ushort>();

		private static readonly Dictionary<ushort, float> _lastPopCompletedAt = new Dictionary<ushort, float>();

		private static readonly HashSet<ushort> _pendingDeathFx = new HashSet<ushort>();

		private static readonly HashSet<ushort> _modelHidden = new HashSet<ushort>();

		private static readonly Dictionary<ushort, float> _gluedAt = new Dictionary<ushort, float>();

		private static readonly StateReplicator<SpitterDeathState>?[] _replicators = new StateReplicator<SpitterDeathState>[4];

		private static readonly SpitterDeathState[] _currentStates = new SpitterDeathState[4];

		private static readonly Action<SpitterDeathState, SpitterDeathState, bool>?[] _handlers = new Action<SpitterDeathState, SpitterDeathState, bool>[4];

		public static void Setup()
		{
			if (!_setupDone)
			{
				_setupDone = true;
				NetworkAPI.RegisterEvent<SpitterDamageEvent>("killable_spitters_damage", (Action<ulong, SpitterDamageEvent>)OnDamageEventReceived);
				NetworkAPI.RegisterEvent<SpitterHealthEvent>("killable_spitters_health", (Action<ulong, SpitterHealthEvent>)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<SpitterDamageEvent>("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<ushort>(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<Exception>(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<string>(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<string>(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<SpitterDeathState> 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<int>(num);
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" missing, spitter ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<ushort>(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<ushort>(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<SpitterHealthEvent>("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<ushort>(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<ushort>(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<ushort>(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<InfectionSpitter> 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<SpitterDeathState> val = StateReplicator<SpitterDeathState>.Create((uint)(1263751252 + i), spitterDeathState, (LifeTimeType)1, (IStateReplicatorHolder<SpitterDeathState>)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<int>(i);
						}
						logger.LogError(val2);
					}
					else
					{
						_currentStates[i] = spitterDeathState;
						int shard = i;
						Action<SpitterDeathState, SpitterDeathState, bool> 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<int>(4);
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" shards, ");
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("health=");
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<float>(Plugin.Config_SpitterHealth);
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(", IsMaster=");
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<bool>(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<Exception>(ex);
				}
				logger.LogError(val);
			}
		}

		private static void UnloadReplicators()
		{
			for (int i = 0; i < 4; i++)
			{
				StateReplicator<SpitterDeathState> 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<int, OriginalStartColor> _tintedSystems = new Dictionary<int, OriginalStartColor>();

		private static readonly Dictionary<int, float> _pendingRestores = new Dictionary<int, float>();

		private static readonly Dictionary<ushort, List<int>> _tintedBySpitter = new Dictionary<ushort, List<int>>();

		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<ParticleSystem> componentsInChildren = ((Component)val).GetComponentsInChildren<ParticleSystem>(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<int> value2))
					{
						value2 = (_tintedBySpitter[index] = new List<int>());
					}
					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<Particle> val = new Il2CppStructArray<Particle>((long)particleCount);
				int particles = ps.GetParticles(val);
				for (int i = 0; i < particles; i++)
				{
					Particle val2 = ((Il2CppArrayBase<Particle>)(object)val)[i];
					((Particle)(ref val2)).startColor = new Color32(color.r, color.g, color.b, byte.MaxValue);
					((Il2CppArrayBase<Particle>)(object)val)[i] = val2;
				}
				ps.SetParticles(val, particles);
			}
		}

		public static void TickRestores()
		{
			if (_pendingRestores.Count == 0)
			{
				return;
			}
			List<int> list = null;
			foreach (var (item, num3) in _pendingRestores)
			{
				if (num3 != float.MaxValue && Clock.Time >= num3)
				{
					(list ?? (list = new List<int>())).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<int> 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<string>(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<Exception>(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; }
	}
}