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 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 MissingSkillIds(IEnumerable learnedIds, IEnumerable childSkillIds) { List list = new List(); if (childSkillIds == null) { return list; } HashSet hashSet = new HashSet(); if (learnedIds != null) { foreach (int learnedId in learnedIds) { hashSet.Add(learnedId); } } HashSet hashSet2 = new HashSet(); 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 _entries = new List(); 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 MatchWarm(int actor, string[]? warm) { List list = new List(); 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 TakeExpired(double now, double lifetimeSeconds) { List list = new List(); 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 Snapshot() { return new List(_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 _wanted = new List(); private readonly List _deadEnds = new List(); 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 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 Wanted() { List list = new List(_wanted.Count); for (int i = 0; i < _wanted.Count; i++) { list.Add(_wanted[i].Key); } return list; } public List> WantedWithClass() { List> list = new List>(_wanted.Count); for (int i = 0; i < _wanted.Count; i++) { list.Add(new KeyValuePair(_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 DeadEnds() { return new List(_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 _inFlight = new HashSet(StringComparer.Ordinal); private readonly Dictionary _tombstones = new Dictionary(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 Plan(IReadOnlyList? prioritySpecies, IReadOnlyList? peers, int perPeerCap) { return Plan(prioritySpecies, peers, perPeerCap, null); } public static List Plan(IReadOnlyList? prioritySpecies, IReadOnlyList? peers, int perPeerCap, Func? isAbandoned) { List list = new List(); if (prioritySpecies == null || prioritySpecies.Count == 0) { return list; } if (peers == null || peers.Count == 0) { return list; } List list2 = new List(); HashSet hashSet = new HashSet(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 dictionary = new Dictionary(); 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 PickEvict(IReadOnlyList lruOrder, int count, int max, ICollection? exclude = null) { List list = new List(); 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? ownerTags) { if (ownerTags == null) { return ""; } Dictionary counts = new Dictionary(StringComparer.OrdinalIgnoreCase); Dictionary seenAt = new Dictionary(StringComparer.OrdinalIgnoreCase); List list = new List(); 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 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 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 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 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 Split(string payload) { if (payload == null) { return null; } List list = new List(); 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 UnknownOptions = new List(); public static SpawnVerbArgs Parse(string[] tokens) { SpawnVerbArgs spawnVerbArgs = new SpawnVerbArgs(); List list = new List(); 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 Apply(IReadOnlyList keys, string? query, Func? resolvedNameOf) { if (keys == null) { return Array.Empty(); } string text = query?.Trim() ?? ""; if (text.Length == 0) { return keys; } List list = new List(); 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 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 list = new List(); List list2 = new List(); 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 list3 = new List(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 Intersection(IEnumerable localWarm, IReadOnlyList peers) { List list = new List(); if (localWarm == null) { return list; } HashSet hashSet = new HashSet(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 _entries = new List(); private readonly List _tombstones = new List(); 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 Tombstones() { return new List(_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 TakeDue(Func peerFacts, double now, out List gaveUp) { List list = new List(); gaveUp = new List(); if (peerFacts == null) { return list; } List list2 = new List(); 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 list3 = new List(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 list4 = new List(_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 Snapshot() { return new List(_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 warm, IEnumerable 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 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 Canonical(IEnumerable keys) { List list = new List(); 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 list2 = new List(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 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 SplitTop(string payload) { List list = new List(); 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 list = new List(); 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 list = new List(a); list.Sort(CanonicalOrder); List list2 = new List(b); list2.Sort(CanonicalOrder); for (int i = 0; i < list.Count; i++) { if (!KeyComparer.Equals(list[i], list2[i])) { return false; } } return true; } } }