using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; 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("CompanionKit.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+294094e110e9aca56665cc10cce0047db95418d1")] [assembly: AssemblyProduct("CompanionKit.Core")] [assembly: AssemblyTitle("CompanionKit.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 CompanionKit.Core { public enum AnchorGlueMode { Off, Combat, Always } public enum AnchorGlueAction { None, Write, SafeTeleport, AgentWarp } public static class AnchorGlue { public const float SafeTeleportGap = 2f; public static bool Engaged(AnchorGlueMode mode, bool hasBody, bool inCombat) { if (!hasBody) { return false; } return mode switch { AnchorGlueMode.Always => true, AnchorGlueMode.Combat => inCombat, _ => false, }; } public static AnchorGlueAction Decide(AnchorGlueMode mode, bool hasBody, bool inCombat, bool anchorAlive, bool isMasterClient, bool positionsSane, bool agentDrivesTransform, float separation) { if (!Engaged(mode, hasBody, inCombat)) { return AnchorGlueAction.None; } if (!anchorAlive || !isMasterClient || !positionsSane) { return AnchorGlueAction.None; } if (agentDrivesTransform) { return AnchorGlueAction.AgentWarp; } if (!(separation > 2f)) { return AnchorGlueAction.Write; } return AnchorGlueAction.SafeTeleport; } public static void WeldPosition(float puppetX, float puppetY, float puppetZ, float facingX, float facingZ, float offsetBehind, out float x, out float y, out float z) { y = puppetY; double num = Math.Sqrt((double)facingX * (double)facingX + (double)facingZ * (double)facingZ); if (num < 1E-06 || offsetBehind == 0f) { x = puppetX; z = puppetZ; } else { x = puppetX - (float)((double)facingX / num * (double)offsetBehind); z = puppetZ - (float)((double)facingZ / num * (double)offsetBehind); } } public static bool ShouldAssertLock(bool unifyEnabled, bool petHasTarget, bool anchorAlive, bool anchorLockIsSameTarget) { if (unifyEnabled && petHasTarget && anchorAlive) { return !anchorLockIsSameTarget; } return false; } } public enum AnchorCollisionMode { Block, PassPlayer, Phantom } public enum AnchorPhysAction { None, Stamp, Unstamp, Restamp } public static class AnchorPhysicsPolicy { public static AnchorPhysAction Decide(AnchorCollisionMode mode, bool weStampedBefore, bool ignoredNow, bool collidersReady) { if (!collidersReady) { return AnchorPhysAction.None; } switch (mode) { case AnchorCollisionMode.Block: if (!(weStampedBefore && ignoredNow)) { return AnchorPhysAction.None; } return AnchorPhysAction.Unstamp; case AnchorCollisionMode.Phantom: return AnchorPhysAction.None; default: if (ignoredNow) { return AnchorPhysAction.None; } if (!weStampedBefore) { return AnchorPhysAction.Stamp; } return AnchorPhysAction.Restamp; } } } public enum CombatTargetSource { None, Commanded, AnchorDefend, PlayerEngaged } public struct CombatTargetFacts { public bool StancePassive; public bool CommandedExists; public float CommandedDistance; public bool AnchorDefendExists; public bool AnchorDefendIsEcho; public float AnchorDefendDistance; public bool PlayerInCombat; public bool PlayerEngagedExists; public float PlayerEngagedDistance; public float AggroRange; public float CombatLeashDistance; } public struct CombatTargetDecision { public CombatTargetSource Source; public string Reason; public bool DropCommandedOrder; } public static class CombatTargetPolicy { private const string StaleReason = "beyond-combat-leash (stale, dropped)"; public static CombatTargetDecision Decide(CombatTargetFacts f) { if (f.StancePassive) { return Clear("passive"); } if (f.CommandedExists) { if (f.CommandedDistance > f.CombatLeashDistance) { return new CombatTargetDecision { Source = CombatTargetSource.None, Reason = "beyond-combat-leash (stale, dropped)", DropCommandedOrder = true }; } return new CombatTargetDecision { Source = CombatTargetSource.Commanded, Reason = "commanded" }; } if (f.AnchorDefendExists && !f.AnchorDefendIsEcho && f.AnchorDefendDistance <= f.AggroRange) { if (f.AnchorDefendDistance > f.CombatLeashDistance) { return Clear("beyond-combat-leash (stale, dropped)"); } return new CombatTargetDecision { Source = CombatTargetSource.AnchorDefend, Reason = "anchor-defend" }; } if (!f.PlayerInCombat) { return Clear("player-not-in-combat"); } if (f.PlayerEngagedExists && f.PlayerEngagedDistance <= f.AggroRange) { if (f.PlayerEngagedDistance > f.CombatLeashDistance) { return Clear("beyond-combat-leash (stale, dropped)"); } return new CombatTargetDecision { Source = CombatTargetSource.PlayerEngaged, Reason = "player-engaged" }; } return Clear("none-in-aggro-range"); } private static CombatTargetDecision Clear(string reason) { return new CombatTargetDecision { Source = CombatTargetSource.None, Reason = reason }; } } public sealed class CreatureAttributes { public const int TypeCount = 9; public float MaxHealth; public float MoveSpeed; public float Impact; public float ImpactResistance; public float Barrier; public float ProtectionAll; public float[] Resist = new float[9]; public float[] Protection = new float[9]; public float[] Damage = new float[9]; public float DamageTotal { get { float num = 0f; for (int i = 0; i < Damage.Length; i++) { num += Damage[i]; } return num; } } public bool HasDamage => DamageTotal > 0f; public CreatureAttributes Clone() { CreatureAttributes creatureAttributes = new CreatureAttributes { MaxHealth = MaxHealth, MoveSpeed = MoveSpeed, Impact = Impact, ImpactResistance = ImpactResistance, Barrier = Barrier, ProtectionAll = ProtectionAll }; Array.Copy(Resist, creatureAttributes.Resist, 9); Array.Copy(Protection, creatureAttributes.Protection, 9); Array.Copy(Damage, creatureAttributes.Damage, 9); return creatureAttributes; } public float[] DistributeTotal(float total) { return DistributeTotal(Damage, total, 9); } public static float[] DistributeTotal(float[]? profile, float total, int typeCount) { if (typeCount < 1) { typeCount = 1; } float[] array = new float[typeCount]; int num = ((profile != null) ? Math.Min(typeCount, profile.Length) : 0); float num2 = 0f; for (int i = 0; i < num; i++) { if (profile[i] > 0f) { num2 += profile[i]; } } if (num2 <= 0f) { array[0] = total; return array; } float num3 = total / num2; for (int j = 0; j < num; j++) { if (profile[j] > 0f) { array[j] = profile[j] * num3; } } return array; } public string Flatten() { StringBuilder stringBuilder = new StringBuilder(); Scalar(stringBuilder, "hp", MaxHealth); Scalar(stringBuilder, "spd", MoveSpeed); Scalar(stringBuilder, "imp", Impact); Scalar(stringBuilder, "impres", ImpactResistance); Scalar(stringBuilder, "bar", Barrier); Scalar(stringBuilder, "protall", ProtectionAll); Sparse(stringBuilder, "res", Resist); Sparse(stringBuilder, "prot", Protection); Sparse(stringBuilder, "dmg", Damage); return stringBuilder.ToString(); } public static CreatureAttributes Parse(string s) { if (string.IsNullOrWhiteSpace(s)) { return null; } CreatureAttributes creatureAttributes = new CreatureAttributes(); bool flag = false; string[] array = s.Split(new char[1] { ';' }); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0) { string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 1).Trim(); switch (text2) { case "hp": flag |= TryF(text3, out creatureAttributes.MaxHealth); break; case "spd": flag |= TryF(text3, out creatureAttributes.MoveSpeed); break; case "imp": flag |= TryF(text3, out creatureAttributes.Impact); break; case "impres": flag |= TryF(text3, out creatureAttributes.ImpactResistance); break; case "bar": flag |= TryF(text3, out creatureAttributes.Barrier); break; case "protall": flag |= TryF(text3, out creatureAttributes.ProtectionAll); break; case "res": flag |= ParseSparse(text3, creatureAttributes.Resist); break; case "prot": flag |= ParseSparse(text3, creatureAttributes.Protection); break; case "dmg": flag |= ParseSparse(text3, creatureAttributes.Damage); break; } } } if (!flag) { return null; } return creatureAttributes; } private static void Scalar(StringBuilder sb, string key, float v) { if (v != 0f) { if (sb.Length > 0) { sb.Append(';'); } sb.Append(key).Append('=').Append(v.ToString("R", CultureInfo.InvariantCulture)); } } private static void Sparse(StringBuilder sb, string key, float[] values) { bool flag = false; for (int i = 0; i < values.Length; i++) { if (values[i] == 0f) { continue; } if (!flag) { if (sb.Length > 0) { sb.Append(';'); } sb.Append(key).Append('='); flag = true; } else { sb.Append(','); } sb.Append(i.ToString(CultureInfo.InvariantCulture)).Append(':').Append(values[i].ToString("R", CultureInfo.InvariantCulture)); } } private static bool ParseSparse(string list, float[] into) { bool result = false; string[] array = list.Split(new char[1] { ',' }); foreach (string text in array) { int num = text.IndexOf(':'); if (num > 0 && int.TryParse(text.Substring(0, num), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) && result2 >= 0 && result2 < into.Length && TryF(text.Substring(num + 1), out var v)) { into[result2] = v; result = true; } } return result; } private static bool TryF(string s, out float v) { return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v); } } public enum DonorMatch { None, Substring, Exact } public struct DonorChainPick { public int UsedIndex; public bool Revisit; public DonorChainPick(int usedIndex, bool revisit) { UsedIndex = usedIndex; Revisit = revisit; } } public static class DonorChain { public static bool AcceptSubstringAt(int chainIndex, int chainCount, bool haveEarlierFallback) { if (chainIndex >= chainCount - 1) { return !haveEarlierFallback; } return false; } public static DonorChainPick Resolve(IReadOnlyList probes) { int num = probes?.Count ?? 0; int num2 = -1; for (int i = 0; i < num; i++) { switch (probes[i]) { case DonorMatch.Exact: return new DonorChainPick(i, revisit: false); case DonorMatch.Substring: if (AcceptSubstringAt(i, num, num2 >= 0)) { return new DonorChainPick(i, revisit: false); } if (num2 < 0) { num2 = i; } break; } } if (num2 < 0) { return new DonorChainPick(-1, revisit: false); } return new DonorChainPick(num2, revisit: true); } } public static class DonorTable { public static readonly string[] OversizedScenes = new string[14] { "ChersoneseNewTerrain", "Emercar", "HallowedMarshNewTerrain", "Abrassar", "AntiqueField", "Caldera", "CierzoNewTerrain", "Berg", "Monsoon", "Levant", "Harmattan", "NewSirocco", "CierzoDestroyed", "CierzoTutorial" }; public static Dictionary> Parse(string text) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (SpeciesTable.TableLine item in SpeciesTable.Lines(text)) { SplitPin(item.Key, out string key, out string _); if (key.Length == 0) { continue; } if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List()); } string[] fields = item.Fields; for (int i = 0; i < fields.Length; i++) { string text2 = fields[i].Trim(); if (text2.Length != 0 && !value.Contains(text2, StringComparer.OrdinalIgnoreCase)) { value.Add(text2); } } } return dictionary; } public static Dictionary ParsePins(string text) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (SpeciesTable.TableLine item in SpeciesTable.Lines(text)) { SplitPin(item.Key, out string key, out string pin); if (key.Length != 0 && pin.Length != 0 && !dictionary.ContainsKey(key)) { dictionary[key] = pin; } } return dictionary; } public static Dictionary MergePins(Dictionary builtIn, Dictionary overrides) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (builtIn != null) { foreach (KeyValuePair item in builtIn) { dictionary[item.Key] = item.Value; } } if (overrides != null) { foreach (KeyValuePair @override in overrides) { dictionary[@override.Key] = @override.Value; } } return dictionary; } private static void SplitPin(string rawKey, out string key, out string pin) { key = (rawKey ?? "").Trim(); pin = ""; int num = key.IndexOf('|'); if (num >= 0) { pin = key.Substring(num + 1).Trim(); key = key.Substring(0, num).Trim(); } } public static bool PinMatches(string gameObjectName, string pin) { if (string.IsNullOrEmpty(gameObjectName) || string.IsNullOrEmpty(pin)) { return false; } return string.Equals(StripInstanceSuffix(gameObjectName), StripInstanceSuffix(pin), StringComparison.OrdinalIgnoreCase); } public static string StripInstanceSuffix(string name) { string text = (name ?? "").Trim(); if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - "(Clone)".Length).Trim(); } if (text.EndsWith(")", StringComparison.Ordinal)) { int num = text.LastIndexOf('('); if (num > 0) { bool flag = num + 1 < text.Length - 1; for (int i = num + 1; i < text.Length - 1 && flag; i++) { if (!char.IsDigit(text[i])) { flag = false; } } if (flag) { text = text.Substring(0, num).Trim(); } } } return text; } public static Dictionary> Merge(Dictionary> builtIn, Dictionary> overrides) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); if (overrides != null) { foreach (KeyValuePair> @override in overrides) { dictionary[@override.Key] = new List(@override.Value); } } if (builtIn != null) { foreach (KeyValuePair> item in builtIn) { if (!dictionary.TryGetValue(item.Key, out var value)) { value = (dictionary[item.Key] = new List()); } foreach (string item2 in item.Value) { if (!value.Contains(item2, StringComparer.OrdinalIgnoreCase)) { value.Add(item2); } } } } return dictionary; } public static bool IsOversized(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { return false; } string[] oversizedScenes = OversizedScenes; for (int i = 0; i < oversizedScenes.Length; i++) { if (string.Equals(oversizedScenes[i], sceneName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public static List FilterViable(IEnumerable scenes, out List excluded) { List list = new List(); excluded = new List(); foreach (string item in scenes ?? new List()) { (IsOversized(item) ? excluded : list).Add(item); } return list; } public static List ExpeditionCandidates(IEnumerable scenes) { FilterViable(scenes, out List excluded); return excluded; } public static List KeysForScene(Dictionary> table, string sceneName) { return KeysForScene(table, sceneName, includeOversized: false); } public static List KeysForScene(Dictionary> table, string sceneName, bool includeOversized) { List list = new List(); if (table == null || string.IsNullOrEmpty(sceneName)) { return list; } foreach (KeyValuePair> item in table) { if (item.Value == null) { continue; } foreach (string item2 in item.Value) { if ((includeOversized || !IsOversized(item2)) && string.Equals(item2, sceneName, StringComparison.OrdinalIgnoreCase)) { list.Add(item.Key); break; } } } return list; } public static List OrderCandidates(IEnumerable scenes, string currentScene) { List list = new List(); List list2 = new List(); List list3 = new List(); string text = Regions.RegionOf(currentScene); foreach (string item in scenes ?? new List()) { if (string.Equals(item, currentScene, StringComparison.OrdinalIgnoreCase)) { list3.Add(item); } else if (text != null && Regions.RegionOf(item) == text) { list2.Add(item); } else { list.Add(item); } } list.AddRange(list2); list.AddRange(list3); return list; } private static bool Contains(this List list, string value, StringComparer cmp) { foreach (string item in list) { if (cmp.Equals(item, value)) { return true; } } return false; } } public static class Regions { private static readonly string[,] Keywords = new string[12, 2] { { "Chersonese", "Chersonese" }, { "Cierzo", "Chersonese" }, { "Emercar", "Emercar" }, { "Berg", "Emercar" }, { "Hallowed", "Hallowed" }, { "Monsoon", "Hallowed" }, { "Abrassar", "Abrassar" }, { "Levant", "Abrassar" }, { "Antique", "Antique" }, { "Harmattan", "Antique" }, { "Caldera", "Caldera" }, { "Sirocco", "Caldera" } }; public static string? RegionOf(string? sceneName) { if (sceneName == null || sceneName.Length == 0) { return null; } for (int i = 0; i < Keywords.GetLength(0); i++) { if (sceneName.IndexOf(Keywords[i, 0], StringComparison.OrdinalIgnoreCase) >= 0) { return Keywords[i, 1]; } } return null; } } public static class SpeciesYaw { public static List> Parse(string spec) { List> list = new List>(); foreach (SpeciesTable.TableLine item in SpeciesTable.Lines(spec, ',')) { float num = SpeciesTable.FloatOr(item.Fields, 0, float.NaN, null, item.Key); if (!float.IsNaN(num)) { list.Add(new KeyValuePair(item.Key, num)); } } return list; } public static float For(List> pairs, string speciesId) { if (pairs == null || string.IsNullOrEmpty(speciesId)) { return float.NaN; } string text = null; float result = float.NaN; foreach (KeyValuePair pair in pairs) { if (!string.IsNullOrEmpty(pair.Key)) { if (string.Equals(pair.Key, speciesId, StringComparison.OrdinalIgnoreCase)) { return pair.Value; } if (Species.NameMatches(speciesId, pair.Key) && (text == null || pair.Key.Length > text.Length)) { text = pair.Key; result = pair.Value; } } } return result; } } public static class ExpeditionConfigMigration { public enum Outcome { SkipLegacyDefault, SkipInSync, SkipCkCustomized, Migrate } public static Outcome Decide(T legacy, T legacyDefault, T ck, T ckDefault) { EqualityComparer equalityComparer = EqualityComparer.Default; if (equalityComparer.Equals(legacy, legacyDefault)) { return Outcome.SkipLegacyDefault; } if (equalityComparer.Equals(legacy, ck)) { return Outcome.SkipInSync; } if (!equalityComparer.Equals(ck, ckDefault)) { return Outcome.SkipCkCustomized; } return Outcome.Migrate; } } public static class ExpeditionLog { public static List Parse(string text) { List list = new List(); if (string.IsNullOrEmpty(text)) { return list; } string[] array = text.Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length != 0 && text2[0] != '#') { Append(list, text2); } } return list; } public static string Format(IEnumerable scenes) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("# Scenes whose body templates have been dumped by an expedition or in-place capture.\n"); stringBuilder.Append("# Read at boot for [Expedition] AutoWarmAtBoot=all; safe to edit or delete.\n"); if (scenes != null) { foreach (string scene in scenes) { if (!string.IsNullOrEmpty(scene)) { stringBuilder.Append(scene).Append('\n'); } } } return stringBuilder.ToString(); } public static bool Append(List scenes, string scene) { if (scenes == null || string.IsNullOrEmpty(scene)) { return false; } foreach (string scene2 in scenes) { if (string.Equals(scene2, scene, StringComparison.OrdinalIgnoreCase)) { return false; } } scenes.Add(scene); return true; } } public static class ExpeditionWarm { public enum Mode { Off, Needed, All } public struct SpeciesResolve { public string Species; public bool HasExpeditionScenes; public string FirstExpeditionScene; public bool HasAdditiveDonor; public bool HasCachedTemplate; } public enum ListOutcome { Warm, SkippedAdditive, SkippedCached, Unknown } public struct ListDecision { public string Species; public ListOutcome Outcome; public string Scene; } public sealed class PlanInput { public Mode Mode; public SpeciesResolve? ActivePet; public IEnumerable ManifestScenes; public Func SceneHasUncachedKey; public List AlwaysWarm; } public sealed class Plan { public List Scenes = new List(); public List ListDecisions = new List(); } public struct WarmRetryPolicy { public float MaxWaitSeconds; public float PollSeconds; } public struct WarmRetryState { public float ElapsedSeconds; public int Polls; } public enum WarmGate { Proceed, Wait, GiveUp } public static bool TryParseMode(string raw, out Mode mode) { mode = Mode.Off; string a = raw?.Trim(); if (string.Equals(a, "off", StringComparison.OrdinalIgnoreCase)) { mode = Mode.Off; return true; } if (string.Equals(a, "needed", StringComparison.OrdinalIgnoreCase)) { mode = Mode.Needed; return true; } if (string.Equals(a, "all", StringComparison.OrdinalIgnoreCase)) { mode = Mode.All; return true; } return false; } public static List ParseSpeciesList(string raw) { List list = new List(); if (string.IsNullOrEmpty(raw)) { return list; } string[] array = raw.Split(new char[1] { ',' }); foreach (string text in array) { ExpeditionLog.Append(list, text.Trim()); } return list; } public static string NeededScene(in SpeciesResolve r) { if (!r.HasExpeditionScenes || r.HasAdditiveDonor || r.HasCachedTemplate || string.IsNullOrEmpty(r.FirstExpeditionScene)) { return null; } return r.FirstExpeditionScene; } public static ListDecision DecideListed(in SpeciesResolve r) { ListDecision result = new ListDecision { Species = r.Species }; if (!r.HasExpeditionScenes && !r.HasAdditiveDonor) { result.Outcome = ListOutcome.Unknown; } else if (r.HasAdditiveDonor) { result.Outcome = ListOutcome.SkippedAdditive; } else if (r.HasCachedTemplate) { result.Outcome = ListOutcome.SkippedCached; } else if (!string.IsNullOrEmpty(r.FirstExpeditionScene)) { result.Outcome = ListOutcome.Warm; result.Scene = r.FirstExpeditionScene; } else { result.Outcome = ListOutcome.Unknown; } return result; } public static Plan Decide(PlanInput input) { Plan plan = new Plan(); if (input == null) { return plan; } switch (input.Mode) { case Mode.Needed: if (input.ActivePet.HasValue) { string text = NeededScene(input.ActivePet.Value); if (text != null) { ExpeditionLog.Append(plan.Scenes, text); } } break; case Mode.All: if (input.ManifestScenes == null || input.SceneHasUncachedKey == null) { break; } foreach (string manifestScene in input.ManifestScenes) { if (!string.IsNullOrEmpty(manifestScene) && input.SceneHasUncachedKey(manifestScene)) { ExpeditionLog.Append(plan.Scenes, manifestScene); } } break; } if (input.AlwaysWarm != null) { foreach (SpeciesResolve item2 in input.AlwaysWarm) { ListDecision item = DecideListed(item2); plan.ListDecisions.Add(item); if (item.Outcome == ListOutcome.Warm) { ExpeditionLog.Append(plan.Scenes, item.Scene); } } } return plan; } public static WarmGate WarmRetryStep(in WarmRetryState s, bool blocked, in WarmRetryPolicy p) { if (!blocked) { return WarmGate.Proceed; } if (s.ElapsedSeconds >= p.MaxWaitSeconds) { return WarmGate.GiveUp; } return WarmGate.Wait; } public static WarmRetryState WarmRetryAdvance(in WarmRetryState s, float pollSeconds) { return new WarmRetryState { ElapsedSeconds = s.ElapsedSeconds + ((pollSeconds > 0f) ? pollSeconds : 0f), Polls = s.Polls + 1 }; } public static int WarmRetryTerminates(in WarmRetryPolicy p, int guardSteps = 100000) { WarmRetryState s = default(WarmRetryState); for (int i = 0; i < guardSteps; i++) { switch (WarmRetryStep(in s, blocked: true, in p)) { case WarmGate.GiveUp: return i; default: return i; case WarmGate.Wait: break; } s = WarmRetryAdvance(in s, p.PollSeconds); } return -1; } } public static class ExpeditionResume { public enum Action { Ignore, RunPayloadThenReturn, Restore, AbortLoud } public struct State { public int Leg; public string ActiveScene; public string HomeScene; public string DonorScene; public bool ReturnRequested; public bool Restoring; public bool PayloadRan; public bool LoaderBusy; } public struct Decision { public Action Action; public string Reason; } public static Decision Decide(State s) { if (s.LoaderBusy) { return Make(Action.Ignore, "a load is still in flight (scene '" + s.ActiveScene + "') — not a landing"); } bool flag = SameScene(s.ActiveScene, s.HomeScene); bool flag2 = SameScene(s.ActiveScene, s.DonorScene); if (s.Leg == 1) { if (flag2) { return Make(Action.RunPayloadThenReturn, s.PayloadRan ? "landed in the donor scene (payload already ran) — heading home" : "landed in the donor scene — running the payload, then heading home"); } if (flag) { return Make(Action.Ignore, "still in the home scene on the outbound leg — the switch was already requested and is in motion; a resume cannot mean 'it never left' (the watchdog covers a dead outbound)"); } return Make(Action.AbortLoud, "outbound landed in unexpected scene '" + s.ActiveScene + "'"); } if (s.Leg == 2) { if (flag) { if (!s.Restoring) { return Make(Action.Restore, "landed home — restoring player position(s)"); } return Make(Action.Ignore, "home, but a restore is already running — duplicate resume"); } if (flag2) { return Make(Action.Ignore, s.ReturnRequested ? "still in the donor scene — the return was requested but its load has not landed yet" : "still in the donor scene — the return-leg dwell has not fired yet"); } return Make(Action.AbortLoud, "return leg landed in unexpected scene '" + s.ActiveScene + "'"); } return Make(Action.Ignore, $"no leg in flight (leg {s.Leg})"); } private static Decision Make(Action a, string why) { return new Decision { Action = a, Reason = why }; } private static bool SameScene(string a, string b) { if (!string.IsNullOrEmpty(a) && !string.IsNullOrEmpty(b)) { return string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } return false; } } public static class ExpeditionVerb { public enum Kind { Status, Delay, DelayInvalid, Target } public struct Command { public Kind Kind; public float DelaySeconds; public string Target; } public const float MaxDelaySeconds = 30f; public static Command Parse(string[] parts) { if (parts == null || parts.Length < 2) { return new Command { Kind = Kind.Status }; } if (parts.Length >= 3 && string.Equals(parts[1], "delay", StringComparison.OrdinalIgnoreCase)) { if (float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && result >= 0f && result <= 30f) { return new Command { Kind = Kind.Delay, DelaySeconds = result }; } return new Command { Kind = Kind.DelayInvalid }; } return new Command { Kind = Kind.Target, Target = string.Join(" ", parts, 1, parts.Length - 1) }; } } public enum GuardEndResult { Closed, AlreadyClosed, StaleToken } public sealed class GuardWindow { private int _nextToken; public double ExpirySeconds { get; } public int Owner { get; private set; } public double ExpireAt { get; private set; } public GuardWindow(double expirySeconds) { ExpirySeconds = expirySeconds; } public bool Active(double now) { if (Owner != 0) { return now < ExpireAt; } return false; } public bool Expired(double now) { if (Owner != 0) { return now >= ExpireAt; } return false; } public int Begin(double now) { Owner = ++_nextToken; ExpireAt = now + ExpirySeconds; return Owner; } public bool Refresh(int token, double now) { if (token == 0 || token != Owner) { return false; } ExpireAt = now + ExpirySeconds; return true; } public GuardEndResult End(int token) { if (Owner == 0) { return GuardEndResult.AlreadyClosed; } if (token != Owner) { return GuardEndResult.StaleToken; } Owner = 0; return GuardEndResult.Closed; } } public struct IdleFacingDecision { public bool ReAim; public float DirX; public float DirZ; public float TurnRateDegPerSec; } public sealed class IdleFacingPolicy { public const float DefaultEngageAngleDeg = 40f; public const float DefaultReleaseAngleDeg = 8f; public const float DefaultDeadZone = 1f; public const float DefaultTurnRateDegPerSec = 120f; private bool _reAiming; public bool ReAiming => _reAiming; public void Reset() { _reAiming = false; } public IdleFacingDecision Decide(float heldX, float heldZ, float toOwnerX, float toOwnerZ, float engageAngleDeg = 40f, float releaseAngleDeg = 8f, float deadZone = 1f, float turnRateDegPerSec = 120f) { double num = Math.Sqrt((double)toOwnerX * (double)toOwnerX + (double)toOwnerZ * (double)toOwnerZ); if (num < (double)deadZone || num < 1E-06) { _reAiming = false; return default(IdleFacingDecision); } float num2 = (float)((double)toOwnerX / num); float num3 = (float)((double)toOwnerZ / num); double num4 = Math.Sqrt((double)heldX * (double)heldX + (double)heldZ * (double)heldZ); if (num4 < 1E-06) { _reAiming = true; return new IdleFacingDecision { ReAim = true, DirX = num2, DirZ = num3, TurnRateDegPerSec = turnRateDegPerSec }; } float num5 = AngleBetweenDeg((float)((double)heldX / num4), (float)((double)heldZ / num4), num2, num3); if (_reAiming) { if (num5 <= releaseAngleDeg) { _reAiming = false; } } else if (num5 >= engageAngleDeg) { _reAiming = true; } return new IdleFacingDecision { ReAim = _reAiming, DirX = num2, DirZ = num3, TurnRateDegPerSec = turnRateDegPerSec }; } public static float AngleBetweenDeg(float ax, float az, float bx, float bz) { double num = (double)ax * (double)bx + (double)az * (double)bz; if (num > 1.0) { num = 1.0; } else if (num < -1.0) { num = -1.0; } return (float)(Math.Acos(num) * 180.0 / Math.PI); } } public readonly struct ItemKey { public string Key { get; } public int? ItemId { get; } public ItemKey(string key, int? itemId) { Key = key; ItemId = itemId; } public static bool TryRead(object? jsonValue, out ItemKey key) { if (jsonValue is double num) { int value = (int)num; key = new ItemKey(value.ToString(CultureInfo.InvariantCulture), value); return true; } if (jsonValue is string text) { string text2 = text.Trim(); if (text2.Length > 0) { int? itemId = null; if (int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { itemId = result; } key = new ItemKey(text2, itemId); return true; } } key = default(ItemKey); return false; } public bool Matches(int liveId, string? liveName) { if (ItemId.HasValue) { return ItemId.Value == liveId; } if (!string.IsNullOrEmpty(liveName)) { return string.Equals(Key, liveName.Trim(), StringComparison.OrdinalIgnoreCase); } return false; } } public static class Json { private const int MaxDepth = 64; public static object Parse(string text, Action onDuplicateKey = null) { if (text == null) { throw new FormatException("json: input is null."); } int pos = 0; object result = ParseValue(text, ref pos, 0, onDuplicateKey); SkipWhitespace(text, ref pos); if (pos != text.Length) { throw Error(text, pos, "trailing content after the document"); } return result; } private static object ParseValue(string s, ref int pos, int depth, Action onDup) { SkipWhitespace(s, ref pos); if (pos >= s.Length) { throw Error(s, pos, "unexpected end of input"); } char c = s[pos]; switch (c) { case '{': return ParseObject(s, ref pos, depth, onDup); case '[': return ParseArray(s, ref pos, depth, onDup); case '"': return ParseString(s, ref pos); case 't': return ParseLiteral(s, ref pos, "true", true); case 'f': return ParseLiteral(s, ref pos, "false", false); case 'n': return ParseLiteral(s, ref pos, "null", null); default: switch (c) { case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return ParseNumber(s, ref pos); default: throw Error(s, pos, $"unexpected character '{c}'"); } } } private static Dictionary ParseObject(string s, ref int pos, int depth, Action onDup) { if (depth >= 64) { throw Error(s, pos, $"maximum nesting depth ({64}) exceeded"); } pos++; Dictionary dictionary = new Dictionary(StringComparer.Ordinal); SkipWhitespace(s, ref pos); if (pos < s.Length && s[pos] == '}') { pos++; return dictionary; } while (true) { SkipWhitespace(s, ref pos); if (pos >= s.Length || s[pos] != '"') { throw Error(s, pos, "expected a quoted object key"); } string text = ParseString(s, ref pos); SkipWhitespace(s, ref pos); if (pos >= s.Length || s[pos] != ':') { throw Error(s, pos, "expected ':' after object key"); } pos++; if (onDup != null && dictionary.ContainsKey(text)) { onDup(text); } dictionary[text] = ParseValue(s, ref pos, depth + 1, onDup); SkipWhitespace(s, ref pos); if (pos >= s.Length) { throw Error(s, pos, "unterminated object"); } if (s[pos] != ',') { break; } pos++; } if (s[pos] == '}') { pos++; return dictionary; } throw Error(s, pos, "expected ',' or '}' in object"); } private static List ParseArray(string s, ref int pos, int depth, Action onDup) { if (depth >= 64) { throw Error(s, pos, $"maximum nesting depth ({64}) exceeded"); } pos++; List list = new List(); SkipWhitespace(s, ref pos); if (pos < s.Length && s[pos] == ']') { pos++; return list; } while (true) { list.Add(ParseValue(s, ref pos, depth + 1, onDup)); SkipWhitespace(s, ref pos); if (pos >= s.Length) { throw Error(s, pos, "unterminated array"); } if (s[pos] != ',') { break; } pos++; } if (s[pos] == ']') { pos++; return list; } throw Error(s, pos, "expected ',' or ']' in array"); } private static string ParseString(string s, ref int pos) { pos++; StringBuilder stringBuilder = new StringBuilder(); while (pos < s.Length) { char c = s[pos++]; switch (c) { case '"': return stringBuilder.ToString(); default: stringBuilder.Append(c); break; case '\\': { if (pos >= s.Length) { throw Error(s, pos, "unterminated escape"); } char c2 = s[pos++]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': { if (pos + 4 > s.Length) { throw Error(s, pos, "truncated \\u escape"); } if (!ushort.TryParse(s.Substring(pos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { throw Error(s, pos, "invalid \\u escape"); } stringBuilder.Append((char)result); pos += 4; break; } default: throw Error(s, pos - 1, $"invalid escape '\\{c2}'"); } break; } } } throw Error(s, pos, "unterminated string"); } private static object ParseNumber(string s, ref int pos) { int num = pos; if (pos < s.Length && s[pos] == '-') { pos++; } while (pos < s.Length && s[pos] >= '0' && s[pos] <= '9') { pos++; } if (pos < s.Length && s[pos] == '.') { pos++; while (pos < s.Length && s[pos] >= '0' && s[pos] <= '9') { pos++; } } if (pos < s.Length && (s[pos] == 'e' || s[pos] == 'E')) { pos++; if (pos < s.Length && (s[pos] == '+' || s[pos] == '-')) { pos++; } while (pos < s.Length && s[pos] >= '0' && s[pos] <= '9') { pos++; } } string text = s.Substring(num, pos - num); if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { throw Error(s, num, "invalid number '" + text + "'"); } return result; } private static object ParseLiteral(string s, ref int pos, string literal, object value) { if (pos + literal.Length > s.Length || string.CompareOrdinal(s, pos, literal, 0, literal.Length) != 0) { throw Error(s, pos, "expected '" + literal + "'"); } pos += literal.Length; return value; } private static void SkipWhitespace(string s, ref int pos) { while (pos < s.Length) { switch (s[pos]) { case '\t': case '\n': case '\r': case ' ': pos++; break; case '/': if (pos + 1 < s.Length && s[pos + 1] == '/') { while (pos < s.Length && s[pos] != '\n') { pos++; } break; } return; default: return; } } } private static FormatException Error(string s, int pos, string message) { int num = 1; int num2 = 1; for (int i = 0; i < pos && i < s.Length; i++) { if (s[i] == '\n') { num++; num2 = 1; } else { num2++; } } return new FormatException($"json: {message} (line {num}, col {num2})."); } } public struct LoafPoint { public bool Active; public float X; public float Z; } public sealed class LoafTracker { public const float DefaultMinDistance = 2f; public const float DefaultMaxDistance = 4f; public const float DefaultRepickDistance = 3f; public const float EngageSpeed = 0.3f; public const float DisengageSpeed = 1f; public const float EngageDelaySeconds = 0.75f; public const float SpeedSmoothingSeconds = 0.25f; private bool _has; private float _anchorX; private float _anchorZ; private float _pointX; private float _pointZ; private int _seq; private bool _engaged; private bool _hasPrev; private float _prevX; private float _prevZ; private float _smoothedSpeed; private float _stillFor; public bool HasPoint => _has; public bool Engaged => _engaged; public void Clear() { _has = false; _seq = 0; _engaged = false; _hasPrev = false; _smoothedSpeed = 0f; _stillFor = 0f; } public void Reject() { _has = false; } public LoafPoint Resolve(float ownerX, float ownerZ, float ownerHeadingX, float ownerHeadingZ, float dtSeconds, float minDist, float maxDist, float repickDistance) { if (maxDist <= 0f) { Clear(); return default(LoafPoint); } float num = ((minDist < 0f) ? 0f : minDist); float maxDist2 = ((maxDist < num) ? num : maxDist); float num2 = ((repickDistance <= 0f) ? 3f : repickDistance); if (dtSeconds > 0f) { if (_hasPrev) { float num3 = ownerX - _prevX; float num4 = ownerZ - _prevZ; float num5 = (float)Math.Sqrt(num3 * num3 + num4 * num4) / dtSeconds; float num6 = dtSeconds / (dtSeconds + 0.25f); _smoothedSpeed += (num5 - _smoothedSpeed) * num6; } _prevX = ownerX; _prevZ = ownerZ; _hasPrev = true; if (_smoothedSpeed <= 0.3f) { _stillFor += dtSeconds; if (!_engaged && _stillFor >= 0.75f) { _engaged = true; } } else { _stillFor = 0f; if (_engaged && _smoothedSpeed >= 1f) { _engaged = false; _has = false; } } } if (!_engaged) { return default(LoafPoint); } bool flag = !_has; if (_has) { float num7 = ownerX - _anchorX; float num8 = ownerZ - _anchorZ; if (num7 * num7 + num8 * num8 > num2 * num2) { flag = true; } } if (flag) { Pick(ownerX, ownerZ, ownerHeadingX, ownerHeadingZ, _seq, num, maxDist2, out _pointX, out _pointZ); _seq++; _anchorX = ownerX; _anchorZ = ownerZ; _has = true; } return new LoafPoint { Active = true, X = _pointX, Z = _pointZ }; } public static void Pick(float ownerX, float ownerZ, float ownerHeadingX, float ownerHeadingZ, int seq, float minDist, float maxDist, out float x, out float z) { uint num = Hash((uint)seq); float num2 = (float)(num & 0xFFFF) / 65535f; float num3 = (float)((num >> 16) & 0xFFFF) / 65535f; float num4 = minDist + (maxDist - minDist) * num3; double num5 = Math.Sqrt((double)ownerHeadingX * (double)ownerHeadingX + (double)ownerHeadingZ * (double)ownerHeadingZ); double num9; double num10; if (num5 > 1E-06) { double num6 = Math.Atan2((double)(0f - ownerHeadingZ) / num5, (double)(0f - ownerHeadingX) / num5); double num7 = ((double)num2 - 0.5) * Math.PI; double num8 = num6 + num7; num9 = Math.Cos(num8); num10 = Math.Sin(num8); } else { double num11 = (double)num2 * 2.0 * Math.PI; num9 = Math.Cos(num11); num10 = Math.Sin(num11); } x = ownerX + (float)(num9 * (double)num4); z = ownerZ + (float)(num10 * (double)num4); } private static uint Hash(uint v) { v ^= 0xA3C59AC3u; v *= 2654435769u; v ^= v >> 16; v *= 2654435769u; v ^= v >> 16; v *= 2654435769u; return v; } } public static class Scenes { public static bool IsGameplay(string sceneName) { if (!string.IsNullOrEmpty(sceneName) && !sceneName.StartsWith("MainMenu", StringComparison.OrdinalIgnoreCase) && sceneName.IndexOf("Transition", StringComparison.OrdinalIgnoreCase) < 0) { return sceneName.IndexOf("LowMemory", StringComparison.OrdinalIgnoreCase) < 0; } return false; } public static bool IsMainMenu(string sceneName) { if (!string.IsNullOrEmpty(sceneName)) { return sceneName.StartsWith("MainMenu", StringComparison.OrdinalIgnoreCase); } return false; } } public static class SaveNaming { public static string SanitizeKey(string uid) { string text = new string((uid ?? "").Where(char.IsLetterOrDigit).ToArray()); if (text.Length != 0) { return text; } return "global"; } public static string FileName(string prefix, string uid) { return prefix + SanitizeKey(uid) + ".txt"; } } public static class Species { public static bool NameMatches(string creatureName, string filter) { if (string.IsNullOrEmpty(filter)) { return true; } if (string.IsNullOrEmpty(creatureName)) { return false; } return CultureInfo.InvariantCulture.CompareInfo.IndexOf(creatureName, filter, CompareOptions.IgnoreCase) >= 0; } public static bool NameEquals(string creatureName, string speciesId) { if (!string.IsNullOrEmpty(creatureName) && !string.IsNullOrEmpty(speciesId)) { return string.Equals(creatureName, speciesId, StringComparison.OrdinalIgnoreCase); } return false; } public static int MatchRank(string creatureName, string filter) { if (!NameMatches(creatureName, filter)) { return 0; } if (!NameEquals(creatureName, filter)) { return 1; } return 2; } } public static class RangedSpecial { public readonly struct ShooterCandidate { public readonly string ProjectileName; public readonly string Path; public ShooterCandidate(string projectileName, string path) { ProjectileName = projectileName; Path = path; } } public enum ImpactOutcome { Resolve, AlreadyResolved, Miss } public enum VolleyState { Pending, Hit, Miss } public const float DefaultTimeoutSeconds = 6f; public static int ChooseShooter(IReadOnlyList candidates, string filter) { if (candidates == null || candidates.Count == 0) { return -1; } if (!string.IsNullOrEmpty(filter)) { for (int i = 0; i < candidates.Count; i++) { if (!string.IsNullOrEmpty(candidates[i].ProjectileName) && (Contains(candidates[i].ProjectileName, filter) || Contains(candidates[i].Path, filter))) { return i; } } } for (int j = 0; j < candidates.Count; j++) { if (!string.IsNullOrEmpty(candidates[j].ProjectileName)) { return j; } } return -1; } public static ImpactOutcome ResolveImpact(bool alreadyResolved, bool hitIsCharacter, bool hitIsValidEnemy) { if (alreadyResolved) { return ImpactOutcome.AlreadyResolved; } if (hitIsCharacter && hitIsValidEnemy) { return ImpactOutcome.Resolve; } return ImpactOutcome.Miss; } public static VolleyState AssessVolley(bool resolved, int explodedCount, int firedCount) { if (resolved) { return VolleyState.Hit; } if (firedCount > 0 && explodedCount >= firedCount) { return VolleyState.Miss; } return VolleyState.Pending; } public static float TimeoutSeconds(float cfgSeconds, float projectileLifespan) { bool flag = cfgSeconds > 0f; bool flag2 = projectileLifespan > 0f; if (flag && flag2) { return Math.Min(cfgSeconds, projectileLifespan); } if (flag) { return cfgSeconds; } if (flag2) { return projectileLifespan; } return 6f; } public static bool WasShot(float shootTimeBefore, float shootTimeAfter) { return shootTimeAfter > shootTimeBefore; } private static bool Contains(string haystack, string needle) { if (haystack != null) { return haystack.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } public static class RigSurgery { public static HashSet RootChain(T root, Func parent) where T : class { HashSet hashSet = new HashSet(); for (T val = root; val != null; val = parent(val)) { hashSet.Add(val); } return hashSet; } public static T FindGraftRoot(T bone, HashSet donorRootChain, Func parent) where T : class { if (bone == null || donorRootChain == null || donorRootChain.Contains(bone)) { return null; } T result = bone; T val = parent(bone); while (val != null && !donorRootChain.Contains(val)) { result = val; val = parent(val); } return result; } public static List DedupeGraftRoots(IEnumerable roots, Func parent) where T : class { List list = new List(); HashSet hashSet = new HashSet(); foreach (T item in roots ?? Array.Empty()) { if (item != null && hashSet.Add(item)) { list.Add(item); } } List list2 = new List(); foreach (T item2 in list) { bool flag = false; T val = parent(item2); while (val != null && !flag) { if (hashSet.Contains(val)) { flag = true; } val = parent(val); } if (!flag) { list2.Add(item2); } } return list2; } public static string Summarize(int smrs, int bones, int internalBones, int external, int nullBones, int rootBonesExternal, int jointsExternal, int cloth) { return $"{smrs} SMR(s), {bones} bone slot(s) — internal={internalBones} external={external} " + $"null={nullBones} rootBoneExt={rootBonesExternal} jointsExt={jointsExternal} cloth={cloth}"; } public static bool IsHealthy(int external, int nullBones, int rootBonesExternal, int jointsExternal) { if (external == 0 && nullBones == 0 && rootBonesExternal == 0) { return jointsExternal == 0; } return false; } } public static class SpeciesResolve { public static int MatchIndex(string? speciesId, IReadOnlyList? keys) { if (string.IsNullOrEmpty(speciesId) || keys == null) { return -1; } for (int i = 0; i < keys.Count; i++) { string text = keys[i]; if (!string.IsNullOrEmpty(text) && Species.NameMatches(speciesId, text)) { return i; } } return -1; } } public static class SpeciesTable { public readonly struct TableLine { public readonly string Key; public readonly string[] Fields; public readonly string Raw; public TableLine(string key, string[] fields, string raw) { Key = key; Fields = fields; Raw = raw; } } public static IEnumerable Lines(string text, char lineSeparator = '\n') { string[] array = (text ?? "").Split(new char[1] { lineSeparator }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0 || text2[0] == '#') { continue; } int num = text2.IndexOf('='); if (num > 0) { string text3 = text2.Substring(0, num).Trim(); if (text3.Length != 0) { yield return new TableLine(text3, text2.Substring(num + 1).Split(new char[1] { ',' }), text2); } } } } public static float FloatOr(string[] fields, int index, float fallback, Action warn, string key) { if (index >= fields.Length) { return fallback; } string text = fields[index].Trim(); if (text.Length == 0) { return fallback; } if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } warn?.Invoke($"'{key}': unparsable number '{text}' — using {fallback}."); return fallback; } public static double DoubleOr(string[] fields, int index, double fallback, Action warn, string key) { if (index >= fields.Length) { return fallback; } string text = fields[index].Trim(); if (text.Length == 0) { return fallback; } if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } warn?.Invoke($"'{key}': unparsable number '{text}' — using {fallback}."); return fallback; } public static int IntOr(string[] fields, int index, int fallback, Action warn, string key) { if (index >= fields.Length) { return fallback; } string text = fields[index].Trim(); if (text.Length == 0) { return fallback; } if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } warn?.Invoke($"'{key}': unparsable integer '{text}' — using {fallback}."); return fallback; } public static Dictionary Merge(Dictionary builtIn, Dictionary overrides) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (builtIn != null) { foreach (KeyValuePair item in builtIn) { dictionary[item.Key] = item.Value; } } if (overrides != null) { foreach (KeyValuePair @override in overrides) { dictionary[@override.Key] = @override.Value; } } return dictionary; } public static bool TryResolve(Dictionary table, string speciesId, out T value, string defaultKey = null) { string key; return TryResolveKey(table, speciesId, out key, out value, defaultKey); } public static bool TryResolveKey(Dictionary table, string speciesId, out string key, out T value, string defaultKey = null) { if (table != null && !string.IsNullOrEmpty(speciesId)) { if (table.TryGetValue(speciesId, out value)) { key = speciesId; return true; } string text = null; foreach (KeyValuePair item in table) { if (!string.IsNullOrEmpty(item.Key) && (defaultKey == null || !string.Equals(item.Key, defaultKey, StringComparison.OrdinalIgnoreCase)) && Species.NameMatches(speciesId, item.Key) && (text == null || item.Key.Length > text.Length)) { text = item.Key; } } if (text != null) { key = text; value = table[text]; return true; } } if (table != null && defaultKey != null && table.TryGetValue(defaultKey, out value)) { key = defaultKey; return true; } key = null; value = default(T); return false; } } public static class StatAdoption { public static bool ShouldAdopt(string savedSpeciesId, string bodySpeciesId, bool hasCapture, bool alreadyHasAttributes) { if (!hasCapture || alreadyHasAttributes) { return false; } return IsSameSpecies(savedSpeciesId, bodySpeciesId); } public static bool IsSameSpecies(string a, string b) { if (!string.IsNullOrWhiteSpace(a) && !string.IsNullOrWhiteSpace(b)) { return string.Equals(a.Trim(), b.Trim(), StringComparison.OrdinalIgnoreCase); } return false; } } public static class TargetPick { public struct Candidate { public float X; public float Z; } public static int PickFrontTarget(float px, float pz, float fwdX, float fwdZ, float maxRange, float coneDegrees, IList candidates) { if (candidates == null || candidates.Count == 0 || maxRange <= 0f) { return -1; } double num = Math.Sqrt((double)fwdX * (double)fwdX + (double)fwdZ * (double)fwdZ); if (num < 1E-06) { return -1; } double num2 = (double)fwdX / num; double num3 = (double)fwdZ / num; double num4 = Math.Cos(Math.Max(0.0, Math.Min(360.0, coneDegrees)) * 0.5 * Math.PI / 180.0); int result = -1; double num5 = maxRange; for (int i = 0; i < candidates.Count; i++) { double num6 = candidates[i].X - px; double num7 = candidates[i].Z - pz; double num8 = Math.Sqrt(num6 * num6 + num7 * num7); if (!(num8 > num5) && (!(num8 > 1E-06) || !(num6 / num8 * num2 + num7 / num8 * num3 < num4))) { num5 = num8; result = i; } } return result; } } public enum UnloadAssetsMode { Off, EveryN, Always } public static class TerrainMaintenance { public static bool ShouldUnload(UnloadAssetsMode mode, int everyN, int harvestCount) { switch (mode) { case UnloadAssetsMode.Off: return false; case UnloadAssetsMode.Always: return true; case UnloadAssetsMode.EveryN: if (harvestCount <= 0) { return false; } if (everyN <= 1) { return true; } return harvestCount % everyN == 0; default: return true; } } } public static class WorldPosition { public const float VoidFloorY = -3000f; public const float MaxSqrMagnitude = 100000000f; public static bool IsSane(float x, float y, float z) { if (y > -3000f) { return x * x + y * y + z * z < 100000000f; } return false; } } }