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; }
}
}