Decompiled source of SpawnKit v0.5.2

plugins/SpawnKit.Core.dll

Decompiled 6 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("SpawnKit.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.2.0")]
[assembly: AssemblyInformationalVersion("0.5.2+091b206910305beb491301afe1db01b8cd7b8e72")]
[assembly: AssemblyProduct("SpawnKit.Core")]
[assembly: AssemblyTitle("SpawnKit.Core")]
[assembly: AssemblyMetadata("BuildStamp", "091b2069 2026-08-28")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[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;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SpawnKit.Core
{
	public enum AiLiveness
	{
		NoAiGraph,
		WorldPaused,
		AiBehaviourDisabled,
		NotStartInitialized,
		DistanceCulled,
		Ticking
	}
	public static class AiLivenessRules
	{
		public static AiLiveness Classify(bool hasAi, int aiStateCount, bool aiBehaviourEnabled, bool startInitDone, bool closeToPlayer, bool gameplayPaused)
		{
			if (!hasAi || aiStateCount <= 0)
			{
				return AiLiveness.NoAiGraph;
			}
			if (gameplayPaused)
			{
				return AiLiveness.WorldPaused;
			}
			if (!aiBehaviourEnabled)
			{
				return AiLiveness.AiBehaviourDisabled;
			}
			if (!startInitDone)
			{
				return AiLiveness.NotStartInitialized;
			}
			if (!closeToPlayer)
			{
				return AiLiveness.DistanceCulled;
			}
			return AiLiveness.Ticking;
		}

		public static bool CanEngageUnprompted(AiLiveness v)
		{
			return v == AiLiveness.Ticking;
		}

		public static string Explain(AiLiveness v)
		{
			return v switch
			{
				AiLiveness.NoAiGraph => "no CharacterAI or an empty AI-state graph — this body can never fight; TemplateAiGate should have refused it upstream (report it).", 
				AiLiveness.WorldPaused => "the WORLD SIM is paused (NetworkLevelLoader.IsGameplayPaused) — no creature, spawned OR vanilla, detects/wanders/culls while this holds, yet every dev verb still answers. Run Beastwhispering's 'unstick fix' and re-check before trusting any real-time combat result.", 
				AiLiveness.AiBehaviourDisabled => "the CharacterAI Behaviour is disabled — usually CharAIDisable's VeryFar leg; get a player within DistanceToEnable (or raise [AI] AiDisableDistance).", 
				AiLiveness.NotStartInitialized => "Character.IsStartInitDone is false — ProcessInit never completed, so detection returns at its first guard forever even though wander still runs. Suspect the visual/rig init chain ('ghostdiag <name> fix').", 
				AiLiveness.DistanceCulled => "distance-culled by CharAIDisable (no player inside DistanceToEnable) — close the gap.", 
				AiLiveness.Ticking => "live — this body can detect and engage on its own.", 
				_ => "unknown.", 
			};
		}

		public static string Format(AiLiveness v)
		{
			if (v != AiLiveness.Ticking)
			{
				return v.ToString() + " (" + Explain(v) + ")";
			}
			return "Ticking";
		}
	}
	public static class CasterSkillGate
	{
		public const string CensusError = "skills=?/?";

		public static List<int> MissingSkillIds(IEnumerable<int> learnedIds, IEnumerable<int> childSkillIds)
		{
			List<int> list = new List<int>();
			if (childSkillIds == null)
			{
				return list;
			}
			HashSet<int> hashSet = new HashSet<int>();
			if (learnedIds != null)
			{
				foreach (int learnedId in learnedIds)
				{
					hashSet.Add(learnedId);
				}
			}
			HashSet<int> hashSet2 = new HashSet<int>();
			foreach (int childSkillId in childSkillIds)
			{
				if (!hashSet.Contains(childSkillId) && hashSet2.Add(childSkillId))
				{
					list.Add(childSkillId);
				}
			}
			return list;
		}

		public static string Census(int learnedCount, int childCount, bool hasKnowledge)
		{
			if (!hasKnowledge)
			{
				return "skills=-/-";
			}
			return $"skills={learnedCount}/{childCount}";
		}
	}
	public enum CorpseLootVerdict
	{
		Alive,
		NoLootableComponent,
		ComponentDisabled,
		NoPouch,
		NoPouchInteractionTrigger,
		DropsPresentButInert,
		NoDropsConfigured,
		Lootable
	}
	public readonly struct CorpseLootObservation
	{
		public readonly bool IsDead;

		public readonly bool HasComponent;

		public readonly bool ComponentEnabled;

		public readonly bool HasPouch;

		public readonly bool PouchHasInteractionTrigger;

		public readonly int LootDropEntries;

		public readonly int LootDropsWithDropper;

		public readonly int SkinDropEntries;

		public readonly int SkinDropsWithDropper;

		public readonly bool Lootable;

		public readonly bool Skinable;

		public CorpseLootObservation(bool isDead, bool hasComponent, bool componentEnabled, bool hasPouch, bool pouchHasInteractionTrigger, int lootDropEntries, int lootDropsWithDropper, int skinDropEntries, int skinDropsWithDropper, bool lootable, bool skinable)
		{
			IsDead = isDead;
			HasComponent = hasComponent;
			ComponentEnabled = componentEnabled;
			HasPouch = hasPouch;
			PouchHasInteractionTrigger = pouchHasInteractionTrigger;
			LootDropEntries = lootDropEntries;
			LootDropsWithDropper = lootDropsWithDropper;
			SkinDropEntries = skinDropEntries;
			SkinDropsWithDropper = skinDropsWithDropper;
			Lootable = lootable;
			Skinable = skinable;
		}
	}
	public static class CorpseLootDiagnosis
	{
		public static CorpseLootVerdict Classify(in CorpseLootObservation o)
		{
			if (!o.IsDead)
			{
				return CorpseLootVerdict.Alive;
			}
			if (!o.HasComponent)
			{
				return CorpseLootVerdict.NoLootableComponent;
			}
			if (!o.ComponentEnabled)
			{
				return CorpseLootVerdict.ComponentDisabled;
			}
			if (!o.HasPouch)
			{
				return CorpseLootVerdict.NoPouch;
			}
			if (!o.PouchHasInteractionTrigger)
			{
				return CorpseLootVerdict.NoPouchInteractionTrigger;
			}
			if (o.Lootable || o.Skinable)
			{
				return CorpseLootVerdict.Lootable;
			}
			if (o.LootDropsWithDropper > 0 || o.SkinDropsWithDropper > 0)
			{
				return CorpseLootVerdict.DropsPresentButInert;
			}
			return CorpseLootVerdict.NoDropsConfigured;
		}

		public static bool IsActionableDefect(CorpseLootVerdict v)
		{
			if ((uint)(v - 2) <= 3u)
			{
				return true;
			}
			return false;
		}

		public static string Explain(CorpseLootVerdict v)
		{
			return v switch
			{
				CorpseLootVerdict.Alive => "not dead yet — probe again after death", 
				CorpseLootVerdict.NoLootableComponent => "no LootableOnDeath component — this body never had corpse loot", 
				CorpseLootVerdict.ComponentDisabled => "LootableOnDeath is DISABLED -> OnDeath early-returns (ForceLootableEnabled re-enables it at mint)", 
				CorpseLootVerdict.NoPouch => "Inventory.Pouch is null -> the corpse cannot be made lootable", 
				CorpseLootVerdict.NoPouchInteractionTrigger => "pouch has no interaction trigger -> OnDeath early-returns before MakeLootable", 
				CorpseLootVerdict.DropsPresentButInert => "drop entries carry droppers yet m_lootable/m_skinable are false -> LootableOnDeath.Start did not populate (BUG)", 
				CorpseLootVerdict.NoDropsConfigured => "no loot AND no skin droppers -> empty undetectable pouch; this creature has no corpse loot by design (scripted/boss reward), NOT a SpawnKit regression", 
				CorpseLootVerdict.Lootable => "m_lootable/m_skinable set -> a normal loot/skin prompt is expected", 
				_ => "unknown", 
			};
		}
	}
	public enum CorpsePolicy
	{
		Vanilla,
		NoBody
	}
	public static class CorpseRules
	{
		public static CorpsePolicy EffectivePolicy(CorpsePolicy? requested, CorpsePolicy configured)
		{
			return requested ?? configured;
		}

		public static float EffectiveLinger(float? requested, float configured)
		{
			float num = requested ?? configured;
			if (!(num < 0f))
			{
				return num;
			}
			return 0f;
		}
	}
	public enum RemovalReason
	{
		SilentDespawn,
		Killed,
		WatchDied,
		WatchDespawned,
		CancelledPending
	}
	public static class DisengagePolicy
	{
		public static bool NeedsDisengage(RemovalReason reason)
		{
			return reason != RemovalReason.CancelledPending;
		}

		public static bool ByReference(bool exists)
		{
			return exists;
		}

		public static bool IsStaleEntry(bool entryExists, bool entryAlive)
		{
			if (entryExists)
			{
				return !entryAlive;
			}
			return true;
		}
	}
	public static class ExpeditionRow
	{
		public struct Buttons
		{
			public bool ShowPrewarm;

			public bool ShowWarmTrip;

			public bool Interactive;

			public string SpawnLabel;

			public bool SpawnCostsATrip;
		}

		public struct Arm
		{
			public string Key;

			public float At;
		}

		public struct ClickResult
		{
			public bool Fire;

			public Arm State;
		}

		public static Buttons For(bool bodyReady, bool expeditionOnly, bool expeditionsAllowed, bool tripInFlight, bool armed)
		{
			Buttons result = new Buttons
			{
				Interactive = !tripInFlight,
				SpawnLabel = "Spawn"
			};
			if (tripInFlight)
			{
				return result;
			}
			bool flag = expeditionOnly && !bodyReady && expeditionsAllowed;
			result.ShowPrewarm = !bodyReady && !expeditionOnly;
			result.ShowWarmTrip = flag;
			result.SpawnCostsATrip = flag;
			if (flag)
			{
				result.SpawnLabel = (armed ? "Confirm?" : "Spawn (trip)");
			}
			return result;
		}

		public static ClickResult Click(Arm current, string key, float now, float windowSeconds, bool confirmRequired)
		{
			if (!confirmRequired)
			{
				return new ClickResult
				{
					Fire = true,
					State = default(Arm)
				};
			}
			if (IsArmed(current, key, now, windowSeconds))
			{
				return new ClickResult
				{
					Fire = true,
					State = default(Arm)
				};
			}
			return new ClickResult
			{
				Fire = false,
				State = new Arm
				{
					Key = key,
					At = now
				}
			};
		}

		public static bool IsArmed(Arm current, string key, float now, float windowSeconds)
		{
			if (!string.IsNullOrEmpty(current.Key) && string.Equals(current.Key, key, StringComparison.OrdinalIgnoreCase) && now - current.At >= 0f)
			{
				return now - current.At <= windowSeconds;
			}
			return false;
		}
	}
	public enum FailReason
	{
		None,
		NotInitialized,
		Disabled,
		NotMaster,
		NoPlayer,
		NoAIManager,
		CapExceeded,
		UnknownSpecies,
		MintFailed,
		Cancelled,
		HarvestFailed,
		RoomWithGuests,
		PeersNotReady,
		SpeciesColdOnPeer
	}
	public struct ForgivenEntry
	{
		public int Actor;

		public string Uid;

		public string Species;

		public double At;
	}
	public sealed class ForgivenLedger
	{
		public const int MaxEntries = 64;

		private readonly List<ForgivenEntry> _entries = new List<ForgivenEntry>();

		public int Count => _entries.Count;

		private static string Norm(string? s)
		{
			if (s != null)
			{
				return s.Trim();
			}
			return "";
		}

		private int IndexOf(int actor, string normUid)
		{
			for (int i = 0; i < _entries.Count; i++)
			{
				if (_entries[i].Actor == actor && string.Equals(Norm(_entries[i].Uid), normUid, StringComparison.Ordinal))
				{
					return i;
				}
			}
			return -1;
		}

		public bool Add(int actor, string? uid, string? species, double now)
		{
			string text = Norm(uid);
			if (text.Length == 0)
			{
				return false;
			}
			if (IndexOf(actor, text) >= 0)
			{
				return true;
			}
			_entries.Add(new ForgivenEntry
			{
				Actor = actor,
				Uid = text,
				Species = Norm(species),
				At = now
			});
			while (_entries.Count > 64)
			{
				_entries.RemoveAt(0);
			}
			return true;
		}

		public List<ForgivenEntry> MatchWarm(int actor, string[]? warm)
		{
			List<ForgivenEntry> list = new List<ForgivenEntry>();
			for (int num = _entries.Count - 1; num >= 0; num--)
			{
				if (_entries[num].Actor == actor && SpeciesRoomPolicy.Contains(warm, _entries[num].Species))
				{
					list.Add(_entries[num]);
					_entries.RemoveAt(num);
				}
			}
			list.Reverse();
			return list;
		}

		public List<ForgivenEntry> TakeExpired(double now, double lifetimeSeconds)
		{
			List<ForgivenEntry> list = new List<ForgivenEntry>();
			if (lifetimeSeconds <= 0.0)
			{
				return list;
			}
			for (int num = _entries.Count - 1; num >= 0; num--)
			{
				if (!(now - _entries[num].At < lifetimeSeconds))
				{
					list.Add(_entries[num]);
					_entries.RemoveAt(num);
				}
			}
			list.Reverse();
			return list;
		}

		public void ForgetUid(string? uid)
		{
			string text = Norm(uid);
			if (text.Length == 0)
			{
				return;
			}
			for (int num = _entries.Count - 1; num >= 0; num--)
			{
				if (string.Equals(Norm(_entries[num].Uid), text, StringComparison.Ordinal))
				{
					_entries.RemoveAt(num);
				}
			}
		}

		public void ForgetActor(int actor)
		{
			for (int num = _entries.Count - 1; num >= 0; num--)
			{
				if (_entries[num].Actor == actor)
				{
					_entries.RemoveAt(num);
				}
			}
		}

		public void Clear()
		{
			_entries.Clear();
		}

		public List<ForgivenEntry> Snapshot()
		{
			return new List<ForgivenEntry>(_entries);
		}
	}
	public enum WantOutcome
	{
		Queued,
		AlreadyQueued,
		DeadEnd,
		Empty
	}
	public sealed class GuestHarvestStanding
	{
		private struct Entry
		{
			public string Key;

			public WantClass Class;
		}

		private static readonly StringComparer KeyComparer = StringComparer.OrdinalIgnoreCase;

		private readonly List<Entry> _wanted = new List<Entry>();

		private readonly List<string> _deadEnds = new List<string>();

		public int CyclesSpent { get; private set; }

		public int VoluntaryCyclesSpent { get; private set; }

		public bool Warming { get; set; }

		public int Generation { get; private set; }

		public int WantedCount => _wanted.Count;

		private static string Norm(string key)
		{
			if (key != null)
			{
				return key.Trim();
			}
			return "";
		}

		private static bool Holds(List<string> keys, string normKey)
		{
			for (int i = 0; i < keys.Count; i++)
			{
				if (KeyComparer.Equals(Norm(keys[i]), normKey))
				{
					return true;
				}
			}
			return false;
		}

		private int IndexOfWanted(string normKey)
		{
			for (int i = 0; i < _wanted.Count; i++)
			{
				if (KeyComparer.Equals(_wanted[i].Key, normKey))
				{
					return i;
				}
			}
			return -1;
		}

		public bool IsCurrent(int generation)
		{
			return generation == Generation;
		}

		public bool ReleaseWarm(int generation)
		{
			if (!IsCurrent(generation))
			{
				return false;
			}
			Warming = false;
			return true;
		}

		public List<string> Wanted()
		{
			List<string> list = new List<string>(_wanted.Count);
			for (int i = 0; i < _wanted.Count; i++)
			{
				list.Add(_wanted[i].Key);
			}
			return list;
		}

		public List<KeyValuePair<string, WantClass>> WantedWithClass()
		{
			List<KeyValuePair<string, WantClass>> list = new List<KeyValuePair<string, WantClass>>(_wanted.Count);
			for (int i = 0; i < _wanted.Count; i++)
			{
				list.Add(new KeyValuePair<string, WantClass>(_wanted[i].Key, _wanted[i].Class));
			}
			return list;
		}

		public bool TryGetClass(string species, out WantClass cls)
		{
			int num = IndexOfWanted(Norm(species));
			cls = ((num >= 0) ? _wanted[num].Class : WantClass.HostWant);
			return num >= 0;
		}

		public List<string> DeadEnds()
		{
			return new List<string>(_deadEnds);
		}

		public WantOutcome Want(string species, bool front)
		{
			return Want(species, front, WantClass.HostWant);
		}

		public WantOutcome Want(string species, bool front, WantClass cls)
		{
			string text = Norm(species);
			if (text.Length == 0)
			{
				return WantOutcome.Empty;
			}
			if (Holds(_deadEnds, text))
			{
				return WantOutcome.DeadEnd;
			}
			if (IndexOfWanted(text) >= 0)
			{
				return WantOutcome.AlreadyQueued;
			}
			Entry item = new Entry
			{
				Key = text,
				Class = cls
			};
			if (front)
			{
				_wanted.Insert(0, item);
			}
			else
			{
				_wanted.Add(item);
			}
			return WantOutcome.Queued;
		}

		public bool TryTakeNext(out string species)
		{
			WantClass cls;
			return TryTakeNext(out species, out cls);
		}

		public bool TryTakeNext(out string species, out WantClass cls)
		{
			if (!TryPeekNext(out species, out cls))
			{
				return false;
			}
			_wanted.RemoveAt(0);
			return true;
		}

		public bool TryPeekAt(int index, out string species, out WantClass cls)
		{
			if (index < 0 || index >= _wanted.Count)
			{
				species = null;
				cls = WantClass.HostWant;
				return false;
			}
			species = _wanted[index].Key;
			cls = _wanted[index].Class;
			return true;
		}

		public bool RemoveAt(int index)
		{
			if (index < 0 || index >= _wanted.Count)
			{
				return false;
			}
			_wanted.RemoveAt(index);
			return true;
		}

		public bool TryPeekNext(out string species, out WantClass cls)
		{
			if (_wanted.Count == 0)
			{
				species = null;
				cls = WantClass.HostWant;
				return false;
			}
			species = _wanted[0].Key;
			cls = _wanted[0].Class;
			return true;
		}

		public void MarkDeadEnd(string species)
		{
			string text = Norm(species);
			if (text.Length != 0 && !Holds(_deadEnds, text))
			{
				_deadEnds.Add(text);
			}
		}

		public void ChargeCycles(int delta)
		{
			ChargeCycles(delta, WantClass.HostWant);
		}

		public void ChargeCycles(int delta, WantClass cls)
		{
			if (delta > 0)
			{
				if (cls == WantClass.Voluntary)
				{
					VoluntaryCyclesSpent += delta;
				}
				else
				{
					CyclesSpent += delta;
				}
			}
		}

		public void ClearWanted()
		{
			_wanted.Clear();
		}

		public int ClearWanted(WantClass cls)
		{
			int num = 0;
			for (int num2 = _wanted.Count - 1; num2 >= 0; num2--)
			{
				if (_wanted[num2].Class == cls)
				{
					_wanted.RemoveAt(num2);
					num++;
				}
			}
			return num;
		}

		public void Reset()
		{
			_wanted.Clear();
			_deadEnds.Clear();
			CyclesSpent = 0;
			VoluntaryCyclesSpent = 0;
			Warming = false;
			Generation++;
		}
	}
	public enum WantClass
	{
		HostWant,
		Voluntary
	}
	public enum GuestWarmVerdict
	{
		Warm,
		RefuseBudget,
		RefuseCeiling,
		RefuseAdmission,
		RefuseCombat,
		RefuseDisabled,
		NotGuest
	}
	public static class GuestWarmPolicy
	{
		public static bool Unlimited(int budget)
		{
			return budget <= 0;
		}

		public static GuestWarmVerdict Decide(WantClass cls, int cyclesSpent, int mirrorBudget, int voluntarySpent, int voluntaryBudget, int sessionCycles, int sessionCeiling, bool admissionOk, bool inCombat, bool guestPrewarmEnabled, bool isGuest)
		{
			if (!isGuest)
			{
				return GuestWarmVerdict.NotGuest;
			}
			if (cls == WantClass.Voluntary && !guestPrewarmEnabled)
			{
				return GuestWarmVerdict.RefuseDisabled;
			}
			if (!Unlimited(sessionCeiling) && sessionCycles >= sessionCeiling)
			{
				return GuestWarmVerdict.RefuseCeiling;
			}
			if (cls == WantClass.Voluntary)
			{
				if (!Unlimited(voluntaryBudget) && voluntarySpent >= voluntaryBudget)
				{
					return GuestWarmVerdict.RefuseBudget;
				}
			}
			else if (!Unlimited(mirrorBudget) && cyclesSpent >= mirrorBudget)
			{
				return GuestWarmVerdict.RefuseBudget;
			}
			if (!admissionOk)
			{
				return GuestWarmVerdict.RefuseAdmission;
			}
			if (inCombat)
			{
				return GuestWarmVerdict.RefuseCombat;
			}
			return GuestWarmVerdict.Warm;
		}

		public static string Reason(GuestWarmVerdict v, WantClass cls)
		{
			switch (v)
			{
			case GuestWarmVerdict.Warm:
				return "";
			case GuestWarmVerdict.RefuseBudget:
				if (cls != WantClass.Voluntary)
				{
					return "mirror-budget";
				}
				return "voluntary-budget";
			case GuestWarmVerdict.RefuseCeiling:
				return "session-ceiling";
			case GuestWarmVerdict.RefuseAdmission:
				return "admission";
			case GuestWarmVerdict.RefuseCombat:
				return "in-combat";
			case GuestWarmVerdict.RefuseDisabled:
				return "disabled";
			case GuestWarmVerdict.NotGuest:
				return "not-guest";
			default:
				return v.ToString();
			}
		}

		public static bool IsTransient(GuestWarmVerdict v)
		{
			if (v != GuestWarmVerdict.RefuseAdmission)
			{
				return v == GuestWarmVerdict.RefuseCombat;
			}
			return true;
		}
	}
	public enum CursorStrategy
	{
		None,
		GameSeamReleased,
		VanillaMenuOwns
	}
	public static class MenuCursorPolicy
	{
		public static bool EngageSeam(bool menuOpen, bool vanillaMenuFocused)
		{
			if (menuOpen)
			{
				return !vanillaMenuFocused;
			}
			return false;
		}

		public static bool ForceClose(bool menuOpen, bool vanillaMenuFocused)
		{
			return menuOpen && vanillaMenuFocused;
		}

		public static bool CanOpen(bool vanillaMenuFocused)
		{
			return !vanillaMenuFocused;
		}

		public static bool CloseOnEsc(bool menuOpen, bool escPressed)
		{
			return menuOpen && escPressed;
		}

		public static CursorStrategy Resolve(bool menuOpen, bool vanillaMenuFocused)
		{
			if (!menuOpen)
			{
				return CursorStrategy.None;
			}
			if (!vanillaMenuFocused)
			{
				return CursorStrategy.GameSeamReleased;
			}
			return CursorStrategy.VanillaMenuOwns;
		}

		public static CursorStrategy ResolveLog(bool nowOpen, bool vanillaMenuFocused)
		{
			if (vanillaMenuFocused)
			{
				return CursorStrategy.VanillaMenuOwns;
			}
			if (!nowOpen)
			{
				return CursorStrategy.None;
			}
			return CursorStrategy.GameSeamReleased;
		}
	}
	public static class MintHeal
	{
		public struct SettleState
		{
			public float PrevMax;

			public float StartMax;

			public float StartHp;

			public int StableTicks;

			public int Ticks;

			public float Elapsed;

			public bool SawChange;

			public bool IsStable => StableTicks >= 3;

			public static SettleState Start(float prevMax)
			{
				return new SettleState
				{
					PrevMax = prevMax,
					StartMax = prevMax,
					StartHp = -1f
				};
			}
		}

		public const float SettleBudget = 1f;

		public const float Tick = 0.1f;

		public const int StableTicks = 3;

		public static float Initial(float cur, float max)
		{
			if (!(max > 0f))
			{
				return -1f;
			}
			if (cur >= max)
			{
				return -1f;
			}
			return max;
		}

		public static float Next(float prevMax, float cur, float max)
		{
			if (!(max > 0f))
			{
				return -1f;
			}
			if (max == prevMax)
			{
				return -1f;
			}
			float num = cur + (max - prevMax);
			if (num > max)
			{
				num = max;
			}
			if (num < 0f)
			{
				num = 0f;
			}
			return num;
		}

		public static bool Stable(float prevMax, float max)
		{
			return max == prevMax;
		}

		public static bool Done(int stableTicks, float elapsed, bool sawChange)
		{
			if (!(elapsed >= 1f))
			{
				if (sawChange)
				{
					return stableTicks >= 3;
				}
				return false;
			}
			return true;
		}

		public static float Step(ref SettleState s, float cur, float max)
		{
			s.Elapsed += 0.1f;
			s.Ticks++;
			if (s.StartHp < 0f)
			{
				s.StartHp = cur;
			}
			if (Stable(s.PrevMax, max))
			{
				s.StableTicks++;
			}
			else
			{
				s.StableTicks = 0;
				s.SawChange = true;
			}
			float result = Next(s.PrevMax, cur, max);
			s.PrevMax = max;
			return result;
		}

		public static bool Done(in SettleState s)
		{
			return Done(s.StableTicks, s.Elapsed, s.SawChange);
		}
	}
	public enum PublishPayload
	{
		ReusePollBuffer,
		EncodeFresh
	}
	public static class MirrorEncodeReuse
	{
		public static PublishPayload Choose(bool hasBuffer, double bufferedAt, double now)
		{
			if (!hasBuffer || bufferedAt != now)
			{
				return PublishPayload.EncodeFresh;
			}
			return PublishPayload.ReusePollBuffer;
		}
	}
	public static class MirrorFailPolicy
	{
		public static bool IsBenign(string? reason)
		{
			return string.Equals(reason, "not-ready", StringComparison.Ordinal);
		}

		public static bool ShouldDespawnColdUnsafe(bool roomGateOk, int priorColdUnsafeCount)
		{
			if (roomGateOk)
			{
				return priorColdUnsafeCount >= 1;
			}
			return true;
		}
	}
	public sealed class MirrorInFlight
	{
		private readonly HashSet<string> _inFlight = new HashSet<string>(StringComparer.Ordinal);

		private readonly Dictionary<string, SpawnNetProtocol.GoneKind> _tombstones = new Dictionary<string, SpawnNetProtocol.GoneKind>(StringComparer.Ordinal);

		public int InFlightCount => _inFlight.Count;

		public int TombstoneCount => _tombstones.Count;

		public void Begin(string uid)
		{
			if (!string.IsNullOrEmpty(uid))
			{
				_inFlight.Add(uid);
			}
		}

		public void Finish(string uid)
		{
			if (!string.IsNullOrEmpty(uid))
			{
				_inFlight.Remove(uid);
				_tombstones.Remove(uid);
			}
		}

		public bool IsInFlight(string uid)
		{
			if (!string.IsNullOrEmpty(uid))
			{
				return _inFlight.Contains(uid);
			}
			return false;
		}

		public bool RecordGone(string uid, SpawnNetProtocol.GoneKind kind)
		{
			if (!IsInFlight(uid))
			{
				return false;
			}
			if (_tombstones.TryGetValue(uid, out var value) && Rank(value) >= Rank(kind))
			{
				return true;
			}
			_tombstones[uid] = kind;
			return true;
		}

		public bool TryPeek(string uid, out SpawnNetProtocol.GoneKind kind)
		{
			if (!string.IsNullOrEmpty(uid) && _tombstones.TryGetValue(uid, out kind))
			{
				return true;
			}
			kind = SpawnNetProtocol.GoneKind.Despawned;
			return false;
		}

		public void Clear()
		{
			_inFlight.Clear();
			_tombstones.Clear();
		}

		private static int Rank(SpawnNetProtocol.GoneKind kind)
		{
			if (kind != SpawnNetProtocol.GoneKind.Died)
			{
				return 2;
			}
			return 1;
		}
	}
	public static class ReplicaShape
	{
		public enum ViewSyncMode
		{
			Unknown,
			Off,
			Unreliable,
			Other
		}

		public struct ShapeCensus
		{
			public bool HasCharacterAI;

			public int NccCount;

			public bool NccEnabled;

			public bool CloseToPlayer;

			public bool HasCharAIDisable;

			public bool AgentPresent;

			public bool AgentEnabled;

			public bool AgentUpdatePosition;

			public bool AgentUpdateRotation;

			public int ActiveAiRoots;

			public bool CcPresent;

			public bool CcEnabled;

			public ViewSyncMode SyncMode;

			public bool IsAI;
		}

		[Flags]
		public enum ShapeFix
		{
			None = 0,
			KillStaleNcc = 1,
			AddFreshNcc = 2,
			EnableNcc = 4,
			SetCloseToPlayer = 8,
			DisableAgent = 0x10,
			DeactivateAiRoots = 0x20,
			EnableCc = 0x40,
			KillCharacterAI = 0x80,
			ForceUnreliableSync = 0x100
		}

		public enum ShapeVerdict
		{
			Healthy,
			Corrected,
			RebuiltDrive
		}

		public struct ShapePlan
		{
			public ShapeVerdict Verdict;

			public ShapeFix Fixes;

			public bool AiGateMissing;
		}

		public struct GateEval
		{
			public bool OuterGate;

			public bool FullGate;

			public bool SyncOk;

			public bool ConvergedOk;
		}

		public enum InitNudge
		{
			None,
			EnableAndInitWanted,
			DirectEquipFlag
		}

		public const int InitNudgeEscalateAttempts = 3;

		public static ShapePlan DecideTemplateClone(ShapeCensus c)
		{
			if (c.HasCharacterAI)
			{
				if (c.NccCount == 0)
				{
					return new ShapePlan
					{
						Verdict = ShapeVerdict.Healthy,
						Fixes = ShapeFix.None
					};
				}
				return new ShapePlan
				{
					Verdict = ShapeVerdict.Corrected,
					Fixes = ShapeFix.KillStaleNcc
				};
			}
			ShapeFix shapeFix = ShapeFix.AddFreshNcc | ShapeFix.SetCloseToPlayer;
			if (c.NccCount > 0)
			{
				shapeFix |= ShapeFix.KillStaleNcc;
			}
			if (c.AgentPresent && (c.AgentEnabled || c.AgentUpdatePosition || c.AgentUpdateRotation))
			{
				shapeFix |= ShapeFix.DisableAgent;
			}
			if (c.ActiveAiRoots > 0)
			{
				shapeFix |= ShapeFix.DeactivateAiRoots;
			}
			if (c.CcPresent && !c.CcEnabled)
			{
				shapeFix |= ShapeFix.EnableCc;
			}
			return new ShapePlan
			{
				Verdict = ShapeVerdict.RebuiltDrive,
				Fixes = shapeFix
			};
		}

		public static ShapePlan DecideActiveReplica(ShapeCensus c)
		{
			ShapeFix shapeFix = ShapeFix.None;
			bool flag = false;
			if (c.HasCharacterAI)
			{
				shapeFix |= ShapeFix.KillCharacterAI;
				flag = true;
			}
			if (c.NccCount == 0)
			{
				shapeFix |= ShapeFix.AddFreshNcc | ShapeFix.SetCloseToPlayer;
				flag = true;
			}
			else
			{
				if (c.NccCount > 1)
				{
					shapeFix |= ShapeFix.KillStaleNcc;
					flag = true;
				}
				if (!c.NccEnabled)
				{
					shapeFix |= ShapeFix.EnableNcc;
				}
				if (!c.CloseToPlayer && !c.HasCharAIDisable)
				{
					shapeFix |= ShapeFix.SetCloseToPlayer;
				}
			}
			if (c.AgentPresent && (c.AgentEnabled || c.AgentUpdatePosition || c.AgentUpdateRotation))
			{
				shapeFix |= ShapeFix.DisableAgent;
			}
			if (c.ActiveAiRoots > 0)
			{
				shapeFix |= ShapeFix.DeactivateAiRoots;
			}
			if (c.CcPresent && !c.CcEnabled)
			{
				shapeFix |= ShapeFix.EnableCc;
			}
			if (c.SyncMode == ViewSyncMode.Off)
			{
				shapeFix |= ShapeFix.ForceUnreliableSync;
			}
			ShapeVerdict verdict = ((shapeFix != ShapeFix.None) ? ((!flag) ? ShapeVerdict.Corrected : ShapeVerdict.RebuiltDrive) : ShapeVerdict.Healthy);
			return new ShapePlan
			{
				Verdict = verdict,
				Fixes = shapeFix,
				AiGateMissing = !c.IsAI
			};
		}

		public static GateEval EvalGates(bool isAI, bool sendInit, bool loadDone, bool init, bool closeToPlayer, bool ccEnabled, ViewSyncMode sync)
		{
			bool flag = isAI || sendInit || !loadDone;
			bool flag2 = init && loadDone && closeToPlayer && ccEnabled;
			bool flag3 = sync == ViewSyncMode.Unreliable;
			return new GateEval
			{
				OuterGate = flag,
				FullGate = flag2,
				SyncOk = flag3,
				ConvergedOk = (flag && flag2 && flag3)
			};
		}

		public static InitNudge DecideInitNudge(bool initialized, bool equipInit, bool hasStartingEquipment, int attempts)
		{
			if (initialized || equipInit)
			{
				return InitNudge.None;
			}
			if (!hasStartingEquipment)
			{
				return InitNudge.DirectEquipFlag;
			}
			if (attempts < 3)
			{
				return InitNudge.EnableAndInitWanted;
			}
			return InitNudge.DirectEquipFlag;
		}

		public static string Describe(ShapeCensus c)
		{
			return string.Format("charAI={0} ncc={1}(en={2} ctp={3}) ", c.HasCharacterAI ? "T" : "F", c.NccCount, c.NccEnabled ? "T" : "F", c.CloseToPlayer ? "T" : "F") + "caid=" + (c.HasCharAIDisable ? "T" : "F") + " agent=" + ((!c.AgentPresent) ? "none" : (c.AgentEnabled ? "ON" : "off")) + "/updPos=" + (c.AgentUpdatePosition ? "T" : "F") + "/updRot=" + (c.AgentUpdateRotation ? "T" : "F") + " " + string.Format("aiRootsActive={0} cc={1}", c.ActiveAiRoots, (!c.CcPresent) ? "none" : (c.CcEnabled ? "on" : "OFF"));
		}
	}
	public static class RigGate
	{
		public struct RigCensus
		{
			public int LiveHitboxes;

			public int CapturedHitboxes;

			public bool HasRagdollRoot;

			public bool RagdollIsHitbox;

			public int RagdollHitboxColliders;

			public int AttackTransforms;

			public int InactiveRigObjects;

			public bool LockingPointAsleep;

			public int RagdollRigidbodyCache;

			public int RagdollJointManagers;

			public int RagdollManagersWithJoint;

			public bool StartInitMissing;
		}

		public static bool NeedsReinit(in RigCensus c, out string reason)
		{
			if (c.StartInitMissing)
			{
				reason = "vanilla init never ran (Character.Start skipped)";
				return true;
			}
			if (c.LiveHitboxes > c.CapturedHitboxes)
			{
				reason = $"hitboxes uncollected ({c.CapturedHitboxes}/{c.LiveHitboxes} captured)";
				return true;
			}
			if (c.LiveHitboxes > 0 && c.CapturedHitboxes <= 0)
			{
				reason = "no hitboxes captured";
				return true;
			}
			if (c.HasRagdollRoot && c.RagdollIsHitbox && c.RagdollHitboxColliders <= 0)
			{
				reason = "ragdoll hitbox colliders empty";
				return true;
			}
			if (c.AttackTransforms == 0)
			{
				reason = "no attack transforms";
				return true;
			}
			if (c.InactiveRigObjects > 0)
			{
				reason = $"{c.InactiveRigObjects} inactive rig object(s)";
				return true;
			}
			if (c.LockingPointAsleep)
			{
				reason = "locking point asleep";
				return true;
			}
			reason = "healthy";
			return false;
		}

		public static bool NeedsRagdollInit(in RigCensus c, out string reason)
		{
			if (!c.HasRagdollRoot)
			{
				reason = "no ragdoll root";
				return false;
			}
			if (c.RagdollRigidbodyCache <= 0 && c.RagdollJointManagers <= 0)
			{
				reason = "ragdoll caches empty";
				return true;
			}
			if (c.RagdollJointManagers > 0 && c.RagdollManagersWithJoint <= 0)
			{
				reason = $"ragdoll already built but jointless ({c.RagdollJointManagers} managers) — NOT repairable";
				return false;
			}
			reason = $"ragdoll already built ({c.RagdollJointManagers} managers, {c.RagdollManagersWithJoint} jointed)";
			return false;
		}
	}
	public static class RingPlacement
	{
		public const float FarRingFactor = 1.6f;

		public const float StepDegrees = 45f;

		public static IReadOnlyList<(float x, float z)> Candidates(float forwardX, float forwardZ, float distance, int count)
		{
			if (count <= 0)
			{
				return Array.Empty<(float, float)>();
			}
			if (distance <= 0f)
			{
				throw new ArgumentOutOfRangeException("distance");
			}
			float num = (float)Math.Sqrt(forwardX * forwardX + forwardZ * forwardZ);
			float num2;
			float num3;
			if (num < 0.0001f)
			{
				num2 = 0f;
				num3 = 1f;
			}
			else
			{
				num2 = forwardX / num;
				num3 = forwardZ / num;
			}
			List<(float, float)> list = new List<(float, float)>(count);
			int num4 = (int)Math.Round(8.0);
			for (int i = 0; i < count; i++)
			{
				int num5 = i % num4;
				float num6 = ((i < num4) ? distance : (distance * 1.6f));
				double num7 = (double)((num5 == 0) ? 0f : ((num5 == num4 - 1) ? 180f : (45f * (float)((num5 + 1) / 2) * ((num5 % 2 == 1) ? 1f : (-1f))))) * Math.PI / 180.0;
				float num8 = (float)Math.Cos(num7);
				float num9 = (float)Math.Sin(num7);
				float num10 = num2 * num8 + num3 * num9;
				float num11 = (0f - num2) * num9 + num3 * num8;
				list.Add((num10 * num6, num11 * num6));
			}
			return list;
		}
	}
	public struct WarmRequest
	{
		public int Actor;

		public string Species;
	}
	public static class RoomWarmRequest
	{
		private static readonly StringComparer KeyComparer = StringComparer.OrdinalIgnoreCase;

		private static string Norm(string? key)
		{
			if (key != null)
			{
				return key.Trim();
			}
			return "";
		}

		public static List<WarmRequest> Plan(IReadOnlyList<string>? prioritySpecies, IReadOnlyList<PeerWarmRow>? peers, int perPeerCap)
		{
			return Plan(prioritySpecies, peers, perPeerCap, null);
		}

		public static List<WarmRequest> Plan(IReadOnlyList<string>? prioritySpecies, IReadOnlyList<PeerWarmRow>? peers, int perPeerCap, Func<int, string, bool>? isAbandoned)
		{
			List<WarmRequest> list = new List<WarmRequest>();
			if (prioritySpecies == null || prioritySpecies.Count == 0)
			{
				return list;
			}
			if (peers == null || peers.Count == 0)
			{
				return list;
			}
			List<string> list2 = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(KeyComparer);
			for (int i = 0; i < prioritySpecies.Count; i++)
			{
				string text = Norm(prioritySpecies[i]);
				if (text.Length != 0 && hashSet.Add(text))
				{
					list2.Add(text);
				}
			}
			if (list2.Count == 0)
			{
				return list;
			}
			Dictionary<int, int> dictionary = new Dictionary<int, int>();
			for (int j = 0; j < list2.Count; j++)
			{
				string text2 = list2[j];
				for (int k = 0; k < peers.Count; k++)
				{
					PeerWarmRow peerWarmRow = peers[k];
					if (peerWarmRow.Status != PeerWarmStatus.Participating || SpeciesRoomPolicy.Contains(peerWarmRow.Warm, text2) || SpeciesRoomPolicy.Contains(peerWarmRow.DeadEnds, text2) || peerWarmRow.RemainingBudget <= 0)
					{
						continue;
					}
					if (isAbandoned != null)
					{
						bool flag;
						try
						{
							flag = isAbandoned(peerWarmRow.Actor, text2);
						}
						catch
						{
							flag = false;
						}
						if (flag)
						{
							continue;
						}
					}
					dictionary.TryGetValue(peerWarmRow.Actor, out var value);
					if (perPeerCap <= 0 || value < perPeerCap)
					{
						dictionary[peerWarmRow.Actor] = value + 1;
						list.Add(new WarmRequest
						{
							Actor = peerWarmRow.Actor,
							Species = text2
						});
					}
				}
			}
			return list;
		}
	}
	public static class RootScale
	{
		public const float DegenerateBelow = 0.1f;

		public static bool IsDegenerate(float v)
		{
			return Math.Abs(v) < 0.1f;
		}

		public static bool NeedsFix(float x, float y, float z)
		{
			if (!IsDegenerate(x) && !IsDegenerate(y))
			{
				return IsDegenerate(z);
			}
			return true;
		}

		public static void Fix(float x, float y, float z, out float ox, out float oy, out float oz)
		{
			float num = 0f;
			if (!IsDegenerate(x) && Math.Abs(x) > num)
			{
				num = Math.Abs(x);
			}
			if (!IsDegenerate(y) && Math.Abs(y) > num)
			{
				num = Math.Abs(y);
			}
			if (!IsDegenerate(z) && Math.Abs(z) > num)
			{
				num = Math.Abs(z);
			}
			if (num <= 0f)
			{
				num = 1f;
			}
			ox = (IsDegenerate(x) ? num : x);
			oy = (IsDegenerate(y) ? num : y);
			oz = (IsDegenerate(z) ? num : z);
		}
	}
	public static class SpawnCap
	{
		public const int DefaultCacheWarnThreshold = 8;

		public static IReadOnlyList<string> PickEvict(IReadOnlyList<string> lruOrder, int count, int max, ICollection<string>? exclude = null)
		{
			List<string> list = new List<string>();
			if (max <= 0 || count <= max)
			{
				return list;
			}
			int num = count - max;
			for (int i = 0; i < lruOrder.Count; i++)
			{
				if (list.Count >= num)
				{
					break;
				}
				string item = lruOrder[i];
				if (exclude == null || !exclude.Contains(item))
				{
					list.Add(item);
				}
			}
			return list;
		}

		public static bool ShouldWarnCacheSize(int count, int threshold)
		{
			if (threshold > 0 && count > 0)
			{
				return count % threshold == 0;
			}
			return false;
		}

		public static bool IsCacheLarge(int count, int threshold)
		{
			if (threshold > 0)
			{
				return count >= threshold;
			}
			return false;
		}
	}
	public sealed class SpawnCountArgs
	{
		public string Species = "";

		public int Count = 1;

		public static SpawnCountArgs Parse(string[] tokens)
		{
			SpawnCountArgs spawnCountArgs = new SpawnCountArgs();
			int num = 1;
			int num2 = tokens.Length;
			if (num2 - num > 1 && int.TryParse(tokens[num2 - 1], out var result) && result > 0)
			{
				spawnCountArgs.Count = Math.Min(result, 99);
				num2--;
			}
			spawnCountArgs.Species = string.Join(" ", tokens, num, Math.Max(0, num2 - num)).Trim();
			return spawnCountArgs;
		}
	}
	public static class SpawnMenuLabels
	{
		public static bool IsDonorMismatch(string key, string donorName)
		{
			if (!string.IsNullOrEmpty(donorName) && !string.IsNullOrEmpty(key))
			{
				return !string.Equals(key.Trim(), donorName.Trim(), StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		public static string RowLabel(string key, string resolvedDonorName, bool expeditionOnly)
		{
			string text = key ?? "";
			if (IsDonorMismatch(key, resolvedDonorName))
			{
				text = text + "  → spawns “" + resolvedDonorName.Trim() + "”";
			}
			if (expeditionOnly)
			{
				text += "  (expedition-only — see log)";
			}
			return text;
		}

		public static string RoomGlyph(bool localWarm, int participating, int coldOnPeers)
		{
			if (!localWarm)
			{
				return "○";
			}
			if (participating <= 0 || coldOnPeers <= 0)
			{
				return "●";
			}
			return "◐";
		}

		public static string OwnerBreakdown(IEnumerable<string?>? ownerTags)
		{
			if (ownerTags == null)
			{
				return "";
			}
			Dictionary<string, int> counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			Dictionary<string, int> seenAt = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			List<string> list = new List<string>();
			foreach (string ownerTag in ownerTags)
			{
				string text = SpawnPolicy.NormalizeOwnerTag(ownerTag);
				if (text.Length == 0)
				{
					text = "(untagged)";
				}
				if (counts.ContainsKey(text))
				{
					counts[text]++;
					continue;
				}
				counts[text] = 1;
				seenAt[text] = list.Count;
				list.Add(text);
			}
			if (list.Count == 0)
			{
				return "";
			}
			list.Sort(delegate(string a, string b)
			{
				int num = counts[b].CompareTo(counts[a]);
				return (num == 0) ? seenAt[a].CompareTo(seenAt[b]) : num;
			});
			StringBuilder stringBuilder = new StringBuilder();
			foreach (string item in list)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(item).Append(' ').Append(counts[item]);
			}
			return stringBuilder.ToString();
		}
	}
	public static class SpawnNetProtocol
	{
		public struct SpawnMsg
		{
			public int Proto;

			public string SpeciesKey;

			public string Uid;

			public int ViewId;

			public string Scene;

			public float X;

			public float Y;

			public float Z;

			public float YawDeg;

			public int Faction;

			public bool StripQuestEvents;

			public string ConsumerData;

			public int RightHandItemId;

			public string RightHandItemUid;

			public int LeftHandItemId;

			public string LeftHandItemUid;
		}

		public enum GoneKind
		{
			Died,
			Despawned,
			Corpse
		}

		public struct GoneMsg
		{
			public string Uid;

			public GoneKind Kind;
		}

		public struct AckMsg
		{
			public string Uid;

			public string Source;

			public int TookMs;
		}

		public struct FailMsg
		{
			public string Uid;

			public string Reason;
		}

		public enum MirrorGate
		{
			Mirror,
			DropDuplicate,
			DropTimeout,
			WaitNotReady,
			WaitSceneMismatch,
			DropNotReadyTimeout,
			RefuseColdUnsafe
		}

		public const int Version = 1;

		public const string VerbSpawn = "sk.spawn";

		public const string VerbGone = "sk.gone";

		public const string VerbCorpse = "sk.corpse";

		public const string VerbAck = "sk.ack";

		public const string VerbFail = "sk.fail";

		public const string VerbResync = "sk.resync";

		public const string VerbTest = "sk.test";

		public const string VerbWarmSet = "sk.warmset";

		public const string VerbWarmClr = "sk.warmclr";

		public const string VerbWant = "sk.want";

		public const string StoreReleaseReasonPrefix = "owner release: ";

		public const string FailReasonNotReady = "not-ready";

		public const string FailReasonColdUnsafe = "cold-unsafe";

		public static string EncodeSpawn(SpawnMsg m)
		{
			return Join(I(m.Proto), Esc(m.SpeciesKey), Esc(m.Uid), I(m.ViewId), Esc(m.Scene), F(m.X), F(m.Y), F(m.Z), F(m.YawDeg), I(m.Faction), m.StripQuestEvents ? "1" : "0", Esc(m.ConsumerData), I(m.RightHandItemId), Esc(m.RightHandItemUid), I(m.LeftHandItemId), Esc(m.LeftHandItemUid));
		}

		public static string EncodeGone(GoneMsg m)
		{
			return Join(Esc(m.Uid), I((int)m.Kind));
		}

		public static string EncodeAck(AckMsg m)
		{
			return Join(Esc(m.Uid), Esc(m.Source), I(m.TookMs));
		}

		public static string EncodeFail(FailMsg m)
		{
			return Join(Esc(m.Uid), Esc(m.Reason));
		}

		public static bool TryDecodeSpawn(string payload, out SpawnMsg m)
		{
			m = default(SpawnMsg);
			List<string> list = Split(payload);
			if (list == null || list.Count < 11)
			{
				return false;
			}
			if (!TryI(list[0], out m.Proto))
			{
				return false;
			}
			m.SpeciesKey = list[1];
			m.Uid = list[2];
			if (!TryI(list[3], out m.ViewId))
			{
				return false;
			}
			m.Scene = list[4];
			if (!TryF(list[5], out m.X) || !TryF(list[6], out m.Y) || !TryF(list[7], out m.Z))
			{
				return false;
			}
			if (!TryF(list[8], out m.YawDeg))
			{
				return false;
			}
			if (!TryI(list[9], out m.Faction))
			{
				return false;
			}
			m.StripQuestEvents = list[10] != "0";
			m.ConsumerData = ((list.Count >= 12) ? list[11] : "");
			if (list.Count >= 13 && TryI(list[12], out var v))
			{
				m.RightHandItemId = v;
			}
			m.RightHandItemUid = ((list.Count >= 14) ? list[13] : "");
			if (list.Count >= 15 && TryI(list[14], out var v2))
			{
				m.LeftHandItemId = v2;
			}
			m.LeftHandItemUid = ((list.Count >= 16) ? list[15] : "");
			if (!string.IsNullOrEmpty(m.Uid) && m.ViewId > 0)
			{
				return !string.IsNullOrEmpty(m.SpeciesKey);
			}
			return false;
		}

		public static bool TryDecodeGone(string payload, out GoneMsg m)
		{
			m = default(GoneMsg);
			List<string> list = Split(payload);
			if (list == null || list.Count < 2)
			{
				return false;
			}
			m.Uid = list[0];
			if (!TryI(list[1], out var v))
			{
				return false;
			}
			if (v < 0 || v > 2)
			{
				return false;
			}
			m.Kind = (GoneKind)v;
			return !string.IsNullOrEmpty(m.Uid);
		}

		public static bool TryDecodeAck(string payload, out AckMsg m)
		{
			m = default(AckMsg);
			List<string> list = Split(payload);
			if (list == null || list.Count < 3)
			{
				return false;
			}
			m.Uid = list[0];
			m.Source = list[1];
			if (!TryI(list[2], out m.TookMs))
			{
				return false;
			}
			return !string.IsNullOrEmpty(m.Uid);
		}

		public static bool TryParseGoneReason(string reason, out GoneMsg m)
		{
			m = default(GoneMsg);
			if (string.IsNullOrEmpty(reason))
			{
				return false;
			}
			return TryDecodeGone(reason.StartsWith("owner release: ", StringComparison.Ordinal) ? reason.Substring("owner release: ".Length) : reason, out m);
		}

		public static bool TryDecodeFail(string payload, out FailMsg m)
		{
			m = default(FailMsg);
			List<string> list = Split(payload);
			if (list == null || list.Count < 2)
			{
				return false;
			}
			m.Uid = list[0];
			m.Reason = list[1];
			return !string.IsNullOrEmpty(m.Uid);
		}

		public static MirrorGate DecideMirror(bool alreadyKnown, bool playerReady, bool loadingDone, string activeScene, string payloadScene, float queuedSeconds, float timeoutSeconds, bool templateCached = true, bool harvestSafe = true)
		{
			if (alreadyKnown)
			{
				return MirrorGate.DropDuplicate;
			}
			bool flag = playerReady && loadingDone;
			bool flag2 = string.Equals(activeScene ?? "", payloadScene ?? "", StringComparison.Ordinal);
			if (queuedSeconds > timeoutSeconds)
			{
				if (!(flag && flag2))
				{
					return MirrorGate.DropNotReadyTimeout;
				}
				return MirrorGate.DropTimeout;
			}
			if (!flag)
			{
				return MirrorGate.WaitNotReady;
			}
			if (!flag2)
			{
				return MirrorGate.WaitSceneMismatch;
			}
			if (!templateCached && !harvestSafe)
			{
				return MirrorGate.RefuseColdUnsafe;
			}
			return MirrorGate.Mirror;
		}

		private static string I(int v)
		{
			return v.ToString(CultureInfo.InvariantCulture);
		}

		private static string F(float v)
		{
			return v.ToString("R", CultureInfo.InvariantCulture);
		}

		private static bool TryI(string s, out int v)
		{
			return int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v);
		}

		private static bool TryF(string s, out float v)
		{
			return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v);
		}

		private static string Join(params string[] fields)
		{
			return string.Join(";", fields);
		}

		private static string Esc(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length + 4);
			foreach (char c in s)
			{
				switch (c)
				{
				case '\\':
					stringBuilder.Append("\\\\");
					break;
				case ';':
					stringBuilder.Append("\\s");
					break;
				case '\n':
					stringBuilder.Append("\\n");
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}

		private static List<string> Split(string payload)
		{
			if (payload == null)
			{
				return null;
			}
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < payload.Length; i++)
			{
				char c = payload[i];
				switch (c)
				{
				case '\\':
					if (i + 1 >= payload.Length)
					{
						return null;
					}
					switch (payload[++i])
					{
					case '\\':
						stringBuilder.Append('\\');
						break;
					case 's':
						stringBuilder.Append(';');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					default:
						return null;
					}
					break;
				case ';':
					list.Add(stringBuilder.ToString());
					stringBuilder.Length = 0;
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			list.Add(stringBuilder.ToString());
			return list;
		}
	}
	public static class SpawnPolicy
	{
		public enum RoomSpawnDecision
		{
			Solo,
			CoopReady,
			CoopDegraded,
			ProceedGhost,
			RefusePeersNotReady,
			RefuseLegacy
		}

		public const float MinDistance = 1f;

		public const float MaxDistance = 50f;

		public static float EffectiveDistance(float? requested, float configured)
		{
			if (!requested.HasValue)
			{
				return configured;
			}
			float value = requested.Value;
			if (value < 1f)
			{
				return 1f;
			}
			if (value > 50f)
			{
				return 50f;
			}
			return value;
		}

		public static string NormalizeOwnerTag(string? tag)
		{
			return tag?.Trim() ?? "";
		}

		public static bool MatchesOwner(string recordTag, string? filter)
		{
			if (filter != null)
			{
				return string.Equals(NormalizeOwnerTag(recordTag), NormalizeOwnerTag(filter), StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		public static bool RefuseRoomSpawn(bool inRoom, int otherPlayerCount, bool allowSpawnInRoom)
		{
			if (inRoom && otherPlayerCount > 0)
			{
				return !allowSpawnInRoom;
			}
			return false;
		}

		public static RoomSpawnDecision DecideRoomSpawn(bool inRoom, int otherPlayerCount, bool coopEnabled, int peersWithoutHello, bool allowSpawnInRoom)
		{
			if (!inRoom || otherPlayerCount <= 0)
			{
				return RoomSpawnDecision.Solo;
			}
			if (!coopEnabled)
			{
				if (!allowSpawnInRoom)
				{
					return RoomSpawnDecision.RefuseLegacy;
				}
				return RoomSpawnDecision.ProceedGhost;
			}
			if (peersWithoutHello <= 0)
			{
				return RoomSpawnDecision.CoopReady;
			}
			if (!allowSpawnInRoom)
			{
				return RoomSpawnDecision.RefusePeersNotReady;
			}
			return RoomSpawnDecision.CoopDegraded;
		}

		public static bool IsRefusal(RoomSpawnDecision d)
		{
			if (d != RoomSpawnDecision.RefusePeersNotReady)
			{
				return d == RoomSpawnDecision.RefuseLegacy;
			}
			return true;
		}

		public static bool ShouldBroadcast(RoomSpawnDecision d)
		{
			if (d != RoomSpawnDecision.CoopReady)
			{
				return d == RoomSpawnDecision.CoopDegraded;
			}
			return true;
		}
	}
	public static class SpawnUid
	{
		public const string Prefix = "SK_";

		public const string ItemPrefix = "SKi_";

		public static string Mint(Guid guid)
		{
			return "SK_" + guid.ToString("N");
		}

		public static string MintItem(Guid guid)
		{
			return "SKi_" + guid.ToString("N");
		}

		public static bool IsSpawnUid(string? uid)
		{
			if (uid != null && uid.StartsWith("SK_", StringComparison.Ordinal))
			{
				return uid.Length > "SK_".Length;
			}
			return false;
		}

		public static bool IsSpawnItemUid(string? uid)
		{
			if (uid != null && uid.StartsWith("SKi_", StringComparison.Ordinal))
			{
				return uid.Length > "SKi_".Length;
			}
			return false;
		}
	}
	public sealed class SpawnVerbArgs
	{
		public string Species = "";

		public float? Distance;

		public float? LifetimeSeconds;

		public string? Faction;

		public string? OwnerTag;

		public string? Body;

		public float? CorpseLingerSeconds;

		public bool KeepQuestEvents;

		public bool IgnoreRoomWarm;

		public readonly List<string> UnknownOptions = new List<string>();

		public static SpawnVerbArgs Parse(string[] tokens)
		{
			SpawnVerbArgs spawnVerbArgs = new SpawnVerbArgs();
			List<string> list = new List<string>();
			for (int i = 1; i < tokens.Length; i++)
			{
				string text = tokens[i];
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				if (string.Equals(text, "keepquest", StringComparison.OrdinalIgnoreCase))
				{
					spawnVerbArgs.KeepQuestEvents = true;
					continue;
				}
				if (string.Equals(text, "force", StringComparison.OrdinalIgnoreCase))
				{
					spawnVerbArgs.IgnoreRoomWarm = true;
					continue;
				}
				int num = text.IndexOf('=');
				if (num <= 0)
				{
					list.Add(text);
					continue;
				}
				string text2 = text.Substring(0, num).Trim().ToLowerInvariant();
				string text3 = text.Substring(num + 1).Trim();
				switch (text2)
				{
				case "dist":
					spawnVerbArgs.Distance = ParseFloat(text3, spawnVerbArgs, text);
					break;
				case "life":
					spawnVerbArgs.LifetimeSeconds = ParseFloat(text3, spawnVerbArgs, text);
					break;
				case "faction":
					spawnVerbArgs.Faction = text3;
					break;
				case "owner":
					spawnVerbArgs.OwnerTag = text3;
					break;
				case "body":
					spawnVerbArgs.Body = text3;
					break;
				case "linger":
					spawnVerbArgs.CorpseLingerSeconds = ParseFloat(text3, spawnVerbArgs, text);
					break;
				default:
					spawnVerbArgs.UnknownOptions.Add(text);
					break;
				}
			}
			spawnVerbArgs.Species = string.Join(" ", list).Trim();
			return spawnVerbArgs;
		}

		private static float? ParseFloat(string val, SpawnVerbArgs result, string token)
		{
			if (float.TryParse(val, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
			{
				return result2;
			}
			result.UnknownOptions.Add(token);
			return null;
		}
	}
	public static class SpawnWatch
	{
		public const float DefaultIntervalSeconds = 0.5f;

		public static WatchState Next(WatchState current, bool exists, bool alive)
		{
			if (current != WatchState.Alive)
			{
				return current;
			}
			if (!exists)
			{
				return WatchState.Despawned;
			}
			if (!alive)
			{
				return WatchState.Died;
			}
			return WatchState.Alive;
		}

		public static bool IsTerminal(WatchState state)
		{
			if (state != WatchState.Died && state != WatchState.Despawned)
			{
				return state == WatchState.Failed;
			}
			return true;
		}
	}
	public enum WatchState
	{
		Pending,
		Alive,
		Died,
		Despawned,
		Failed
	}
	public static class SpeciesFilter
	{
		public static IReadOnlyList<string> Apply(IReadOnlyList<string> keys, string? query, Func<string, string?>? resolvedNameOf)
		{
			if (keys == null)
			{
				return Array.Empty<string>();
			}
			string text = query?.Trim() ?? "";
			if (text.Length == 0)
			{
				return keys;
			}
			List<string> list = new List<string>();
			for (int i = 0; i < keys.Count; i++)
			{
				string text2 = keys[i];
				if (Matches(text2, text))
				{
					list.Add(text2);
				}
				else if (Matches(resolvedNameOf?.Invoke(text2), text))
				{
					list.Add(text2);
				}
			}
			return list;
		}

		private static bool Matches(string? text, string query)
		{
			if (!string.IsNullOrEmpty(text))
			{
				return text.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		public static bool CancelDown(bool escDown, bool padCancelDown)
		{
			return escDown || padCancelDown;
		}

		public static bool EscConsumesFilter(bool open, bool hasQuery, bool escDown)
		{
			return open && hasQuery && escDown;
		}
	}
	public enum PeerWarmStatus
	{
		Unmodded,
		HelloedNoRow,
		Participating
	}
	public struct PeerWarmRow
	{
		public int Actor;

		public PeerWarmStatus Status;

		public string[] Warm;

		public string[] DeadEnds;

		public int RemainingBudget;
	}
	public enum RoomWarmMode
	{
		Local,
		RoomDegraded,
		RoomStrict
	}
	public enum RoomWarmVerdict
	{
		Spawnable,
		ColdOnPeer,
		DeadEndOnPeer,
		ColdLocally
	}
	public struct RoomWarmDecision
	{
		public RoomWarmVerdict Verdict;

		public int[] Actors;

		public string Reason;

		public bool Degraded;
	}
	public static class SpeciesRoomPolicy
	{
		private static readonly StringComparer KeyComparer = StringComparer.OrdinalIgnoreCase;

		public const string ReasonColdLocally = "cold-locally";

		public const string ReasonColdOnPeer = "cold-on-peer";

		public const string ReasonDeadEndOnPeer = "dead-end-on-peer";

		private static readonly int[] EmptyActors = new int[0];

		private static string Norm(string key)
		{
			if (key != null)
			{
				return key.Trim();
			}
			return "";
		}

		public static bool Contains(string[] keys, string key)
		{
			return ContainsNorm(keys, Norm(key));
		}

		private static bool ContainsNorm(string[] keys, string key)
		{
			if (keys == null)
			{
				return false;
			}
			for (int i = 0; i < keys.Length; i++)
			{
				if (KeyComparer.Equals(Norm(keys[i]), key))
				{
					return true;
				}
			}
			return false;
		}

		public static RoomWarmDecision Decide(string speciesKey, bool localWarm, IReadOnlyList<PeerWarmRow> peers, RoomWarmMode mode)
		{
			if (!localWarm)
			{
				return new RoomWarmDecision
				{
					Verdict = RoomWarmVerdict.ColdLocally,
					Actors = EmptyActors,
					Reason = "cold-locally",
					Degraded = false
				};
			}
			if (mode == RoomWarmMode.Local || peers == null || peers.Count == 0)
			{
				return Spawnable();
			}
			string key = Norm(speciesKey);
			List<int> list = new List<int>();
			List<int> list2 = new List<int>();
			for (int i = 0; i < peers.Count; i++)
			{
				PeerWarmRow peerWarmRow = peers[i];
				if (peerWarmRow.Status == PeerWarmStatus.Participating && !ContainsNorm(peerWarmRow.Warm, key))
				{
					if (ContainsNorm(peerWarmRow.DeadEnds, key) || peerWarmRow.RemainingBudget == 0)
					{
						list2.Add(peerWarmRow.Actor);
					}
					else
					{
						list.Add(peerWarmRow.Actor);
					}
				}
			}
			if (list.Count == 0 && list2.Count == 0)
			{
				return Spawnable();
			}
			List<int> list3 = new List<int>(list2.Count + list.Count);
			list3.AddRange(list2);
			list3.AddRange(list);
			list3.Sort();
			RoomWarmDecision result = new RoomWarmDecision
			{
				Verdict = ((list2.Count <= 0) ? RoomWarmVerdict.ColdOnPeer : RoomWarmVerdict.DeadEndOnPeer),
				Actors = list3.ToArray(),
				Reason = ((list2.Count > 0) ? "dead-end-on-peer" : "cold-on-peer"),
				Degraded = false
			};
			if (mode == RoomWarmMode.RoomDegraded)
			{
				result.Verdict = RoomWarmVerdict.Spawnable;
				result.Degraded = true;
			}
			return result;
		}

		private static RoomWarmDecision Spawnable()
		{
			return new RoomWarmDecision
			{
				Verdict = RoomWarmVerdict.Spawnable,
				Actors = EmptyActors,
				Reason = "",
				Degraded = false
			};
		}

		public static List<string> Intersection(IEnumerable<string> localWarm, IReadOnlyList<PeerWarmRow> peers)
		{
			List<string> list = new List<string>();
			if (localWarm == null)
			{
				return list;
			}
			HashSet<string> hashSet = new HashSet<string>(KeyComparer);
			foreach (string item in localWarm)
			{
				string text = Norm(item);
				if (text.Length == 0 || !hashSet.Add(text))
				{
					continue;
				}
				bool flag = true;
				if (peers != null)
				{
					for (int i = 0; i < peers.Count && flag; i++)
					{
						if (peers[i].Status == PeerWarmStatus.Participating && !ContainsNorm(peers[i].Warm, text))
						{
							flag = false;
						}
					}
				}
				if (flag)
				{
					list.Add(text);
				}
			}
			list.Sort(StringComparer.Ordinal);
			return list;
		}

		public static bool IsVeto(RoomWarmDecision d)
		{
			return d.Verdict != RoomWarmVerdict.Spawnable;
		}

		public static bool IsDeadEnd(RoomWarmDecision d)
		{
			return string.Equals(d.Reason, "dead-end-on-peer", StringComparison.Ordinal);
		}
	}
	public static class TemplateAiGate
	{
		public static bool RefusesAdoption(bool hasCharacterAI, bool aiPrefabSet)
		{
			if (hasCharacterAI)
			{
				return !aiPrefabSet;
			}
			return true;
		}

		public static bool RefusesAdoption(bool hasCharacterAI, bool aiPrefabSet, bool isGuest, int aiRootCount)
		{
			if (isGuest && !hasCharacterAI && aiRootCount >= 1)
			{
				return false;
			}
			return RefusesAdoption(hasCharacterAI, aiPrefabSet);
		}

		public static string Reason(string speciesKey, string origin)
		{
			return "'" + speciesKey + "' (" + origin + ") has no CharacterAI with an AIStatesPrefab — CharacterAI.GetAIStates builds its state graph ONLY from AIStatesPrefab (an AIRoot child alone never yields states), so an enemy minted from it can never have AI (BUG-PREBUILTADOPT)";
		}
	}
	public static class VisualGate
	{
		public enum Action
		{
			None,
			ForceVisuals,
			Rebind,
			Both
		}

		public struct Census
		{
			public int Renderers;

			public int RenderReady;

			public int SkinnedReady;

			public bool AnimatorPresent;

			public bool AnimatorInitialized;

			public float BakedX;

			public float BakedY;

			public float BakedZ;

			public int StackedBones;

			public int TotalBones;
		}

		public const float DefaultDegenerateAxisRatio = 0.05f;

		public const float DefaultStackedFraction = 0.8f;

		public static bool DegenerateBake(in Census c, float degenerateAxisRatio = 0.05f)
		{
			float num = Min3(c.BakedX, c.BakedY, c.BakedZ);
			float num2 = Max3(c.BakedX, c.BakedY, c.BakedZ);
			if (num2 > 0f)
			{
				return num / num2 <= degenerateAxisRatio;
			}
			return false;
		}

		public static bool NoUsableBody(in Census c, float degenerateAxisRatio = 0.05f)
		{
			if (c.Renderers != 0)
			{
				return DegenerateBake(in c, degenerateAxisRatio);
			}
			return true;
		}

		public static Action Decide(in Census c, float degenerateAxisRatio, float stackedFraction, out string reason)
		{
			bool flag = c.Renderers == 0 || c.RenderReady == 0 || c.SkinnedReady == 0;
			bool flag2 = DegenerateBake(in c, degenerateAxisRatio);
			bool flag3 = c.TotalBones > 0 && (float)c.StackedBones / (float)c.TotalBones >= stackedFraction;
			bool flag4 = c.AnimatorPresent && (!c.AnimatorInitialized || flag2 || flag3);
			reason = ((!flag) ? "" : ((c.Renderers == 0) ? "no renderers (Start-built visuals never ran)" : ((c.RenderReady == 0) ? "zero render-ready renderers" : "zero render-ready SKINNED renderers (only FX draw)")));
			if (flag4)
			{
				string text = ((!c.AnimatorInitialized) ? "animator not initialized" : (flag2 ? ("degenerate baked bounds (" + F(c.BakedX) + ", " + F(c.BakedY) + ", " + F(c.BakedZ) + ")") : $"unposed skeleton (stacked={c.StackedBones}/{c.TotalBones})"));
				reason = ((reason.Length > 0) ? (reason + "; " + text) : text);
			}
			if (!flag && !flag4)
			{
				reason = "healthy";
			}
			if (flag && flag4)
			{
				return Action.Both;
			}
			if (flag)
			{
				return Action.ForceVisuals;
			}
			if (flag4)
			{
				return Action.Rebind;
			}
			return Action.None;
		}

		public static Action Decide(in Census c, out string reason)
		{
			return Decide(in c, 0.05f, 0.8f, out reason);
		}

		private static float Min3(float a, float b, float c)
		{
			if (!(a < b))
			{
				if (!(b < c))
				{
					return c;
				}
				return b;
			}
			if (!(a < c))
			{
				return c;
			}
			return a;
		}

		private static float Max3(float a, float b, float c)
		{
			if (!(a > b))
			{
				if (!(b > c))
				{
					return c;
				}
				return b;
			}
			if (!(a > c))
			{
				return c;
			}
			return a;
		}

		private static string F(float v)
		{
			return v.ToString("0.0#", CultureInfo.InvariantCulture);
		}
	}
	public enum WantStep
	{
		Send,
		Wait,
		Retired,
		GiveUpColdOnPeer,
		GiveUpDeadEnd
	}
	public struct WantEntry
	{
		public int Actor;

		public string Species;

		public int Attempts;

		public double NextSendAt;
	}
	public sealed class WantBook
	{
		public const int MaxPerActor = 32;

		private static readonly StringComparer KeyComparer = StringComparer.OrdinalIgnoreCase;

		private readonly List<WantEntry> _entries = new List<WantEntry>();

		private readonly List<WantEntry> _tombstones = new List<WantEntry>();

		private readonly double _firstTimeout;

		private readonly double _maxTimeout;

		private readonly int _maxAttempts;

		public int Count => _entries.Count;

		public int AbandonedCount => _tombstones.Count;

		public WantBook(double firstTimeoutSeconds = 30.0, double maxTimeoutSeconds = 120.0, int maxAttempts = 3)
		{
			_firstTimeout = ((firstTimeoutSeconds > 0.0) ? firstTimeoutSeconds : 30.0);
			_maxTimeout = ((maxTimeoutSeconds >= _firstTimeout) ? maxTimeoutSeconds : _firstTimeout);
			_maxAttempts = ((maxAttempts <= 0) ? 1 : maxAttempts);
		}

		private static string Norm(string key)
		{
			if (key != null)
			{
				return key.Trim();
			}
			return "";
		}

		private int IndexOf(int actor, string normKey)
		{
			for (int i = 0; i < _entries.Count; i++)
			{
				if (_entries[i].Actor == actor && KeyComparer.Equals(Norm(_entries[i].Species), normKey))
				{
					return i;
				}
			}
			return -1;
		}

		private int TombstoneIndexOf(int actor, string normKey)
		{
			for (int i = 0; i < _tombstones.Count; i++)
			{
				if (_tombstones[i].Actor == actor && KeyComparer.Equals(Norm(_tombstones[i].Species), normKey))
				{
					return i;
				}
			}
			return -1;
		}

		public bool IsAbandoned(int actor, string species)
		{
			string text = Norm(species);
			if (text.Length != 0)
			{
				return TombstoneIndexOf(actor, text) >= 0;
			}
			return false;
		}

		public List<WantEntry> Tombstones()
		{
			return new List<WantEntry>(_tombstones);
		}

		public void CopyTombstonesFrom(WantBook other)
		{
			if (other == null || other == this)
			{
				return;
			}
			for (int i = 0; i < other._tombstones.Count; i++)
			{
				WantEntry item = other._tombstones[i];
				if (TombstoneIndexOf(item.Actor, Norm(item.Species)) < 0)
				{
					_tombstones.Add(item);
				}
			}
		}

		public bool Want(int actor, string species, double now)
		{
			string text = Norm(species);
			if (text.Length == 0)
			{
				return false;
			}
			if (IndexOf(actor, text) >= 0)
			{
				return true;
			}
			if (TombstoneIndexOf(actor, text) >= 0)
			{
				return false;
			}
			int num = 0;
			for (int i = 0; i < _entries.Count; i++)
			{
				if (_entries[i].Actor == actor)
				{
					num++;
				}
			}
			while (num >= 32)
			{
				for (int j = 0; j < _entries.Count; j++)
				{
					if (_entries[j].Actor == actor)
					{
						_entries.RemoveAt(j);
						break;
					}
				}
				num--;
			}
			_entries.Add(new WantEntry
			{
				Actor = actor,
				Species = text,
				Attempts = 0,
				NextSendAt = now
			});
			return true;
		}

		private double Timeout(int attemptNumber)
		{
			double num = _firstTimeout;
			for (int i = 1; i < attemptNumber; i++)
			{
				num *= 2.0;
				if (num >= _maxTimeout)
				{
					return _maxTimeout;
				}
			}
			if (!(num < _maxTimeout))
			{
				return _maxTimeout;
			}
			return num;
		}

		public WantStep Step(int actor, string species, bool peerWarmNow, bool peerDeadEnd, int peerBudget, double now)
		{
			string text = Norm(species);
			if (text.Length == 0)
			{
				return WantStep.Retired;
			}
			if (peerWarmNow)
			{
				int num = TombstoneIndexOf(actor, text);
				if (num >= 0)
				{
					_tombstones.RemoveAt(num);
				}
			}
			int num2 = IndexOf(actor, text);
			if (num2 < 0)
			{
				return WantStep.Retired;
			}
			if (peerWarmNow)
			{
				_entries.RemoveAt(num2);
				return WantStep.Retired;
			}
			if (peerDeadEnd || peerBudget <= 0)
			{
				_entries.RemoveAt(num2);
				return WantStep.GiveUpDeadEnd;
			}
			WantEntry wantEntry = _entries[num2];
			if (wantEntry.Attempts == 0)
			{
				wantEntry.Attempts = 1;
				wantEntry.NextSendAt = now + Timeout(1);
				_entries[num2] = wantEntry;
				return WantStep.Send;
			}
			if (now < wantEntry.NextSendAt)
			{
				return WantStep.Wait;
			}
			if (wantEntry.Attempts < _maxAttempts)
			{
				wantEntry.Attempts++;
				wantEntry.NextSendAt = now + Timeout(wantEntry.Attempts);
				_entries[num2] = wantEntry;
				return WantStep.Send;
			}
			_entries.RemoveAt(num2);
			if (TombstoneIndexOf(actor, text) < 0)
			{
				_tombstones.Add(wantEntry);
			}
			return WantStep.GiveUpColdOnPeer;
		}

		public List<WantEntry> TakeDue(Func<int, string, (bool warm, bool dead, int budget)?> peerFacts, double now, out List<WantEntry> gaveUp)
		{
			List<WantEntry> list = new List<WantEntry>();
			gaveUp = new List<WantEntry>();
			if (peerFacts == null)
			{
				return list;
			}
			List<int> list2 = new List<int>();
			for (int i = 0; i < _entries.Count; i++)
			{
				list2.Add(i);
			}
			list2.Sort(delegate(int a, int b)
			{
				int num3 = _entries[a].Actor.CompareTo(_entries[b].Actor);
				return (num3 == 0) ? a.CompareTo(b) : num3;
			});
			List<WantEntry> list3 = new List<WantEntry>(list2.Count);
			foreach (int item in list2)
			{
				list3.Add(_entries[item]);
			}
			foreach (WantEntry item2 in list3)
			{
				(bool, bool, int)? tuple;
				try
				{
					tuple = peerFacts(item2.Actor, item2.Species);
				}
				catch
				{
					continue;
				}
				if (!tuple.HasValue)
				{
					continue;
				}
				switch (Step(item2.Actor, item2.Species, tuple.Value.Item1, tuple.Value.Item2, tuple.Value.Item3, now))
				{
				case WantStep.Send:
				{
					int num = IndexOf(item2.Actor, Norm(item2.Species));
					if (num >= 0)
					{
						list.Add(_entries[num]);
					}
					break;
				}
				case WantStep.GiveUpColdOnPeer:
				case WantStep.GiveUpDeadEnd:
					gaveUp.Add(item2);
					break;
				}
			}
			List<WantEntry> list4 = new List<WantEntry>(_tombstones);
			for (int num2 = 0; num2 < list4.Count; num2++)
			{
				(bool, bool, int)? tuple2;
				try
				{
					tuple2 = peerFacts(list4[num2].Actor, list4[num2].Species);
				}
				catch
				{
					continue;
				}
				if (tuple2.HasValue && tuple2.Value.Item1)
				{
					Step(list4[num2].Actor, list4[num2].Species, peerWarmNow: true, tuple2.Value.Item2, tuple2.Value.Item3, now);
				}
			}
			return list;
		}

		public void ForgetActor(int actor)
		{
			for (int num = _entries.Count - 1; num >= 0; num--)
			{
				if (_entries[num].Actor == actor)
				{
					_entries.RemoveAt(num);
				}
			}
			for (int num2 = _tombstones.Count - 1; num2 >= 0; num2--)
			{
				if (_tombstones[num2].Actor == actor)
				{
					_tombstones.RemoveAt(num2);
				}
			}
		}

		public void Clear()
		{
			_entries.Clear();
			_tombstones.Clear();
		}

		public List<WantEntry> Snapshot()
		{
			return new List<WantEntry>(_entries);
		}
	}
	public struct WarmSet
	{
		public bool IsPresent;

		public string[] Warm;

		public string[] DeadEnds;

		public int RemainingBudget;
	}
	public static class WarmSetWire
	{
		public const string Version = "1";

		public const int UnlimitedBudgetSentinel = 99;

		public const int MaxKeysPerField = 256;

		private static readonly string[] NoKeys = new string[0];

		private static readonly StringComparer KeyComparer = StringComparer.OrdinalIgnoreCase;

		public static int PublishedBudget(int hostRemaining, int ceilingRemaining)
		{
			int num = ((hostRemaining < 0) ? 99 : Math.Min(99, hostRemaining));
			if (ceilingRemaining < 0)
			{
				return num;
			}
			return Math.Min(num, Math.Max(0, ceilingRemaining));
		}

		public static string Encode(IEnumerable<string> warm, IEnumerable<string> deadEnds, int remainingBudget)
		{
			if (remainingBudget < 0)
			{
				remainingBudget = 0;
			}
			StringBuilder stringBuilder = new StringBuilder(64);
			stringBuilder.Append("1");
			stringBuilder.Append("|w=");
			AppendKeys(stringBuilder, Canonical(warm));
			stringBuilder.Append("|d=");
			AppendKeys(stringBuilder, Canonical(deadEnds));
			stringBuilder.Append("|b=");
			stringBuilder.Append(remainingBudget.ToString(CultureInfo.InvariantCulture));
			return stringBuilder.ToString();
		}

		public static WarmSet Decode(string payload)
		{
			WarmSet result = new WarmSet
			{
				IsPresent = false,
				Warm = NoKeys,
				DeadEnds = NoKeys,
				RemainingBudget = 0
			};
			if (string.IsNullOrEmpty(payload))
			{
				return result;
			}
			List<string> list = SplitTop(payload);
			if (list == null || list.Count < 1)
			{
				return result;
			}
			if (list[0] != "1")
			{
				return result;
			}
			string[] array = null;
			string[] array2 = null;
			int result2 = 0;
			bool flag = false;
			for (int i = 1; i < list.Count; i++)
			{
				string text = list[i];
				if (text.Length >= 2 && text[0] == 'w' && text[1] == '=')
				{
					if (array != null)
					{
						return result;
					}
					array = ParseKeys(text.Substring(2));
					if (array == null || array.Length > 256)
					{
						return result;
					}
				}
				else if (text.Length >= 2 && text[0] == 'd' && text[1] == '=')
				{
					if (array2 != null)
					{
						return result;
					}
					array2 = ParseKeys(text.Substring(2));
					if (array2 == null || array2.Length > 256)
					{
						return result;
					}
				}
				else if (text.Length >= 2 && text[0] == 'b' && text[1] == '=')
				{
					if (flag)
					{
						return result;
					}
					if (!int.TryParse(text.Substring(2), NumberStyles.Integer, CultureInfo.InvariantCulture, out result2))
					{
						return result;
					}
					if (result2 < 0)
					{
						result2 = 0;
					}
					flag = true;
				}
			}
			if (array == null || array2 == null || !flag)
			{
				return result;
			}
			return new WarmSet
			{
				IsPresent = true,
				Warm = array,
				DeadEnds = array2,
				RemainingBudget = result2
			};
		}

		public static bool SetEquals(WarmSet a, WarmSet b)
		{
			if (a.IsPresent != b.IsPresent)
			{
				return false;
			}
			if (!a.IsPresent)
			{
				return true;
			}
			if (a.RemainingBudget != b.RemainingBudget)
			{
				return false;
			}
			if (KeysEqual(a.Warm, b.Warm))
			{
				return KeysEqual(a.DeadEnds, b.DeadEnds);
			}
			return false;
		}

		public static bool Contains(WarmSet s, string speciesKey)
		{
			if (!s.IsPresent || s.Warm == null)
			{
				return false;
			}
			string text = ((speciesKey == null) ? "" : speciesKey.Trim());
			if (text.Length == 0)
			{
				return false;
			}
			for (int i = 0; i < s.Warm.Length; i++)
			{
				if (KeyComparer.Equals(s.Warm[i], text))
				{
					return true;
				}
			}
			return false;
		}

		private static List<string> Canonical(IEnumerable<string> keys)
		{
			List<string> list = new List<string>();
			if (keys != null)
			{
				foreach (string key in keys)
				{
					if (key != null)
					{
						string text = key.Trim();
						if (text.Length != 0)
						{
							list.Add(text);
						}
					}
				}
			}
			list.Sort(CanonicalOrder);
			List<string> list2 = new List<string>(list.Count);
			for (int i = 0; i < list.Count; i++)
			{
				if (i == 0 || !KeyComparer.Equals(list[i], list[i - 1]))
				{
					list2.Add(list[i]);
				}
			}
			return list2;
		}

		private static void AppendKeys(StringBuilder sb, List<string> keys)
		{
			for (int i = 0; i < keys.Count; i++)
			{
				if (i != 0)
				{
					sb.Append(';');
				}
				Esc(sb, keys[i]);
			}
		}

		private static void Esc(StringBuilder sb, string s)
		{
			foreach (char c in s)
			{
				switch (c)
				{
				case '\\':
					sb.Append("\\\\");
					break;
				case ';':
					sb.Append("\\s");
					break;
				case '|':
					sb.Append("\\p");
					break;
				case '\n':
					sb.Append("\\n");
					break;
				default:
					sb.Append(c);
					break;
				}
			}
		}

		private static List<string> SplitTop(string payload)
		{
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < payload.Length; i++)
			{
				char c = payload[i];
				switch (c)
				{
				case '\\':
					if (i + 1 >= payload.Length)
					{
						return null;
					}
					stringBuilder.Append(c).Append(payload[++i]);
					break;
				case '|':
					list.Add(stringBuilder.ToString());
					stringBuilder.Length = 0;
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			list.Add(stringBuilder.ToString());
			return list;
		}

		private static string[] ParseKeys(string body)
		{
			if (body.Length == 0)
			{
				return NoKeys;
			}
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < body.Length; i++)
			{
				char c = body[i];
				switch (c)
				{
				case '\\':
					if (i + 1 >= body.Length)
					{
						return null;
					}
					switch (body[++i])
					{
					case '\\':
						stringBuilder.Append('\\');
						break;
					case 's':
						stringBuilder.Append(';');
						break;
					case 'p':
						stringBuilder.Append('|');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					default:
						return null;
					}
					break;
				case ';':
					list.Add(stringBuilder.ToString());
					stringBuilder.Length = 0;
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			list.Add(stringBuilder.ToString());
			return list.ToArray();
		}

		private static int CanonicalOrder(string x, string y)
		{
			int num = string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
			if (num == 0)
			{
				return string.CompareOrdinal(x, y);
			}
			return num;
		}

		private static bool KeysEqual(string[] a, string[] b)
		{
			a = a ?? NoKeys;
			b = b ?? NoKeys;
			if (a.Length != b.Length)
			{
				return false;
			}
			List<string> list = new List<string>(a);
			list.Sort(CanonicalOrder);
			List<string> list2 = new List<string>(b);
			list2.Sort(CanonicalOrder);
			for (int i = 0; i < list.Count; i++)
			{
				if (!KeyComparer.Equals(list[i], list2[i]))
				{
					return false;
				}
			}
			return true;
		}
	}
}

plugins/SpawnKit.dll

Decompiled 6 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using CompanionKit;
using CompanionKit.Core;
using DonorKit;
using ForgeKit;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NetKit;
using NetKit.Core;
using SpawnKit.Core;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("SpawnKit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.2.0")]
[assembly: AssemblyInformationalVersion("0.5.2+091b206910305beb491301afe1db01b8cd7b8e72")]
[assembly: AssemblyProduct("SpawnKit")]
[assembly: AssemblyTitle("SpawnKit")]
[assembly: AssemblyMetadata("BuildStamp", "091b2069 2026-08-28")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.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;
		}
	}
}
namespace SpawnKit
{
	public static class BoneProbe
	{
		private static int sampled;

		public static void Dump(string nameFilter, float radius = 30f)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			CharacterManager instance = CharacterManager.Instance;
			Character val = Lifecycle.FirstLocalCharacterOrNull();
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[BONES] no local player — nothing to probe.");
				return;
			}
			DictionaryExt<string, Character> characters = instance.Characters;
			StringBuilder stringBuilder = new StringBuilder();
			int num = 0;
			for (int i = 0; i < characters.Count; i++)
			{
				Character val2 = characters.Values[i];
				if ((Object)(object)val2 == (Object)null || (Object)(object)val2 == (Object)(object)val)
				{
					continue;
				}
				float num2 = Vector3.Distance(((Component)val2).transform.position, ((Component)val).transform.position);
				if (!(num2 > radius) && (string.IsNullOrEmpty(nameFilter) || (val2.Name != null && val2.Name.IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0)))
				{
					string text = "?";
					try
					{
						text = ((object)val2.UID/*cast due to .constrained prefix*/).ToString();
					}
					catch
					{
					}
					bool flag = SpawnUid.IsSpawnUid(text);
					stringBuilder.Append(string.Format("\n  '{0}' uid={1} spawned={2} dist={3:0.0}m", val2.Name, text, flag ? "Y" : "N", num2));
					stringBuilder.Append('\n').Append(SkeletonRig.Census(((Component)val2).gameObject));
					num++;
					if (sampled < 3 && (Object)(object)Plugin.Instance != (Object)null)
					{
						sampled++;
						((MonoBehaviour)Plugin.Instance).StartCoroutine(SampleBoneMotion(val2, text));
					}
				}
			}
			Plugin.Log.LogMessage((object)((num == 0) ? string.Format("[BONES] no characters within {0:0}m{1}.", radius, string.IsNullOrEmpty(nameFilter) ? "" : (" matching '" + nameFilter + "'")) : string.Format("[BONES] {0} character(s) within {1:0}m{2}:{3}", num, radius, string.IsNullOrEmpty(nameFilter) ? "" : (" matching '" + nameFilter + "'"), stringBuilder)));
		}

		private static IEnumerator SampleBoneMotion(Character ch, string uid)
		{
			SkinnedMeshRenderer body = (((Object)(object)ch != (Object)null) ? VisualPass.LargestSmr(((Component)ch).gameObject) : null);
			if ((Object)(object)body == (Object)null)
			{
				sampled--;
				yield break;
			}
			Transform[] bones = body.bones;
			Quaternion[] rot = (Quaternion[])(object)new Quaternion[bones.Length];
			Vector3[] pos = (Vector3[])(object)new Vector3[bones.Length];
			for (int i = 0; i < bones.Length; i++)
			{
				if ((Object)(object)bones[i] != (Object)null)
				{
					rot[i] = bones[i].localRotation;
					pos[i] = bones[i].localPosition;
				}
			}
			for (int f = 0; f < 30; f++)
			{
				yield return null;
			}
			int num = 0;
			int num2 = 0;
			float num3 = 0f;
			float num4 = 0f;
			try
			{
				if ((Object)(object)body == (Object)null || (Object)(object)ch == (Object)null || (Object)(object)((Component)ch).gameObject == (Object)null)
				{
					sampled--;
					yield break;
				}
				bones = body.bones;
				for (int j = 0; j < bones.Length && j < rot.Length; j++)
				{
					if (!((Object)(object)bones[j] == (Object)null))
					{
						num2++;
						float num5 = Quaternion.Angle(rot[j], bones[j].localRotation);
						float num6 = Vector3.Distance(pos[j], bones[j].localPosition);
						if (num5 > 0.5f || num6 > 0.005f)
						{
							num++;
						}
						if (num5 > num3)
						{
							num3 = num5;
						}
						if (num6 > num4)
						{
							num4 = num6;
						}
					}
				}
				Plugin.Log.LogMessage((object)($"[BONES] motion '{ch.Name}' uid={uid}: movedBones={num}/{num2} over 30 frames " + $"maxDelta={num3:0.#}° / {num4:0.###}m"));
			}
			finally
			{
				sampled--;
			}
		}
	}
	internal static class CancelInput
	{
		private static PropertyInfo _isReady;

		private static bool _probed;

		private static bool _disabled;

		private static int _throws;

		internal static bool Down()
		{
			if (_disabled)
			{
				return false;
			}
			try
			{
				if (!RewiredReady())
				{
					return false;
				}
				return ControlsInput.MenuCancelSystem();
			}
			catch (Exception ex)
			{
				if (++_throws >= 3)
				{
					_disabled = true;
					Debug.LogWarning((object)("[SpawnKit] Rewired menu-cancel poll disabled after 3 throws: " + ex.Message));
				}
				return false;
			}
		}

		private static bool RewiredReady()
		{
			if (!_probed)
			{
				_probed = true;
				Type type = Type.GetType("Rewired.ReInput, Rewired_Core", throwOnError: false);
				_isReady = ((type == null) ? null : type.GetProperty("isReady", BindingFlags.Static | BindingFlags.Public));
			}
			if (_isReady == null)
			{
				return false;
			}
			object value = _isReady.GetValue(null, null);
			bool flag = default(bool);
			int num;
			if (value is bool)
			{
				flag = (bool)value;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}
	}
	internal static class CasterSkills
	{
		internal static int EnsureLearned(Character ch)
		{
			int num = 0;
			try
			{
				CharacterSkillKnowledge knowledge = KnowledgeOf(ch);
				InstantiateStartingSkills(ch, ref knowledge);
				if ((Object)(object)knowledge == (Object)null)
				{
					return 0;
				}
				Skill[] componentsInChildren = ((Component)knowledge).GetComponentsInChildren<Skill>(true);
				List<int> list = new List<int>();
				List<int> list2 = new List<int>();
				Skill[] array = componentsInChildren;
				foreach (Skill val in array)
				{
					if ((Object)(object)val != (Object)null)
					{
						list.Add(((Item)val).ItemID);
					}
				}
				IList<Item> learnedItems = ((CharacterKnowledge)knowledge).GetLearnedItems();
				if (learnedItems != null)
				{
					foreach (Item item in learnedItems)
					{
						if ((Object)(object)item != (Object)null)
						{
							list2.Add(item.ItemID);
						}
					}
				}
				List<int> list3 = CasterSkillGate.MissingSkillIds((IEnumerable<int>)list2, (IEnumerable<int>)list);
				Skill[] array2 = componentsInChildren;
				foreach (Skill val2 in array2)
				{
					if (!((Object)(object)val2 == (Object)null) && list3.Contains(((Item)val2).ItemID) && ForceLearn(ch, knowledge, val2))
					{
						num++;
						list3.Remove(((Item)val2).ItemID);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SPAWN] caster skill-knowledge repair threw: " + ex.Message));
			}
			return num;
		}

		internal static string StateFor(Character ch)
		{
			try
			{
				CharacterSkillKnowledge val = KnowledgeOf(ch);
				if ((Object)(object)val == (Object)null)
				{
					return CasterSkillGate.Census(0, 0, false);
				}
				int num = 0;
				Skill[] componentsInChildren = ((Component)val).GetComponentsInChildren<Skill>(true);
				foreach (Skill val2 in componentsInChildren)
				{
					if ((Object)(object)val2 != (Object)null)
					{
						num++;
					}
				}
				return CasterSkillGate.Census(((CharacterKnowledge)val).GetLearnedItems()?.Count ?? 0, num, true);
			}
			catch
			{
				return "skills=?/?";
			}
		}

		private static CharacterSkillKnowledge KnowledgeOf(Character ch)
		{
			if (!((Object)(object)ch != (Object)null) || !((Object)(object)ch.Inventory != (Object)null))
			{
				return null;
			}
			return ch.Inventory.SkillKnowledge;
		}

		private static void InstantiateStartingSkills(Character ch, ref CharacterSkillKnowledge knowledge)
		{
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			StartingEquipment val = (((Object)(object)ch != (Object)null) ? ((Component)ch).GetComponent<StartingEquipment>() : null);
			if ((Object)(object)val == (Object)null || val.StartingSkills == null || val.StartingSkills.Length == 0)
			{
				return;
			}
			if ((Object)(object)knowledge == (Object)null || !val.m_holderInitialized)
			{
				val.InitHolders();
				knowledge = KnowledgeOf(ch);
			}
			if ((Object)(object)knowledge == (Object)null)
			{
				return;
			}
			HashSet<int> hashSet = new HashSet<int>();
			Skill[] componentsInChildren = ((Component)knowledge).GetComponentsInChildren<Skill>(true);
			foreach (Skill val2 in componentsInChildren)
			{
				if ((Object)(object)val2 != (Object)null)
				{
					hashSet.Add(((Item)val2).ItemID);
				}
			}
			Skill[] startingSkills = val.StartingSkills;
			foreach (Skill val3 in startingSkills)
			{
				if ((Object)(object)val3 == (Object)null || !hashSet.Add(((Item)val3).ItemID))
				{
					continue;
				}
				Skill val4 = null;
				try
				{
					val4 = Object.Instantiate<Skill>(val3);
					((Item)val4).SaveType = (SaveTypes)2;
					((Item)val4).UID = SpawnUid.MintItem(Guid.NewGuid());
					((Component)val4).transform.SetParent(((Component)knowledge).transform);
					UnityEngineExtensions.ResetLocal(((Component)val4).transform, true);
					val4.IgnoreLearnNotification = true;
					Plugin.Log.LogMessage((object)($"[SPAWN] instantiated StartingSkill {((Item)val3).ItemID} '{((Item)val3).Name}' on " + "'" + ((Object)((Component)ch).gameObject).name + "' (BUG-CASTERNOSKILL — vanilla InitSkills never re-fires on a mid-gameplay clone)."));
				}
				catch (Exception ex)
				{
					if ((Object)(object)val4 != (Object)null)
					{
						Object.Destroy((Object)(object)((Component)val4).gameObject);
					}
					Plugin.Log.LogWarning((object)("[SPAWN] StartingSkill instantiate '" + ((Object)val3).name + "' threw: " + ex.Message));
				}
			}
		}

		private static bool ForceLearn(Character ch, CharacterSkillKnowledge knowledge, Skill sk)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((Component)sk).gameObject.activeSelf)
				{
					((Component)sk).gameObject.SetActive(true);
				}
				((Item)sk).SaveType = (SaveTypes)2;
				if ((Object)(object)((Item)sk).m_lastParentTrans == (Object)null)
				{
					((Item)sk).m_lastParentTrans = ((Component)sk).transform.parent;
				}
				((Item)sk).ForceUpdateParentChange();
				if (!((CharacterKnowledge)knowledge).IsItemLearned(((Item)sk).ItemID))
				{
					UnityEngineExtensions.ResetLocal(((Component)sk).transform, true);
					((EffectSynchronizer)sk).ProcessEffects();
					((CharacterKnowledge)knowledge).AddItem((Item)(object)sk);
				}
				if (((CharacterKnowledge)knowledge).IsItemLearned(((Item)sk).ItemID))
				{
					Plugin.Log.LogMessage((object)($"[SPAWN] force-learned skill {((Item)sk).ItemID} '{((Item)sk).Name}' on '{((Object)((Component)ch).gameObject).name}' " + "(BUG-CASTERNOSKILL — AIEUseSkill is a silent no-op on an unlearned skill; the clone's UpdateParentChange sync gate skipped RegisterKnowledge, the caster sibling of Bug 39)."));
					return true;
				}
				Plugin.Log.LogWarning((object)($"[SPAWN] skill {((Item)sk).ItemID} '{((Item)sk).Name}' on '{((Object)((Component)ch).gameObject).name}' REFUSED to learn " + "even after ForceUpdateParentChange + direct AddItem — this caster will not attack (BUG-CASTERNOSKILL)."));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SPAWN] force-learn of skill '" + ((Object)sk).name + "' threw: " + ex.Message));
			}
			return false;
		}
	}
	internal static class CorpseGC
	{
		internal static void ScheduleCorpseRemoval(SpawnHandle handle, GameObject corpse)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)handle.CorpsePolicy == 1 && !((Object)(object)corpse == (Object)null) && !((Object)(object)Plugin.Instance == (Object)null))
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(RemoveCorpse(corpse, handle.SpeciesKey, handle.OwnerTag, handle.CorpseLingerSeconds, handle.Uid));
			}
		}

		private static IEnumerator RemoveCorpse(GameObject corpse, string species, string owner, float linger, string uid)
		{
			if (linger > 0f)
			{
				yield return (object)new WaitForSeconds(linger);
			}
			else
			{
				yield return null;
			}
			if (!((Object)(object)corpse == (Object)null))
			{
				Object.Destroy((Object)(object)corpse);
				Plugin.Log.LogMessage((object)$"[SPAWN] corpse removed (NoBody): species='{species}' owner='{owner}' linger={linger:0.#}s.");
				SpawnNet.SendGone(uid, (GoneKind)2);
			}
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class CursorControl
	{
		internal static bool MenuOpen;

		internal static bool VanillaMenuFocused;

		private static CursorStrategy _engaged;

		private static CharacterUI OwnerUI
		{
			get
			{
				Character val = Lifecycle.FirstLocalCharacterOrNull();
				if (!((Object)(object)val != (Object)null))
				{
					return null;
				}
				return val.CharacterUI;
			}
		}

		private static void Postfix(CharacterUI __instance, ref bool __result)
		{
			if (__instance == OwnerUI)
			{
				VanillaMenuFocused = __result;
				if (MenuCursorPolicy.EngageSeam(MenuOpen, __result))
				{
					__result = true;
				}
			}
		}

		internal static void SetMenuOpen(bool open)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: 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_001b: 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_0023: Invalid comparison between Unknown and I4
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Invalid comparison between Unknown and I4
			MenuOpen = open;
			CursorStrategy val = MenuCursorPolicy.ResolveLog(open, VanillaMenuFocused);
			if (val == _engaged)
			{
				return;
			}
			_engaged = val;
			if ((int)val != 1)
			{
				if ((int)val == 2)
				{
					Plugin.Log.LogMessage((object)"[MENU] cursor: vanilla menu owns cursor (yielding — our window force-closes).");
				}
				else
				{
					Plugin.Log.LogMessage((object)"[MENU] cursor: game-seam restored (menu closed — cursor + camera/movement back under game control).");
				}
			}
			else
			{
				Plugin.Log.LogMessage((object)"[MENU] cursor: game-seam released (IsMenuFocused override engaged — game frees cursor + stops camera/movement).");
			}
		}
	}
	internal static class EnemySpawner
	{
		private static readonly List<SpawnHandle> _spawns = new List<SpawnHandle>();

		internal static readonly ViewLease Lease = new ViewLease("[SPAWN]");

		private const byte CorpseMuteGroup = 253;

		private static GameObject _mintHolder;

		private const float ColdDegradedLogSeconds = 60f;

		private static readonly Dictionary<string, float> _coldDegradedLoggedAt = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, float> _gateNotEvaluatedLoggedAt = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

		private static readonly List<SpawnHandle> _watchScratch = new List<SpawnHandle>();

		private static bool _watchScratchBusy;

		internal static int PendingReleaseCount => Lease.PendingCount;

		internal static int TrackedCount => _spawns.Count;

		private static void ReleaseViewId(int viewId)
		{
			Lease.Release(viewId);
		}

		private static void DeferViewRelease(int viewId, GameObject body, bool corpseMayPersist = false)
		{
			Lease.DeferRelease(viewId, body, corpseMayPersist);
		}

		private static void SweepPendingReleases()
		{
			Lease.SweepPending((Action<LeaseEntry<GameObject>, LeaseVerdict, float>)AgedOutNotice, (Action<Exception>)CorpseMuteReassertFailed);
		}

		private static void AgedOutNotice(LeaseEntry<GameObject> pr, LeaseVerdict verdict, float age)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			if ((int)verdict == 3)
			{
				Plugin.Log.LogMessage((object)($"[SPAWN] viewID {pr.ViewId} parked on corpse '{((Object)pr.Body).name}' for " + $"{age:0}s — CORRECT under the Vanilla corpse policy (no corpse GC exists; " + "the id stays reserved until scene unload destroys the corpse and the sweep releases it)."));
			}
			else
			{
				Plugin.Log.LogWarning((object)($"[SPAWN] viewID {pr.ViewId} has been parked on '{((Object)pr.Body).name}' for " + $"{age:0}s (> {300f:0}s) and the body STILL holds the view — the corpse/scene " + "teardown that should free it never fired. Leaving it reserved (a live-view release would fire PUN's warning); this is a viewID leak — investigate. (skcoopdump/dump shows parked entries + ages.)"));
			}
		}

		private static void CorpseMuteReassertFailed(Exception e)
		{
			Plugin.Log.LogWarning((object)($"[SPAWN] re-asserting the corpse send-block (group {(byte)253}) threw " + "(" + e.GetType().Name + ": " + e.Message + ") — muted corpses may resume streaming at guests (V-PARKLEAK). Warned once per session."));
		}

		private static void MuteCorpseView(Character corpse)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)corpse == (Object)null))
			{
				GameObject gameObject;
				try
				{
					gameObject = ((Component)corpse).gameObject;
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[SPAWN] corpse view mute failed: " + ex.Message));
					return;
				}
				MuteResult val = Lease.MuteView(gameObject);
				if (val.Error != null)
				{
					Plugin.Log.LogWarning((object)("[SPAWN] corpse view mute failed: " + val.Error));
				}
				else if (val.Muted)
				{
					Plugin.Log.LogMessage((object)($"[SPAWN] muted corpse viewID {val.ViewId} (group {(byte)253} send-blocked; " + "view stays registered, parked id stays reserved) — RunViewUpdate skips blocked groups, so the " + $"corpse stops streaming at guests who can never hold it (V-PARKLEAK). pt={PhotonNetwork.time:F1}"));
				}
			}
		}

		internal static string PendingReleasesDump()
		{
			return Lease.PendingDump();
		}

		private static GameObject MintHolder()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			if ((Object)(object)_mintHolder == (Object)null)
			{
				_mintHolder = new GameObject("SK_MintHolder");
				_mintHolder.SetActive(false);
			}
			return _mintHolder;
		}

		internal static FailReason Preflight(SpawnHandle handle, SpawnOptions opts)
		{
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected I4, but got Unknown
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Invalid comparison between Unknown and I4
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Invalid comparison between Unknown and I4
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_030d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			opts = opts ?? new SpawnOptions();
			if (!((Object)(object)Plugin.Instance == (Object)null) && Plugin.Log != null)
			{
				if (!Plugin.Enabled.Value)
				{
					Plugin.Log.LogWarning((object)"[SPAWN] refused: [Spawner] Enabled=false (kill-switch).");
					return (FailReason)2;
				}
				if (PhotonNetwork.isNonMasterClientInRoom)
				{
					Plugin.Log.LogWarning((object)"[SPAWN] refused: only the master client spawns (clients receive, Phase 3).");
					return (FailReason)3;
				}
				Character val = Lifecycle.FirstLocalCharacterOrNull();
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)"[SPAWN] refused: no local player character yet.");
					return (FailReason)4;
				}
				int num = 0;
				try
				{
					PhotonPlayer[] otherPlayers = PhotonNetwork.otherPlayers;
					num = ((otherPlayers != null) ? otherPlayers.Length : 0);
				}
				catch
				{
				}
				string names;
				int num2 = SpawnNet.PeersWithoutHelloCount(out names);
				RoomSpawnDecision val2 = SpawnPolicy.DecideRoomSpawn(PhotonNetwork.inRoom, num, Plugin.EnableCoopSpawns.Value, num2, Plugin.AllowSpawnInRoom.Value);
				switch (val2 - 1)
				{
				case 4:
					Notify.Player(val, "Spawn refused: guests are connected and [Coop] EnableCoopSpawns=false ([Spawner] AllowSpawnInRoom=true to override into the ghost behavior).");
					Plugin.Log.LogWarning((object)($"[SPAWN] refused (RefuseLegacy): {num} other player(s), coop disabled, " + "AllowSpawnInRoom=false — a broadcast-less spawn is a master-only ghost."));
					return (FailReason)11;
				case 3:
					Notify.Player(val, $"Spawn refused: {num2} player(s) without a compatible SpawnKit ({names}) — " + "they need the mod, or [Spawner] AllowSpawnInRoom=true accepts they'll see nothing.");
					Plugin.Log.LogWarning((object)("[SPAWN] refused (PeersNotReady): no sk.hello from " + names + " — unmodded or incompatible (skcoopdump shows the hello ledger)."));
					return (FailReason)12;
				case 1:
					Plugin.Log.LogWarning((object)("[SPAWN] co-op DEGRADED spawn (AllowSpawnInRoom override): " + names + " never handshook — modded peers will mirror, those peers get the pre-Phase-3 ghost."));
					break;
				case 2:
					Plugin.Log.LogWarning((object)"[SPAWN] ghost spawn (coop disabled + AllowSpawnInRoom override): NO broadcast — guests get warn spam + the invulnerable-replica combat lock. On your head.");
					break;
				case 0:
					Plugin.Log.LogMessage((object)($"[SPAWN] co-op spawn: {num} peer(s) all handshaken — " + "broadcast follows once the spawn resolves Alive."));
					break;
				}
				if (PhotonNetwork.inRoom && WarmMirror.ParticipatingCount() > 0)
				{
					handle.RoomGateEvaluated = true;
					RoomWarmDecision val3 = Spawner.RoomWarmDecision(handle.SpeciesKey);
					if ((int)val3.Verdict == 1 || (int)val3.Verdict == 2)
					{
						if (!opts.IgnoreRoomWarm)
						{
							string text = string.Join(", ", Array.ConvertAll(val3.Actors, (int a) => a.ToString()));
							Notify.Player(val, $"Spawn refused: '{handle.SpeciesKey}' is cold on {val3.Actors.Length} guest(s) — " + "they would stall loading a donor scene to see it ([Coop] RoomWarmMode).");
							Plugin.Log.LogWarning((object)("[SPAWN] refused '" + handle.SpeciesKey + "' — cold on peer actor(s) [" + text + "] (RoomWarmMode=RoomStrict, " + val3.Reason + "). skwarmdump shows each peer's row."));
							return (FailReason)13;
						}
						Plugin.Log.LogWarning((object)("[SPAWN] '" + handle.SpeciesKey + "' is cold on peer actor(s) [" + string.Join(", ", Array.ConvertAll(val3.Actors, (int a) => a.ToString())) + "] — spawning anyway (SpawnOptions.IgnoreRoomWarm: the dev-verb 'force' escape)."));
					}
					else if (val3.Degraded)
					{
						if (LogColdDegraded(handle.SpeciesKey))
						{
							Plugin.Log.LogWarning((object)("[SPAWN] '" + handle.SpeciesKey + "' cold on actor(s) [" + string.Join(", ", Array.ConvertAll(val3.Actors, (int a) => a.ToString())) + "] — spawning anyway (RoomWarmMode=RoomDegraded, " + val3.Reason + "); asking them to warm it."));
						}
						Spawner.RequestRoomWarm(new string[1] { handle.SpeciesKey });
					}
					handle.RoomGateOk = (int)val3.Verdict == 0 && !val3.Degraded;
				}
				if ((Object)(object)AISquadManager.Instance == (Object)null)
				{
					Notify.Player(val, "Cannot spawn here (no AI manager in this scene).");
					return (FailReason)5;
				}
				WatchTick();
				if (_spawns.Count + 1 > Plugin.MaxActiveSpawns.Value)
				{
					Notify.Player(val, $"Spawn refused: would exceed MaxActiveSpawns ({_spawns.Count} active/pending, cap {Plugin.MaxActiveSpawns.Value}).");
					return (FailReason)6;
				}
				_spawns.Add(handle);
				return (FailReason)0;
			}
			return (FailReason)1;
		}

		private static bool LogColdDegraded(string speciesKey)
		{
			float unscaledTime = Time.unscaledTime;
			if (_coldDegradedLoggedAt.TryGetValue(speciesKey ?? "", out var value) && unscaledTime - value < 60f)
			{
				return false;
			}
			_coldDegradedLoggedAt[speciesKey ?? ""] = unscaledTime;
			return true;
		}

		private static bool LogGateNotEvaluated(string speciesKey)
		{
			float unscaledTime = Time.unscaledTime;
			if (_gateNotEvaluatedLoggedAt.TryGetValue(speciesKey ?? "", out var value) && unscaledTime - value < 60f)
			{
				return false;
			}
			_gateNotEvaluatedLoggedAt[speciesKey ?? ""] = unscaledTime;
			return true;
		}

		internal static IEnumerator SpawnRoutine(SpawnHandle handle, SpawnOptions opts)
		{
			opts = opts ?? new SpawnOptions();
			GameObject template = null;
			bool acquired = false;
			yield return SpawnTemplates.Acquire(handle.SpeciesKey, delegate(GameObject t)
			{
				template = t;
				acquired = true;
			});
			while (!acquired)
			{
				yield return null;
			}
			if (handle.State != SpawnState.Pending)
			{
				yield break;
			}
			Character val = Lifecycle.FirstLocalCharacterOrNull();
			if ((Object)(object)template == (Object)null)
			{
				bool flag = ResolvesInDonorTable(handle.SpeciesKey);
				if ((Object)(object)val != (Object)null)
				{
					Notify.Player(val, flag ? ("'" + handle.SpeciesKey + "' is known, but no body could be harvested for it (see log).") : ("No spawnable species matches '" + handle.SpeciesKey + "' (see log / 'spawnlist')."));
				}
				Fail(handle, (FailReason)(flag ? 10 : 7));
				yield break;
			}
			if ((Object)(object)val == (Object)null)
			{
				Fail(handle, (FailReason)4);
				yield break;
			}
			if (!Mint(template, val, opts, handle))
			{
				Fail(handle, (FailReason)8);
				yield break;
			}
			handle.MintFrame = Time.frameCount;
			yield return null;
			yield return null;
			if (handle.State != SpawnState.Pending)
			{
				yield break;
			}
			GameObject val2 = (((Object)(object)handle.Character != (Object)null) ? ((Component)handle.Character).gameObject : null);
			if ((Object)(object)val2 == (Object)null || !val2.activeInHierarchy)
			{
				Plugin.Log.LogError((object)"[SPAWN] spawn went INACTIVE within two frames of activation — the ASYNC duplicate-UID guard signature (AddCharacter runs from Character.Start; grep output_log.txt for 'has the same UID'). Treating as a mint FAILURE: destroying the deactivated clone and releasing its viewID (a zombie would pin a cap slot + leak the id).");
				if ((Object)(object)val2 != (Object)null)
				{
					Object.Destroy((Object)(object)val2);
				}
				DeferViewRelease(handle.ViewId, val2);
				Fail(handle, (FailReason)8);
				yield break;
			}
			float num = -1f;
			bool flag2 = false;
			try
			{
				Character character = handle.Character;
				if ((Object)(object)character != (Object)null && (Object)(object)character.Stats != (Object)null)
				{
					string text = "ok";
					try
					{
						character.Stats.RefreshVitalMaxStat(false);
					}
					catch (Exception)
					{
						text = "fallback";
						try
						{
							character.Stats.UpdateStats();
						}
						catch (Exception ex2)
						{
							text = "threw";
							Plugin.Log.LogWarning((object)("[SPAWN] mint-heal stat refresh threw: " + ex2.Message));
						}
					}
					float currentHealth = character.Stats.CurrentHealth;
					float maxHealth = character.Stats.MaxHealth;
					float baseMaxHealth = character.Stats.BaseMaxHealth;
					float num2 = MintHeal.Initial(currentHealth, maxHealth);
					if (num2 >= 0f)
					{
						character.Stats.SetHealth(num2);
					}
					int num3 = 1;
					try
					{
						if ((Object)(object)Global.Lobby != (Object)null)
						{
							num3 = Global.Lobby.PlayersInLobby.Count;
						}
					}
					catch
					{
					}
					flag2 = num3 > 1;
					num = maxHealth;
					Plugin.Log.LogMessage((object)($"[SPAWN] mint-heal uid={handle.Uid} refresh={text} hp={currentHealth:0.#}/{maxHealth:0.#} base={baseMaxHealth:0.#} " + $"-> {character.Stats.CurrentHealth:0.#}/{character.Stats.MaxHealth:0.#} lobby={num3} master={PhotonNetwork.isMasterClient} " + $"frame+{Time.frameCount - handle.MintFrame}"));
					if (num2 >= 0f)
					{
						Plugin.Log.LogMessage((object)($"[SPAWN] healed to full at mint: {currentHealth:0.#} -> {num2:0.#} " + "(the clone carries the donor's serialized current health)."));
					}
				}
			}
			catch (Exception ex3)
			{
				Plugin.Log.LogWarning((object)("[SPAWN] heal-to-full at mint threw: " + ex3.Message));
			}
			if (flag2 && num >= 0f && (Object)(object)Plugin.Instance != (Object)null)
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(SettleHealth(handle, num));
			}
			HumanoidWeapon.EnsureEquipped(handle.Character);
			CasterSkills.EnsureLearned(handle.Character);
			if (Plugin.PostActivationVisualPass != null && Plugin.PostActivationVisualPass.Value)
			{
				VisualPass.Run(handle.Character, handle.SpeciesKey);
			}
			if (PhotonNetwork.inRoom && !opts.IgnoreRoomWarm && WarmMirror.ParticipatingCount() > 0)
			{
				RoomWarmDecision val3 = Spawner.RoomWarmDecision(handle.SpeciesKey);
				if ((int)val3.Verdict == 1 || (int)val3.Verdict == 2)
				{
					if (handle.RoomGateEvaluated)
					{
						string text2 = string.Join(", ", Array.ConvertAll(val3.Actors, (int a) => a.ToString()));
						Plugin.Log.LogWarning((object)("[SPAWN] refused '" + handle.SpeciesKey + "' — cold on peer actor(s) [" + text2 + "] (RoomWarmMode=RoomStrict, " + val3.Reason + ") — the room went cold DURING the mint (an LRU eviction, a fresh joiner, or a spent budget). Tearing the body back down."));
						GameObject val4 = (((Object)(object)handle.Character != (Object)null) ? ((Component)handle.Character).gameObject : null);
						if ((Object)(object)val4 != (Object)null)
						{
							Object.Destroy((Object)(object)val4);
						}
						DeferViewRelease(handle.ViewId, val4);
						Fail(handle, (FailReason)13);
						Spawner.RequestRoomWarm(new string[1] { handle.SpeciesKey });
						yield break;
					}
					if (LogGateNotEvaluated(handle.SpeciesKey))
					{
						Plugin.Log.LogWarning((object)("[SPAWN] '" + handle.SpeciesKey + "' — a peer joined during the mint; not enforcing the room gate on a spawn authorised before it (degraded)"));
					}
					Spawner.RequestRoomWarm(new string[1] { handle.SpeciesKey });
				}
				else
				{
					handle.RoomGateOk = (int)val3.Verdict == 0 && !val3.Degraded;
				}
			}
			handle.Resolve(SpawnState.Alive, (FailReason)0);
			SpawnNet.BroadcastSpawn(handle);
		}

		private static void Fail(SpawnHandle handle, FailReason reason)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			_spawns.Remove(handle);
			handle.Resolve(SpawnState.Failed, reason);
		}

		internal static SpawnHandle FindByUid(string uid)
		{
			if (string.IsNullOrEmpty(uid))
			{
				return null;
			}
			foreach (SpawnHandle spawn in _spawns)
			{
				if (string.Equals(spawn.Uid, uid, StringComparison.Ordinal))
				{
					return spawn;
				}
			}
			return null;
		}

		private static bool ResolvesInDonorTable(string speciesKey)
		{
			try
			{
				string text = default(string);
				List<string> list = default(List<string>);
				return SpeciesTable.TryResolveKey<List<string>>(DonorHarvest.DonorScenes, speciesKey?.Trim() ?? "", ref text, ref list, (string)null) && list != null && list.Count > 0;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[SPAWN] donor-table resolve for '" + speciesKey + "' threw: " + ex.Message + " — treating as unknown species."));
				return false;
			}
		}

		internal static bool RefusedNonMasterHarvest(string tag, bool allowVoluntary = false)
		{
			if (!PhotonNetwork.isNonMasterClientInRoom)
			{
				return false;
			}
			if (allowVoluntary && Plugin.GuestPrewarm != null && Plugin.GuestPrewarm.Value)
			{
				return false;
			}
			ModLog log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)(tag + " refused: only the master client harvests donor scenes — a client would load one locally (hitch + LightProbes decay, invisible to the host)." + (allowVoluntary ? " [Coop] GuestPrewarm=false; set it true to queue a voluntary warm instead." : " Phase 3.")));
			}
			return true;
		}

		private static IEnumerator SettleHealth(SpawnHandle handle, float prevMax)
		{
			SettleState st = SettleState.Start(prevMax);
			string uid = handle.Uid;
			Character character;
			float maxHealth;
			do
			{
				yield return (object)new WaitForSeconds(0.1f);
				if (!handle.IsAlive)
				{
					yield break;
				}
				character = handle.Character;
				if ((Object)(object)character == (Object)null || (Object)(object)character.Stats == (Object)null)
				{
					yield break;
				}
				try
				{
					float currentHealth = character.Stats.CurrentHealth;
					maxHealth = character.Stats.MaxHealth;
					float prevMax2 = st.PrevMax;
					float num = MintHeal.Step(ref st, currentHealth, maxHealth);
					if (num >= 0f)
					{
						character.Stats.SetHealth(num);
						Plugin.Log.LogMessage((object)$"[SPAWN] mint-heal settle uid={uid} max {prevMax2:0.#} -> {maxHealth:0.#} hp {currentHealth:0.#} -> {num:0.#} t={st.Elapsed:0.0}s");
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[SPAWN] mint-heal settle uid=" + uid + " threw: " + ex.Message));
					yield break;
				}
			}
			while (!MintHeal.Done(ref st));
			bool isStable = ((SettleState)(ref st)).IsStable;
			float num2 = -1f;
			try
			{
				num2 = character.Stats.BaseMaxHealth;
			}
			catch
			{
			}
			string text = $"[SPAWN] mint-heal settle uid={uid} done hp={character.Stats.CurrentHealth:0.#}/{maxHealth:0.#} base={num2:0.#} " + string.Format("(startHp={0:0.#} startMax={1:0.#} moved={2}) ticks={3} t={4:0.0}s stable={5}", st.StartHp, st.StartMax, st.SawChange ? "T" : "F", st.Ticks, st.Elapsed, isStable ? "T" : "F");
			if (!isStable)
			{
				Plugin.Log.LogWarning((object)(text + " — budget hit before the max stat held still; check spawndump hp=cur/max"));
			}
			else if (!st.SawChange && maxHealth == num2)
			{
				Plugin.Log.LogWarning((object)(text + " — max==base: coop stack never landed within budget"));
			}
			else
			{
				Plugin.Log.LogMessage((object)text);
			}
		}

		private static bool Mint(GameObject template, Character player, SpawnOptions opts, SpawnHandle handle)
		{
			//IL_040a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_0485: Unknown result type (might be due to invalid IL or missing references)
			//IL_0490: Unknown result type (might be due to invalid IL or missing references)
			//IL_0496: Invalid comparison between Unknown and I4
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(template, MintHolder().transform);
			int viewId = -1;
			try
			{
				((Object)val).name = ((Object)template).name.Replace("SK_Template_", "SK_Spawn_");
				Character component = val.GetComponent<Character>();
				if ((Object)(object)component == (Object)null)
				{
					Plugin.Log.LogError((object)"[SPAWN] template clone has no Character component — destroying.");
					Object.Destroy((Object)(object)val);
					return false;
				}
				MintNormalize.Apply(val, component, opts, out var uid, ref viewId);
				handle.CorpsePolicy = CorpseRules.EffectivePolicy(opts.Corpse, Plugin.DefaultCorpsePolicy.Value);
				handle.CorpseLingerSeconds = CorpseRules.EffectiveLinger(opts.CorpseLingerSeconds, Plugin.DefaultCorpseLingerSeconds.Value);
				handle.CoopFaction = ((!opts.Faction.HasValue) ? (-1) : ((int)opts.Faction.Value));
				handle.CoopStripQuestEvents = opts.StripQuestEvents;
				handle.ConsumerData = opts.ConsumerData ?? "";
				if (Plugin.AiDisableDistance != null && Plugin.AiDisableDistance.Value > 0f)
				{
					CharacterAI component2 = val.GetComponent<CharacterAI>();
					if ((Object)(object)component2 != (Object)null)
					{
						component2.DistanceToUpdate = new Vector2(Plugin.AiDisableDistance.Value, Plugin.AiDisableDistance.Value);
					}
				}
				Vector3 val2;
				string note;
				if (opts.Position.HasValue)
				{
					val2 = opts.Position.Value;
					note = "pos=explicit";
				}
				else
				{
					val2 = PickGround(player, SpawnPolicy.EffectiveDistance(opts.Distance, Plugin.SpawnDistance.Value), out note);
				}
				Vector3 val3 = Flat(((Component)player).transform.position - val2);
				Quaternion val4 = (Quaternion)(((??)opts.Rotation) ?? ((((Vector3)(ref val3)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(val3) : Quaternion.identity));
				val.transform.SetPositionAndRotation(val2, val4);
				if (opts.OnBeforeActivate != null)
				{
					try
					{
						opts.OnBeforeActivate.Invoke(val, component);
					}
					catch (Exception arg)
					{
						Plugin.Log.LogError((object)("[SPAWN] consumer OnBeforeActivate threw for '" + handle.SpeciesKey + "' " + $"(owner '{handle.OwnerTag}') — hook skipped, mint continues: {arg}"));
					}
				}
				val.transform.SetParent((Transform)null, true);
				MintNormalize.DeferAiCulling(val, "'" + ((Object)val).name + "' (master mint)");
				CharacterAI component3 = val.GetComponent<CharacterAI>();
				if ((Object)(object)component3 != (Object)null)
				{
					try
					{
						component3.InitStartPos();
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[SPAWN] InitStartPos threw for '" + ((Object)val).name + "' (" + ex.GetType().Name + ": " + ex.Message + ") — the AI's wander home was NOT anchored to the spawn position."));
					}
				}
				string arg2 = "?";
				string arg3 = "?";
				string text = "?";
				string text2 = "?";
				string text3 = "?";
				try
				{
					arg2 = (((Object)(object)component3 != (Object)null && component3.AiStates != null) ? component3.AiStates.Length : 0).ToString();
				}
				catch
				{
				}
				try
				{
					arg3 = (((Object)(object)component3 != (Object)null && (Object)(object)component3.CurrentAiState != (Object)null) ? ((object)component3.CurrentAiState).GetType().Name : "none");
				}
				catch
				{
				}
				try
				{
					text = (((Object)(object)component.Stats != (Object)null) ? $"{component.Stats.CurrentHealth:0.#}/{component.Stats.MaxHealth:0.#} base={component.Stats.BaseMaxHealth:0.#}" : "?");
				}
				catch
				{
				}
				try
				{
					text2 = ((object)Unsafe.As<Factions, Factions>(ref component.Faction)/*cast due to .constrained prefix*/).ToString();
				}
				catch
				{
				}
				try
				{
					text3 = component.Alive.ToString();
				}
				catch
				{
				}
				Plugin.Log.LogMessage((object)($"[SPAWN] '{((Object)val).name}' uid={uid} viewID={viewId} owner='{handle.OwnerTag}' pos={val2:F1} ({note}) | " + string.Format("active={0} alive={1} hp={2} faction={3} lifetime={4} ", val.activeInHierarchy, text3, text, text2, opts.LifetimeSeconds.HasValue ? opts.LifetimeSeconds.Value.ToString("0.#") : "donor") + string.Format("corpse={0}{1} | ", handle.CorpsePolicy, ((int)handle.CorpsePolicy == 1) ? $"({handle.CorpseLingerSeconds:0.#}s)" : "") + $"aiStates={arg2} state={arg3} agent={(Object)(object)val.GetComponent<NavMeshAgent>() != (Object)null} " + $"charAIDisable={(Object)(object)val.GetComponent<CharAIDisable>() != (Object)null} " + "aiLive=" + DescribeAiLiveness(component, component3)));
				try
				{
					string text4 = SpawnTemplates.ResolvedDonorName(handle.SpeciesKey);
					if (!string.IsNullOrEmpty(text4) && !string.Equals(text4, handle.SpeciesKey, StringComparison.Ordinal))
					{
						Plugin.Log.LogMessage((object)("[SPAWN] donor for '" + handle.SpeciesKey + "': '" + text4 + "' (template identity — see the [SPAWN] label warning if mismatched)."));
					}
				}
				catch
				{
				}
				if (!val.activeInHierarchy)
				{
					Plugin.Log.LogError((object)"[SPAWN] spawn is INACTIVE right after activation — duplicate-UID guard signature (grep output_log.txt for 'has the same UID'). Treating as a mint FAILURE: destroying the deactivated clone and releasing its viewID (a zombie would pin a cap slot + leak the id).");
					Object.Destroy((Object)(object)val);
					DeferViewRelease(viewId, val);
					return false;
				}
				handle.Character = component;
				handle.Uid = uid;
				handle.ViewId = viewId;
				return true;
			}
			catch (Exception arg4)
			{
				Plugin.Log.LogError((object)$"[SPAWN] mint threw — destroying pending clone and releasing viewID {viewId}: {arg4}");
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
					DeferViewRelease(viewId, val);
				}
				else
				{
					ReleaseViewId(viewId);
				}
				return false;
			}
		}

		private static Vector3 PickGround(Character player, float distance, out string note)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)player).transform.position;
			Vector3 forward = ((Component)player).transform.forward;
			IReadOnlyList<(float, float)> readOnlyList = RingPlacement.Candidates(forward.x, forward.z, distance, 16);
			Vector3 val = default(Vector3);
			NavMeshHit val2 = default(NavMeshHit);
			for (int i = 0; i < readOnlyList.Count; i++)
			{
				((Vector3)(ref val))..ctor(position.x + readOnlyList[i].Item1, position.y, position.z + readOnlyList[i].Item2);
				if (NavMesh.SamplePosition(val, ref val2, 4f, -1) && Mathf.Abs(((NavMeshHit)(ref val2)).position.y - position.y) <= 2.5f)
				{
					note = $"ring[{i}]";
					return ((NavMeshHit)(ref val2)).position;
				}
			}
			note = "NO navmesh candidate in elevation band — placed ahead unprobed";
			Vector3 val3 = Flat(forward);
			Vector3 val4;
			if (!(((Vector3)(ref val3)).sqrMagnitude > 0.01f))
			{
				val4 = Vector3.forward;
			}
			else
			{
				val3 = Flat(forward);
				val4 = ((Vector3)(ref val3)).normalized;
			}
			return position + val4 * distance;
		}

		private static Vector3 Flat(Vector3 v)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(v.x, 0f, v.z);
		}

		internal static void Despawn(SpawnHandle handle, bool kill)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if (handle == null)
			{
				return;
			}
			if (handle.State == SpawnState.Pending)
			{
				Fail(handle, (FailReason)9);
			}
			else
			{
				if (handle.State != SpawnState.Alive)
				{
					return;
				}
				Character character = handle.Character;
				if (kill && (Object)(object)character != (Object)null && SafeAlive(character))
				{
					try
					{
						character.ReceiveHit((Weapon)null, 999999f, Vector3.forward, character.CenterPosition, 45f, 1f, (Character)null, 0f);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[SPAWN] overkill ReceiveHit threw on '" + ((Object)((Component)character).gameObject).name + "' (uid " + handle.Uid + ") — a third-party ReceiveHit patch? Falling back to a silent despawn. (" + ex.Message + ")"));
					}
					if (!SafeAlive(character))
					{
						Plugin.Log.LogMessage((object)("[SPAWN] overkilled '" + ((Object)((Component)character).gameObject).name + "' (uid " + handle.Uid + ") — WatchTick will resolve the death (loot + disengage on the Died transition)."));
						return;
					}
					Plugin.Log.LogWarning((object)("[SPAWN] overkill left '" + ((Object)((Component)character).gameObject).name + "' Alive (invincible/resistant?) — falling back to a silent despawn (uid " + handle.Uid + ")."));
				}
				if (_spawns.Remove(handle))
				{
					SpawnDisengage.DisengageSpawn((RemovalReason)0, character);
					GameObject val = (((Object)(object)character != (Object)null) ? ((Component)character).gameObject : null);
					if ((Object)(object)val != (Object)null)
					{
						Object.Destroy((Object)(object)val);
					}
					DeferViewRelease(handle.ViewId, val);
					handle.State = SpawnState.Despawned;
					SpawnNet.SendGone(handle.Uid, (GoneKind)1);
					handle.FireDespawned();
				}
			}
		}

		public static void DespawnAll(string ownerTag, bool kill)
		{
			WatchTick();
			int num = 0;
			int num2 = 0;
			SpawnHandle[] array = _spawns.ToArray();
			foreach (SpawnHandle spawnHandle in array)
			{
				if (SpawnPolicy.MatchesOwner(spawnHandle.OwnerTag, ownerTag))
				{
					num++;
					try
					{
						Despawn(spawnHandle, kill);
					}
					catch (Exception ex)
					{
						num2++;
						Plugin.Log.LogWarning((object)("[SPAWN] despawnall: Despawn threw for uid " + spawnHandle.Uid + " — continuing the sweep. (" + ex.Message + ")"));
					}
				}
			}
			Plugin.Log.LogMessage((object)("[SPAWN] despawnall (" + (kill ? "kill" : "silent") + ((ownerTag != null) ? (", owner '" + ownerTag + "'") : "") + $"): {num} processed" + ((num2 > 0) ? $" ({num2} threw — see warnings)" : "") + $", {_spawns.Count} still tracked pre-destroy (async — spawndump for the settled count)."));
		}

		internal static IReadOnlyList<SpawnHandle> Snapshot(string ownerTag)
		{
			List<SpawnHandle> list = new List<SpawnHandle>();
			Snapshot(ownerTag, list);
			return list;
		}

		internal static void Snapshot(string ownerTag, List<SpawnHandle> into)
		{
			into.Clear();
			foreach (SpawnHandle spawn in _spawns)
			{
				if (SpawnPolicy.MatchesOwner(spawn.OwnerTag, ownerTag))
				{
					into.Add(spawn);
				}
			}
		}

		internal static int LootProbeAll()
		{
			WatchTick();
			int num = 0;
			SpawnHandle[] array = _spawns.ToArray();
			foreach (SpawnHandle spawnHandle in array)
			{
				if (spawnHandle.State == SpawnState.Alive)
				{
					Character character = spawnHandle.Character;
					if (!((Object)(object)character == (Object)null))
					{
						LootProbe.Log(character, "probe '" + spawnHandle.SpeciesKey + "' (owner '" + spawnHandle.OwnerTag + "')");
						num++;
					}
				}
			}
			return num;
		}

		public static string Dump()
		{
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			WatchTick();
			Character val = Lifecycle.FirstLocalCharacterOrNull();
			string arg = ((!PhotonNetwork.inRoom) ? "" : (" role=" + (PhotonNetwork.isMasterClient ? "MASTER" : "GUEST") + " (replicas: see [MIRROR] below)"));
			int pendingPersistCount = Lease.PendingPersistCount;
			int num = PendingReleaseCount - pendingPersistCount;
			string text = ((pendingPersistCount > 0) ? $" ({pendingPersistCount} viewID release(s) parked on surviving corpses)" : "") + ((num > 0) ? $" ({num} viewID release(s) parked pending teardown)" : "");
			object arg2 = _spawns.Count;
			Scene activeScene = SceneManager.GetActiveScene();
			StringBuilder stringBuilder = new StringBuilder($"[SPAWN] {arg2} tracked spawn(s) in '{((Scene)(ref activeScene)).name}'{arg}" + text + ":");
			foreach (SpawnHandle spawn in _spawns)
			{
				if (spawn.State == SpawnState.Pending)
				{
					stringBuilder.Append("\n  '" + spawn.SpeciesKey + "' owner='" + spawn.OwnerTag + "' state=Pending (template acquiring)");
					continue;
				}
				Character character = spawn.Character;
				if ((Object)(object)character == (Object)null)
				{
					continue;
				}
				string text2 = "?";
				string text3 = "?";
				string text4 = "-";
				try
				{
					text2 = (((Object)(object)character.Stats != (Object)null) ? $"{character.Stats.CurrentHealth:0.#}/{character.Stats.MaxHealth:0.#} base={character.Stats.BaseMaxHealth:0.#}" : "?");
				}
				catch
				{
				}
				CharacterAI component = ((Component)character).GetComponent<CharacterAI>();
				try
				{
					text3 = (((Object)(object)component != (Object)null && (Object)(object)component.CurrentAiState != (Object)null) ? ((object)component.CurrentAiState).GetType().Name : "none");
				}
				catch
				{
				}
				try
				{
					Character val2 = (((Object)(object)component != (Object)null && (Object)(object)component.TargetingSystem != (Object)null) ? component.TargetingSystem.LockedCharacter : null);
					if ((Object)(object)val2 != (Object)null)
					{
						text4 = val2.Name;
					}
				}
				catch
				{
				}
				float num2 = (((Object)(object)val != (Object)null) ? Vector3.Distance(((Component)val).transform.position, ((Component)character).transform.position) : (-1f));
				stringBuilder.Append($"\n  '{((Object)((Component)character).gameObject).name}' species='{spawn.SpeciesKey}' owner='{spawn.OwnerTag}' lifecycle={spawn.State} " + $"uid={character.UID} viewID={spawn.ViewId} alive={SafeAlive(character)} hp={text2} dist={num2:0.#} " + $"active={((Component)character).gameObject.activeInHierarchy} aiEnabled={(Object)(object)component != (Object)null && ((Behaviour)component).enabled} " + "aiLive=" + DescribeAiLiveness(character, component) + " state=" + text3 + " target=" + text4 + " " + HumanoidWeapon.StateFor(character) + " " + CasterSkills.StateFor(character));
			}
			string text5 = PendingReleasesDump();
			if (text5.Length > 0)
			{
				stringBuilder.Append('\n').Append(text5);
			}
			return stringBuilder.ToString();
		}

		internal static string DescribeAiLiveness(Character c, CharacterAI cai)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				bool flag = (Object)(object)cai != (Object)null;
				int num = 0;
				try
				{
					num = ((flag && cai.AiStates != null) ? cai.AiStates.Length : 0);
				}
				catch
				{
				}
				bool flag2 = false;
				try
				{
					flag2 = flag && ((Behaviour)cai).enabled;
				}
				catch
				{
				}
				bool flag3 = false;
				try
				{
					flag3 = (Object)(object)c != (Object)null && c.IsStartInitDone;
				}
				catch
				{
				}
				bool flag4 = false;
				try
				{
					flag4 = flag && ((CharacterControl)cai).CloseToPlayer;
				}
				catch
				{
				}
				bool flag5 = false;
				try
				{
					flag5 = (Object)(object)NetworkLevelLoader.Instance != (Object)null && NetworkLevelLoader.Instance.IsGameplayPaused;
				}
				catch
				{
				}
				return AiLivenessRules.Format(AiLivenessRules.Classify(flag, num, flag2, flag3, flag4, flag5));
			}
			catch (Exception ex)
			{
				return "?(" + ex.GetType().Name + ")";
			}
		}

		public static void WatchTick()
		{
			SweepPendingReleases();
			bool flag = !_watchScratchBusy;
			List<SpawnHandle> list;
			if (flag)
			{
				_watchScratchBusy = true;
				_watchScratch.Clear();
				list = _watchScratch;
			}
			else
			{
				list = new List<SpawnHandle>(_spawns.Count);
			}
			list.AddRange(_spawns);
			try
			{
				WatchWalk(list);
			}
			finally
			{
				if (flag)
				{
					_watchScratch.Clear();
					_watchScratchBusy = false;
				}
			}
		}

		private static void WatchWalk(List<SpawnHandle> snapshot)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected I4, but got Unknown
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Invalid comparison between Unknown and I4
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			foreach (SpawnHandle item in snapshot)
			{
				if (item.State != SpawnState.Alive)
				{
					continue;
				}
				bool flag = (Object)(object)item.Character != (Object)null;
				bool flag2 = flag && SafeAlive(item.Character);
				SpawnState spawnState = (SpawnState)SpawnWatch.Next((WatchState)item.State, flag, flag2);
				if (spawnState == item.State)
				{
					continue;
				}
				GameObject val = ((spawnState == SpawnState.Died) ? ((Component)item.Character).gameObject : null);
				if (spawnState == SpawnState.Died)
				{
					LootProbe.Log(item.Character, "death '" + item.SpeciesKey + "'");
				}
				SpawnDisengage.DisengageSpawn((RemovalReason)((spawnState == SpawnState.Died) ? 2 : 3), (spawnState == SpawnState.Died) ? item.Character : null);
				if (!_spawns.Remove(item))
				{
					continue;
				}
				bool flag3 = false;
				if (spawnState == SpawnState.Died && (int)item.CorpsePolicy == 0 && Plugin.CorpseViewRelease != null && Plugin.CorpseViewRelease.Value)
				{
					try
					{
						Views.Neutralize(val, "corpse '" + item.SpeciesKey + "' uid " + item.Uid);
						flag3 = true;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[SPAWN] CorpseViewRelease neutralize threw for uid " + item.Uid + " (" + ex.GetType().Name + ": " + ex.Message + ") — falling back to the mute-and-park path."));
					}
					if (flag3)
					{
						ReleaseViewId(item.ViewId);
					}
				}
				if (!flag3)
				{
					if (spawnState == SpawnState.Died)
					{
						MuteCorpseView(item.Character);
					}
					DeferViewRelease(item.ViewId, (spawnState == SpawnState.Died) ? val : null, spawnState == SpawnState.Died && (int)item.CorpsePolicy == 0);
				}
				item.State = spawnState;
				Plugin.Log.LogMessage((object)$"[WATCH] '{item.SpeciesKey}' (owner '{item.OwnerTag}', uid {item.Uid}) -> {spawnState}.");
				SpawnNet.SendGone(item.Uid, (GoneKind)(spawnState != SpawnState.Died));
				if (spawnState == SpawnState.Died)
				{
					item.FireDied();
				}
				else
				{
					item.FireDespawned();
				}
				if (spawnState == SpawnState.Died)
				{
					CorpseGC.ScheduleCorpseRemoval(item, val);
				}
			}
		}

		internal static bool SafeAlive(Character c)
		{
			try
			{
				return c.Alive;
			}
			catch
			{
				return false;
			}
		}
	}
	internal static class ExpeditionRun
	{
		public static bool InProgress { get; private set; }

		public static string CurrentSpecies { get; private set; }

		public static string ForceReset()
		{
			if (!InProgress)
			{
				return "[EXPEDITION] SpawnKit's trip guard is already open — nothing to reset on this side.";
			}
			string text = CurrentSpecies ?? "?";
			InProgress = false;
			CurrentSpecies = null;
			return "[EXPEDITION] FORCE RESET SpawnKit's trip guard (was waiting on '" + text + "'). The spawn menu accepts expeditions again.";
		}

		public static bool ForSpecies(string speciesKey, Action<bool, string> onDone, bool force = false)
		{
			string key = (speciesKey ?? "").Trim();
			bool fired = false;
			Action<bool, string> done = delegate(bool ok, string why)
			{
				if (fired)
				{
					Plugin.Log.LogWarning((object)("[EXPEDITION] double completion for '" + key + "' suppressed (" + why + ")."));
					return;
				}
				fired = true;
				try
				{
					onDone?.Invoke(ok, why);
				}
				catch (Exception arg2)
				{
					Plugin.Log.LogError((object)$"[EXPEDITION] onDone callback for '{key}' threw: {arg2}");
				}
			};
			if (!Plugin.EnableExpeditions.Value)
			{
				done.Invoke(false, "expeditions are disabled ([Expedition] EnableExpeditions = false)");
				return false;
			}
			if (key.Length == 0)
			{
				done.Invoke(false, "no species given");
				return false;
			}
			if (InProgress || ExpeditionHarvest.InProgress)
			{
				done.Invoke(false, "an expedition is already running");
				return false;
			}
			if (!force && Spawner.CanMintNow(key))
			{
				done.Invoke(true, "already resident — no trip needed");
				return false;
			}
			string text = default(string);
			List<string> list = default(List<string>);
			if (!SpeciesTable.TryResolveKey<List<string>>(DonorHarvest.DonorScenes, key, ref text, ref list, (string)null) || list == null || list.Count == 0)
			{
				done.Invoke(false, "'" + key + "' is not in the donor table — 'spawnlist' shows the spawnable species");
				return false;
			}
			if (!force && !Spawner.IsExpeditionOnly(key))
			{
				done.Invoke(false, "'" + key + "' has an ADDITIVE donor — 'spawnprewarm " + key + "' harvests it with no loading screens (pass 'force' to take the trip anyway)");
				return false;
			}
			List<string> list2 = default(List<string>);
			string text2 = default(string);
			if (!DonorHarvest.TryGetExpeditionScenes(key, ref list2, ref text2) || list2 == null || list2.Count == 0)
			{
				done.Invoke(false, "'" + key + "' has no expedition donor scene (see 'spawnlist' / DonorScenes.txt)");
				return false;
			}
			string scene = list2[0];
			InProgress = true;
			CurrentSpecies = key;
			try
			{
				if (!ExpeditionOrchestrator.BeginTrip(scene, (Action<TripResult>)delegate(TripResult trip)
				{
					//IL_000c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0045: Unknown result type (might be due to invalid IL or missing references)
					//IL_007f: Unknown result type (might be due to invalid IL or missing references)
					//IL_009b: Unknown result type (might be due to invalid IL or missing references)
					//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
					InProgress = false;
					CurrentSpecies = null;
					if (!trip.EndedHome)
					{
						Plugin.Log.LogError((object)("[EXPEDITION] SpawnKit: the trip to '" + scene + "' did NOT bring the party home — refusing to spawn on top of it. See the teardown line above; 'goto <scene>' recovers."));
						done.Invoke(false, "the expedition did not bring you home — see the log");
					}
					else if (!trip.PayloadRan)
					{
						done.Invoke(false, "the expedition to '" + scene + "' was aborted before it could harvest");
					}
					else
					{
						Plugin.Log.LogMessage((object)$"[EXPEDITION] SpawnKit: home from '{scene}' — {trip.Built} new body template(s) cached.");
						done.Invoke(true, (trip.Built > 0) ? $"harvested {trip.Built} new body template(s) from {scene}" : (scene + " harvested (0 new — the cache already covered it)"));
					}
				}))
				{
					InProgress = false;
					CurrentSpecies = null;
					done.Invoke(false, "the expedition would not start — see the refusal in the log");
					return false;
				}
				if (!fired)
				{
					Plugin.Log.LogMessage((object)("[EXPEDITION] SpawnKit: '" + text2 + "' → donor region '" + scene + "'" + ((list2.Count > 1) ? $" (+{list2.Count - 1} more candidate(s))" : "") + " — two loading screens, there and back. One trip caches EVERY species that region donates."));
				}
				return true;
			}
			catch (Exception arg)
			{
				InProgress = false;
				CurrentSpecies = null;
				Plugin.Log.LogError((object)$"[EXPEDITION] SpawnKit: launching the trip to '{scene}' threw: {arg}");
				done.Invoke(false, "the expedition threw before it could start — see the log");
				return false;
			}
		}
	}
	internal static class GhostDiag
	{
		private static int sampling;

		internal static bool Enabled
		{
			get
			{
				if (Plugin.GhostDiagnostics != null)
				{
					return Plugin.GhostDiagnostics.Value;
				}
				return false;
			}
		}

		internal static void Dump(Character ch, string tag)
		{
			if ((Object)(object)ch == (Object)null || (Object)(object)((Component)ch).gameObject == (Object)null)
			{
				return;
			}
			try
			{
				Plugin.Log.LogMessage((object)Describe(ch, tag));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[GHOSTDIAG] " + tag + " threw: " + ex.Message));
			}
		}

		internal static void DumpDelayed(Character ch)
		{
			if (!((Object)(object)ch == (Object)null) && Enabled && !((Object)(object)Plugin.Instance == (Object)null) && sampling < 3)
			{
				sampling++;
				((MonoBehaviour)Plugin.Instance).StartCoroutine(DelayedRoutine(ch));
			}
		}

		private static IEnumerator DelayedRoutine(Character ch)
		{
			try
			{
				for (int f = 0; f < 60; f++)
				{
					if ((Object)(object)ch == (Object)null || (Object)(object)((Component)ch).gameObject == (Object)null)
					{
						yield break;
					}
					yield return null;
				}
				float until = Time.unscaledTime + 5f;
				while (Time.unscaledTime < until && IsGameplayLoading())
				{
					yield return null;
				}
				if (!((Object)(object)ch == (Object)null) && !((Object)(object)((Component)ch).gameObject == (Object)null))
				{
					Dump(ch, "delayed");
				}
			}
			finally
			{
				sampling--;
			}
		}

		private static bool IsGameplayLoading()
		{
			try
			{
				return (Object)(object)NetworkLevelLoader.Instance != (Object)null && NetworkLevelLoader.Instance.IsGameplayLoading;
			}
			catch
			{
				return false;
			}
		}

		internal static void DumpNearby(string nameFilter, bool fix, float radius = 30f)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			Character val = Lifecycle.FirstLocalCharacterOrNull();
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[GHOSTDIAG] no local player — nothing to census.");
				return;
			}
			CharacterManager instance = CharacterManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[GHOSTDIAG] no CharacterManager.");
				return;
			}
			DictionaryExt<string, Character> characters = instance.Characters;
			int num = 0;
			for (int i = 0; i < characters.Count; i++)
			{
				Character val2 = characters.Values[i];
				if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)val) && !(Vector3.Distance(((Component)val2).transform.position, ((Component)val).transform.position) > radius) && (string.IsNullOrEmpty(nameFilter) || (val2.Name != null && val2.Name.IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0)))
				{
					num++;
					if (!fix)
					{
						Dump(val2, "verb");
						continue;
					}
					Dump(val2, "preFix");
					GhostRig.Reinit(val2, "verb", force: true);
					Dump(val2, "postFix");
				}
			}
			if (num == 0)
			{
				Plugin.Log.LogWarning((object)($"[GHOSTDIAG] no characters within {radius:0}m" + (string.IsNullOrEmpty(nameFilter) ? "" : (" matching '" + nameFilter + "'")) + "."));
			}
		}

		internal static string Describe(Character ch, string tag)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0511: Unknown result type (might be due to invalid IL or missing references)
			//IL_0520: Unknown result type (might be due to invalid IL or missing references)
			//IL_052f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0401: Unknown result type (might be due to invalid IL or missing references)
			//IL_0406: Unknown result type (might be due to invalid IL or missing references)
			//IL_0408: Unknown result type (might be due to invalid IL or missing references)
			//IL_040d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0482: Unknown result type (might be due to invalid IL or missing references)
			//IL_0477: Unknown result type (might be due to invalid IL or missing references)
			//IL_0479: Unknown result type (might be due to invalid IL or missing references)
			//IL_047b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0487: Unknown result type (might be due to invalid IL or missing references)
			//IL_0438: Unknown result type (might be due to invalid IL or missing references)
			//IL_043d: Unknown result type (might be due to invalid IL or missing references)
			//IL_044f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0451: Unknown result type (might be due to invalid IL or missing references)
			//IL_0453: Unknown result type (might be due to invalid IL or missing references)
			//IL_0458: Unknown result type (might be due to invalid IL or missing references)
			//IL_045a: Unknown result type (might be due to invalid IL or missing references)
			//IL_045c: Unknown result type (might be due to invalid IL or missing references)
			//IL_045e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0463: Unknown result type (might be due to invalid IL or missing references)
			//IL_0443: Unknown result type (might be due to invalid IL or missing references)
			//IL_0445: Unknown result type (might be due to invalid IL or missing references)
			//IL_0446: Unknown result type (might be due to invalid IL or missing references)
			//IL_0448: Unknown result type (might be due to invalid IL or missing references)
			//IL_074f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0754: Unknown result type (might be due to invalid IL or missing references)
			//IL_0756: Unknown result type (might be due to invalid IL or missing references)
			//IL_075b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0784: Unknown result type (might be due to invalid IL or missing references)
			//IL_0776: Unknown result type (might be due to invalid IL or missing references)
			//IL_0789: Unknown result type (might be due to invalid IL or missing references)
			//IL_07af: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_07bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0823: Unknown result type (might be due to invalid IL or missing references)
			//IL_0818: Unknown result type (might be due to invalid IL or missing references)
			//IL_081a: Unknown result type (might be due to invalid IL or missing references)
			//IL_081c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0828: Unknown result type (might be due to invalid IL or missing references)
			//IL_082b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0830: Unknown result type (might be due to invalid IL or missing references)
			//IL_087a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0886: Unknown result type (might be due to invalid IL or missing references)
			//IL_0892: Unknown result type (might be due to invalid IL or missing references)
			//IL_08ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_08b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_08c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_08e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_08e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_08eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_07e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_07f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_07f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_07fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0804: Unknown result type (might be due to invalid IL or missing references)
			//IL_07d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_07dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_07dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_07df: Unknown result type (might be due to invalid IL or missing references)
			GameObject gameObject = ((Component)ch).gameObject;
			StringBuilder stringBuilder = new StringBuilder();
			string text = "?";
			try
			{
				text = ((object)ch.UID/*cast due to .constrained prefix*/).ToString();
			}
			catch
			{
			}
			stringBuilder.Append("[GHOSTDIAG] " + tag + " '" + ch.Name + "' uid=" + text);
			CharacterVisuals visuals = ch.Visuals;
			GameObject val = (((Object)(object)visuals != (Object)null) ? ((Component)visuals).gameObject : null);
			stringBuilder.Append("\n  init   startInit=" + YN(ch.m_startInitDone) + " visualsHolder=" + (((Object)(object)visuals == (Object)null) ? "null" : ((Object)visuals).name)).Append(" holderActiveSelf=" + YN((Object)(object)val != (Object)null && val.activeSelf) + " holderActiveInHier=" + YN((Object)(object)val != (Object)null && val.activeInHierarchy)).Append(" defVisInit=" + YN((Object)(object)visuals != (Object)null && visuals.DefaultVisualsInitialized))
				.Append(" visualDataNull=" + YN(ch.VisualData == null) + " prefabNull=" + YN((Object)(object)ch.CharacterVisualsPrefab == (Object)null))
				.Append(" gameplayLoading=" + YN(IsGameplayLoading()) + " isAI=" + YN(ch.IsAI));
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			if ((Object)(object)visuals != (Object)null)
			{
				Transform[] componentsInChildren = ((Component)visuals).GetComponentsInChildren<Transform>(true);
				foreach (Transform val2 in componentsInChildren)
				{
					num3++;
					if (((Component)val2).gameObject.activeSelf)
					{
						num4++;
					}
					if (((Object)val2).name.EndsWith("_v", StringComparison.Ordinal))
					{
						num++;
						if (((Component)val2).gameObject.activeSelf)
						{
							num2++;
						}
					}
				}
			}
			stringBuilder.Append($"\n  vtree  _vNodes={num} _vActiveSelf={num2} visSubtreeActive={num4}/{num3}");
			int num5 = LayerMask.NameToLayer("Hitbox");
			Hitbox[] a = (((Object)(object)visuals != (Object)null) ? visuals.Hitboxes : null);
			Hitbox[] componentsInChildren2 = gameObject.GetComponentsInChildren<Hitbox>(true);
			stringBuilder.Append($"\n  hits   m_hitboxes={Len(ch.m_hitboxes)} visHitboxes={Len(a)} liveHitboxes={Len(componentsInChildren2)}").Append($" hitboxLayerIdx={num5} layers=[{LayerHistogram(componentsInChildren2)}]");
			int num6 = 0;
			int num7 = 0;
			int num8 = 0;
			List<Collider> ragdollHitboxColliders = ch.m_ragdollHitboxColliders;
			if (ragdollHitboxColliders != null)
			{
				foreach (Collider item in ragdollHitboxColliders)
				{
					if (!((Object)(object)item == (Object)null))
					{
						num8++;
						if (item.enabled)
						{
							num7++;
						}
						if (((Component)item).gameObject.layer == num5)
						{
							num6++;
						}
					}
				}
			}
			stringBuilder.Append("\n  rag    ragdollIsHitbox=" + YN(ch.RagdollIsHitbox) + " ragdollRoot=" + (((Object)(object)ch.RagdollRoot == (Object)null) ? "null" : ((Object)ch.RagdollRoot).name)).Append($" ragdollColliders={Count(ch.m_ragdollColliders)} ragdollHitboxColliders={num8}").Append($" onHitboxLayer={num6}/{num8} enabled={num7}/{num8} ragdollActive={YN(ch.RagdollActive)}");
			Transform[] array = (((Object)(object)visuals != (Object)null) ? visuals.AttackTransforms : null);
			int num9 = 0;
			Vector3 val3 = Vector3.zero;
			Vector3 val4 = Vector3.zero;
			bool flag = true;
			if (array != null)
			{
				Transform[] array2 = array;
				foreach (Transform val5 in array2)
				{
					if (!((Object)(object)val5 == (Object)null))
					{
						num9++;
						Vector3 position = val5.position;
						if (flag)
						{
							val3 = (val4 = position);
							flag = false;
						}
						else
						{
							val3 = Vector3.Min(val3, position);
							val4 = Vector3.Max(val4, position);
						}
					}
				}
			}
			Vector3 val6 = (flag ? Vector3.zero : (val4 - val3));
			Weapon val7 = null;
			try
			{
				val7 = ch.CurrentWeapon;
			}
			catch
			{
			}
			Transform val8 = null;
			Transform val9 = null;
			Transform val10 = null;
			try
			{
				if ((Object)(object)val7 != (Object)null && (Object)(object)((Equipment)val7).EquippedVisuals != (Object)null)
				{
					val8 = ((Component)((Equipment)val7).EquippedVisuals).transform;
					val9 = val8.Find("_LinecastStart");
					val10 = val8.Find("_LinecastEnd");
				}
			}
			catch
			{
			}
			stringBuilder.Append($"\n  atk    attackTransforms={Len(array)} nonNull={num9} spread=({val6.x:0.0#}, {val6.y:0.0#}, {val6.z:0.0#})").Append(" weapon=" + (((Object)(object)val7 == (Object)null) ? "-" : ((Item)val7).Name) + " equippedVisuals=" + (((Object)(object)val8 == (Object)null) ? "null" : ((Object)val8).name)).Append(" linecastStart=" + (((Object)(object)val9 == (Object)null) ? "MISSING" : "found") + " linecastEnd=" + (((Object)(object)val10 == (Object)null) ? "MISSING" : "found"))
				.Append(" unarmedDetector=" + (((Object)(object)visuals != (Object)null && (Object)(object)visuals.UnarmedHitDetector != (Object)null) ? "set" : "null"));
			LockingPoint lockingPoint = ch.LockingPoint;
			GameObject val11 = (((Object)(object)lockingPoint != (Object)null) ? ((Component)lockingPoint).gameObject : null);
			Collider val12 = (((Object)(object)val11 != (Object)null) ? val11.GetComponent<Collider>() : null);
			stringBuilder.Append("\n  lock   lockingPoint=" + (((Object)(object)lockingPoint == (Object)null) ? "null" : ((Object)lockingPoint).name)).Append(" activeInHier=" + YN((Object)(object)val11 != (Object)null && val11.activeInHierarchy)).Append(" layer=" + (((Object)(object)val11 == (Object)null) ? "-" : LayerName(val11.layer)))
				.Append(" collider=" + (((Object)(object)val12 == (Object)null) ? "MISSING" : (val12.enabled ? "enabled" : "disabled")))
				.Append(" inLockMask=" + (((Object)(object)val11 == (Object)null) ? "-" : YN((Global.LockingPointsMask & (1 << val11.layer)) != 0)));
			SkinnedMeshRenderer val13 = VisualPass.LargestSmr(gameObject);
			if ((Object)(object)val13 == (Object)null)
			{
				stringBuilder.Append("\n  pose   bodySmr=none");
			}
			else
			{
				Transform[] bones = val13.bones;
				int num10 = 0;
				int num11 = 0;
				Vector3 val14 = Vector3.zero;
				Vector3 val15 = Vector3.zero;
				bool flag2 = true;
				Vector3 val16 = (((Object)(object)val13.rootBone != (Object)null) ? val13.rootBone.position : ((Component)val13).transform.position);
				Transform[] array3 = bones;
				foreach (Transform val17 in array3)
				{
					if ((Object)(object)val17 == (Object)null)
					{
						num11++;
						continue;
					}
					Vector3 val18 = val17.position - val16;
					if (((Vector3)(ref val18)).sqrMagnitude < 0.0001f)
					{
						num10++;
					}
					if (flag2)
					{
						val14 = (val15 = val17.position);
						flag2 = false;
					}
					else
					{
						val14 = Vector3.Min(val14, val17.position);
						val15 = Vector3.Max(val15, val17.position);
					}
				}
				Vector3 val19 = (flag2 ? Vector3.zero : (val15 - val14));
				Census val20 = VisualPass.Measure(gameObject);
				StringBuilder stringBuilder2 = stringBuilder.Append($"\n  pose   bodySmr='{((Object)val13).name}' bones={Len(bones)} null={num11} stacked={num10}").Append($" boneSpread=({val19.x:0.0#}, {val19.y:0.0#}, {val19.z:0.0#})").Append($" baked=({val20.BakedX:0.0#}, {val20.BakedY:0.0#}, {val20.BakedZ:0.0#})");
				Bounds localBounds = val13.localBounds;
				stringBuilder2.Append(string.Format(" localBounds={0} rootBone={1}", ((Bounds)(ref localBounds)).size, ((Object)(object)val13.rootBone == (Object)null) ? "null" : ((Object)val13.rootBone).name)).Append(" enabled=" + YN(((Renderer)val13).enabled) + " active=" + YN(((Component)val13).gameObject.activeInHierarchy));
			}
			stringBuilder.Append("\n  scale  " + ScaleChain(val13, gameObject));
			stringBuilder.Append(" | ghostly=" + YN(SafeGhostly(ch)) + " useLegacyVisual=" + YN(ch.UseLegacyVisual));
			stringBuilder.Append("\n  diag   " + VisualPass.DiagStr(ch));
			try
			{
				stringBuilder.Append('\n').Append(SkeletonRig.Census(gameObject));
			}
			catch
			{
			}
			return stringBuilder.ToString();
		}

		private static string YN(bool b)
		{
			if (!b)
			{
				return "F";
			}
			return "T";
		}

		private static string ScaleChain(SkinnedMeshRenderer body, GameObject root)
		{
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Transform val = (((Object)(object)body != (Object)null && (Object)(object)body.rootBone != (Object)null) ? body.rootBone : (((Object)(object)body != (Object)null) ? ((Component)body).transform : null));
				if ((Object)(object)val == (Object)null)
				{
					return "no rootBone — nothing to walk";
				}
				StringBuilder stringBuilder = new StringBuilder();
				int num = 0;
				while ((Object)(object)val != (Object)null && num++ < 24)
				{
					Vector3 localScale = val.localScale;
					Vector3 lossyScale = val.lossyScale;
					bool flag = Mathf.Abs(localScale.x) < 0.1f || Mathf.Abs(localScale.y) < 0.1f || Mathf.Abs(localScale.z) < 0.1f;
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(" < ");
					}
					stringBuilder.Append($"{((Object)val).name}[{localScale.x:0.0##},{localScale.y:0.0##},{localScale.z:0.0##}]");
					if (flag)
					{
						stringBuilder.Append("<<DEGENERATE");
					}
					if ((Object)(object)((Component)val).gameObject == (Object)(object)root)
					{
						break;
					}
					val = val.parent;
				}
				Vector3 val2 = (((Object)(object)body != (Object)null && (Object)(object)((Component)body).transform != (Object)null) ? ((Component)body).transform.lossyScale : Vector3.zero);
				return stringBuilder.Append($" | smrLossy=({val2.x:0.0##}, {val2.y:0.0##}, {val2.z:0.0##})").ToString();
			}
			catch (Exception ex)
			{
				return "threw (" + ex.GetType().Name + ")";
			}
		}

		private static bool SafeGhostly(Character ch)
		{
			try
			{
				return ch.Ghostly;
			}
			catch
			{
				return false;
			}
		}

		private static string LayerName(int layer)
		{
			string text = LayerMask.LayerToName(layer);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return layer.ToString();
		}

		private static int Len(Array a)
		{
			return a?.Length ?? (-1);
		}

		private static int Count(ICollection c)
		{
			return c?.Count ?? (-1);
		}

		private static string LayerHistogram(Hitbox[] hits)
		{
			if (hits == null || hits.Length == 0)
			{
				return "";
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (Hitbox val in hits)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string text = LayerMask.LayerToName(((Component)val).gameObject.layer);
					if (string.IsNullOrEmpty(text))
					{
						text = ((Component)val).gameObject.layer.ToString();
					}
					dictionary[text] = ((!dictionary.TryGetValue(text, out var value)) ? 1 : (value + 1));
				}
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, int> item in dictionary)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(item.Key).Append(" x").Append(item.Value);
			}
			return stringBuilder.ToString();
		}
	}
	internal static class GhostRig
	{
		internal static bool Enabled
		{
			get
			{
				if (Plugin.RigReinitPass != null)
				{
					return Plugin.RigReinitPass.Value;
				}
				return false;
			}
		}

		internal static bool Reinit(Character ch, string why, bool force = false)
		{
			//IL_066f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0671: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ch == (Object)null || (Object)(object)((Component)ch).gameObject == (Object)null)
			{
				return false;
			}
			GameObject gameObject = ((Component)ch).gameObject;
			List<string> list = new List<string>();
			RigCensus val = Census(ch);
			string text = default(string);
			bool flag = RigGate.NeedsReinit(ref val, ref text);
			if (!flag && !force)
			{
				if (GhostDiag.Enabled)
				{
					Plugin.Log.LogMessage((object)("[GHOSTFIX] '" + ch.Name + "' (" + why + ") steps=none — gate=" + text + " " + $"(hitboxes {val.CapturedHitboxes}/{val.LiveHitboxes}, ragdollHitbox {val.RagdollHitboxColliders}, " + $"attackXforms {val.AttackTransforms})."));
				}
				return false;
			}
			string text2 = ((force && !flag) ? "forced (gate=healthy)" : text);
			int hitsBefore = Len(ch.m_hitboxes);
			int rhbBefore = Count(ch.m_ragdollHitboxColliders);
			int atkBefore = AtkCount(ch);
			Vector3 spreadBefore = BoneSpread(gameObject);
			Census censusBefore = VisualPass.Measure(gameObject);
			try
			{
				int num = ActivateRigSubtree(gameObject);
				if (num > 0)
				{
					list.Add($"activate({num})");
				}
				CharacterVisuals visuals = ch.Visuals;
				if ((Object)(object)visuals == (Object)null && !ch.m_startInitDone)
				{
					if ((Object)(object)ch.CharacterVisualsPrefab == (Object)null && ch.VisualData == null && (Object)(object)((Component)ch).GetComponentInChildren<CharacterVisuals>(true) == (Object)null)
					{
						list.Add("processInit(SKIPPED:no visuals source)");
						Plugin.Log.LogWarning((object)("[GHOSTFIX] '" + ch.Name + "' (" + why + ") has no visuals prefab, no VisualData and no CharacterVisuals in its hierarchy — ProcessInit would NRE, skipping (this body should have been refused as a spawn template upstream)."));
						Log(ch, why, "no visuals holder — nothing to re-init", list);
						return false;
					}
					try
					{
						ch.ProcessInit();
						visuals = ch.Visuals;
						list.Add("processInit(startInit=" + (ch.m_startInitDone ? "T" : "F") + " holder=" + (((Object)(object)visuals == (Object)null) ? "null" : ((Object)visuals).name) + ")");
					}
					catch (Exception ex)
					{
						list.Add("processInit(threw:" + ex.GetType().Name + ")");
						Plugin.Log.LogWarning((object)("[GHOSTFIX] '" + ch.Name + "' (" + why + ") ProcessInit threw (" + ex.GetType().Name + ": " + ex.Message + ") — the body keeps its un-initialized vanilla state."));
					}
				}
				if ((Object)(object)visuals == (Object)null)
				{
					Log(ch, why, "no visuals holder — nothing to re-init", list);
					return false;
				}
				visuals.m_character = ch;
				if (!visuals.DefaultVisualsInitialized)
				{
					if (ch.VisualData == null)
					{
						list.Add("defVis(SKIPPED:VisualData null)");
					}
					else
					{
						visuals.InitDefaultVisuals();
						list.Add("defVis");
					}
				}
				visuals.InitHitboxes();
				list.Add("initHitboxes");
				ch.m_pelvis = visuals.RagdollRoot;
				ch.RagdollRoot = visuals.RagdollRoot;
				ch.m_hitboxes = visuals.Hitboxes;
				ch.m_dodgeHitboxes = visuals.DodgeHitboxes;
				if (ch.m_hitboxes != null)
				{
					Hitbox[] hitboxes = ch.m_hitboxes;
					foreach (Hitbox val2 in hitboxes)
					{
						if ((Object)(object)val2 != (Object)null)
						{
							val2.OwnerChar = ch;
						}
					}
				}
				list.Add($"relatch(hitboxes={Len(ch.m_hitboxes)})");
				string text3 = default(string);
				if (RigGate.NeedsRagdollInit(ref val, ref text3))
				{
					int num2 = ClearRagdollCaches(ch);
					if (num2 < 0)
					{
						list.Add("ragdollSkip(clear refused)");
					}
					else
					{
						ch.InitRagdoll();
						list.Add((num2 > 0) ? $"initRagdoll(recleared {num2})" : "initRagdoll");
						ch.SetRagdollActive(false);
						list.Add("setRagdollActive(false)");
					}
				}
				else
				{
					list.Add("ragdollSkip(" + text3 + ")");
				}
				Animator componentInChildren = gameObject.GetComponentInChildren<Animator>(true);
				if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.avatar != (Object)null && componentInChildren.avatar.isValid)
				{
					if (!((Behaviour)componentInChildren).enabled)
					{
						((Behaviour)componentInChildren).enabled = true;
						list.Add("animEnable");
					}
					componentInChildren.cullingMode = (AnimatorCullingMode)0;
					componentInChildren.Rebind();
					componentInChildren.Update(0f);
					componentInChildren.Update((Time.fixedDeltaTime > 0f) ? Time.fixedDeltaTime : 0.02f);
					list.Add("rebind+tick");
				}
				int num3 = 0;
				SkinnedMeshRenderer[] componentsInChildren = gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true);
				foreach (SkinnedMeshRenderer val3 in componentsInChildren)
				{
					if ((Object)(object)val3 != (Object)null && !val3.updateWhenOffscreen)
					{
						val3.updateWhenOffscreen = true;
						num3++;
					}
				}
				if (num3 > 0)
				{
					list.Add($"updOff({num3})");
				}
				if ((Object)(object)ch.LockingPoint != (Object)null && !((Component)ch.LockingPoint).gameObject.activeSelf)
				{
					((Component)ch.LockingPoint).gameObject.SetActive(true);
					list.Add("lockingPoint");
				}
				try
				{
					int num4 = HumanoidWeapon.EnsureEquipped(ch);
					if (num4 > 0)
					{
						list.Add($"reEquip({num4})");
					}
				}
				catch (Exception ex2)
				{
					list.Add("reEquip(threw:" + ex2.GetType().Name + ")");
				}
				try
				{
					int num5 = CasterSkills.EnsureLearned(ch);
					if (num5 > 0)
					{
						list.Add($"reLearn({num5})");
					}
				}
				catch (Exception ex3)
				{
					list.Add("reLearn(threw:" + ex3.GetType().Name + ")");
				}
			}
			catch (Exception ex4)
			{
				Plugin.Log.LogWarning((object)string.Format("[GHOSTFIX] '{0}' ({1}) threw after [{2}]: {3}", ch.Name, why, string.Join(", ", list.ToArray()), ex4));
			}
			LogResult(ch, why + " gate=" + text2, list, hitsBefore, rhbBefore, atkBefore, spreadBefore, censusBefore);
			return list.Count > 0;
		}

		private static RigCensus Census(Character ch)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			RigCensus val = default(RigCensus);
			try
			{
				GameObject gameObject = ((Component)ch).gameObject;
				CharacterVisuals visuals = ch.Visuals;
				val.StartInitMissing = !ch.m_startInitDone;
				val.LiveHitboxes = gameObject.GetComponentsInChildren<Hitbox>(true).Length;
				val.CapturedHitboxes = (((Object)(object)visuals != (Object)null && visuals.Hitboxes != null) ? visuals.Hitboxes.Length : 0);
				val.HasRagdollRoot = (Object)(object)ch.RagdollRoot != (Object)null;
				val.RagdollIsHitbox = ch.RagdollIsHitbox;
				val.RagdollHitboxColliders = ((ch.m_ragdollHitboxColliders != null) ? ch.m_ragdollHitboxColliders.Count : 0);
				val.AttackTransforms = AtkCount(ch);
				if (val.AttackTransforms < 0)
				{
					val.AttackTransforms = 0;
				}
				val.InactiveRigObjects = CountInactiveRigObjects(gameObject);
				val.LockingPointAsleep = (Object)(object)ch.LockingPoint != (Object)null && !((Component)ch.LockingPoint).gameObject.activeSelf;
				val.RagdollRigidbodyCache = ((ch.m_ragdollRigidbodies != null) ? ch.m_ragdollRigidbodies.Count : 0);
				CharacterJointManager[] componentsInChildren = gameObject.GetComponentsInChildren<CharacterJointManager>(true);
				foreach (CharacterJointManager val2 in componentsInChildren)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						val.RagdollJointManagers++;
						if (val2.m_hasJoint)
						{
							val.RagdollManagersWithJoint++;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[GHOSTFIX] census threw: " + ex.Message));
			}
			return val;
		}

		private static int CountInactiveRigObjects(GameObject go)
		{
			int num = 0;
			Transform[] componentsInChildren = go.GetComponentsInChildren<Transform>(true);
			foreach (Transform val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && !((Component)val).gameObject.activeSelf)
				{
					GameObject gameObject = ((Component)val).gameObject;
					if ((Object)(object)gameObject.GetComponent<Hitbox>() != (Object)null || (Object)(object)gameObject.GetComponent<Collider>() != (Object)null || (Object)(object)gameObject.GetComponent<Renderer>() != (Object)null || (Object)(object)gameObject.GetComponent<CharacterVisuals>() != (Object)null)
					{
						num++;
					}
				}
			}
			return num;
		}

		private static int ActivateRigSubtree(GameObject go)
		{
			int num = 0;
			Transform[] componentsInChildren = go.GetComponentsInChildren<Transform>(true);
			foreach (Transform val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && !((Component)val).gameObject.activeSelf)
				{
					GameObject gameObject = ((Component)val).gameObject;
					if (!((Object)(object)gameObject.GetComponent<Hitbox>() == (Object)null) || !((Object)(object)gameObject.GetComponent<Collider>() == (Object)null) || !((Object)(object)gameObject.GetComponent<Renderer>() == (Object)null) || !((Object)(object)gameObject.GetComponent<CharacterVisuals>() == (Object)null))
					{
						gameObject.SetActive(true);
						num++;
					}
				}
			}
			return num;
		}

		private static int ClearRagdollCaches(Character ch)
		{
			int num = 0;
			if (ch.m_ragdollRigidbodies != null && ch.m_ragdollRigidbodies.Count > 0)
			{
				foreach (Rigidbody ragdollRigidbody in ch.m_ragdollRigidbodies)
				{
					if ((Object)(object)ragdollRigidbody == (Object)null)
					{
						continue;
					}
					CharacterJointManager[] components = ((Component)ragdollRigidbody).GetComponents<CharacterJointManager>();
					foreach (CharacterJointManager val in components)
					{
						if ((Object)(object)val != (Object)null && val.m_hasJoint)
						{
							Plugin.Log.LogError((object)("[GHOSTFIX] '" + ch.Name + "': REFUSED to clear ragdoll caches — a CharacterJointManager still holds a joint config (BUG-RAGDOLLJOINTLOSS). Leaving the ragdoll as-is."));
							return -1;
						}
					}
				}
				num += ch.m_ragdollRigidbodies.Count;
				foreach (Rigidbody ragdollRigidbody2 in ch.m_ragdollRigidbodies)
				{
					if (!((Object)(object)ragdollRigidbody2 == (Object)null))
					{
						CharacterJointManager[] components2 = ((Component)ragdollRigidbody2).GetComponents<CharacterJointManager>();
						foreach (CharacterJointManager val2 in components2)
						{
							Object.Destroy((Object)(object)val2);
						}
					}
				}
				ch.m_ragdollRigidbodies.Clear();
			}
			if (ch.m_ragdollCharacterJointManagers != null)
			{
				ch.m_ragdollCharacterJointManagers.Clear();
			}
			if (ch.m_ragdollColliders != null)
			{
				num += ch.m_ragdollColliders.Count;
				ch.m_ragdollColliders.Clear();
			}
			if (ch.m_ragdollHitboxColliders != null)
			{
				num += ch.m_ragdollHitboxColliders.Count;
				ch.m_ragdollHitboxColliders.Clear();
			}
			return num;
		}

		private static void LogResult(Character ch, string why, List<string> steps, int hitsBefore, int rhbBefore, int atkBefore, Vector3 spreadBefore, Census censusBefore)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject gameObject = ((Component)ch).gameObject;
				Census val = VisualPass.Measure(gameObject);
				Vector3 val2 = BoneSpread(gameObject);
				int num = LayerMask.NameToLayer("Hitbox");
				int num2 = 0;
				int num3 = 0;
				if (ch.m_ragdollHitboxColliders != null)
				{
					foreach (Collider ragdollHitboxCollider in ch.m_ragdollHitboxColliders)
					{
						if (!((Object)(object)ragdollHitboxCollider == (Object)null))
						{
							num3++;
							if (((Component)ragdollHitboxCollider).gameObject.layer == num)
							{
								num2++;
							}
						}
					}
				}
				StringBuil