using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; 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("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+d10381aec7116e1ea7ae2d3ff4646fae57640f75")] [assembly: AssemblyProduct("SpawnKit.Core")] [assembly: AssemblyTitle("SpawnKit.Core")] [assembly: AssemblyVersion("1.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 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 } 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 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 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 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 class SpawnPolicy { 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 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 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; } 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 } }