Decompiled source of CompanionKit v0.1.0

CompanionKit/CompanionKit.Core.dll

Decompiled 2 days ago
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<DonorMatch> 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<string, List<string>> Parse(string text)
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(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>());
				}
				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<string, string> ParsePins(string text)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(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<string, string> MergePins(Dictionary<string, string> builtIn, Dictionary<string, string> overrides)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			if (builtIn != null)
			{
				foreach (KeyValuePair<string, string> item in builtIn)
				{
					dictionary[item.Key] = item.Value;
				}
			}
			if (overrides != null)
			{
				foreach (KeyValuePair<string, string> @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<string, List<string>> Merge(Dictionary<string, List<string>> builtIn, Dictionary<string, List<string>> overrides)
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
			if (overrides != null)
			{
				foreach (KeyValuePair<string, List<string>> @override in overrides)
				{
					dictionary[@override.Key] = new List<string>(@override.Value);
				}
			}
			if (builtIn != null)
			{
				foreach (KeyValuePair<string, List<string>> item in builtIn)
				{
					if (!dictionary.TryGetValue(item.Key, out var value))
					{
						value = (dictionary[item.Key] = new List<string>());
					}
					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<string> FilterViable(IEnumerable<string> scenes, out List<string> excluded)
		{
			List<string> list = new List<string>();
			excluded = new List<string>();
			foreach (string item in scenes ?? new List<string>())
			{
				(IsOversized(item) ? excluded : list).Add(item);
			}
			return list;
		}

		public static List<string> ExpeditionCandidates(IEnumerable<string> scenes)
		{
			FilterViable(scenes, out List<string> excluded);
			return excluded;
		}

		public static List<string> KeysForScene(Dictionary<string, List<string>> table, string sceneName)
		{
			return KeysForScene(table, sceneName, includeOversized: false);
		}

		public static List<string> KeysForScene(Dictionary<string, List<string>> table, string sceneName, bool includeOversized)
		{
			List<string> list = new List<string>();
			if (table == null || string.IsNullOrEmpty(sceneName))
			{
				return list;
			}
			foreach (KeyValuePair<string, List<string>> 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<string> OrderCandidates(IEnumerable<string> scenes, string currentScene)
		{
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			string text = Regions.RegionOf(currentScene);
			foreach (string item in scenes ?? new List<string>())
			{
				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<string> 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<KeyValuePair<string, float>> Parse(string spec)
		{
			List<KeyValuePair<string, float>> list = new List<KeyValuePair<string, float>>();
			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<string, float>(item.Key, num));
				}
			}
			return list;
		}

		public static float For(List<KeyValuePair<string, float>> pairs, string speciesId)
		{
			if (pairs == null || string.IsNullOrEmpty(speciesId))
			{
				return float.NaN;
			}
			string text = null;
			float result = float.NaN;
			foreach (KeyValuePair<string, float> 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>(T legacy, T legacyDefault, T ck, T ckDefault)
		{
			EqualityComparer<T> equalityComparer = EqualityComparer<T>.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<string> Parse(string text)
		{
			List<string> list = new List<string>();
			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<string> 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<string> 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<string> ManifestScenes;

			public Func<string, bool> SceneHasUncachedKey;

			public List<SpeciesResolve> AlwaysWarm;
		}

		public sealed class Plan
		{
			public List<string> Scenes = new List<string>();

			public List<ListDecision> ListDecisions = new List<ListDecision>();
		}

		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<string> ParseSpeciesList(string raw)
		{
			List<string> list = new List<string>();
			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<string> 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<string> 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<string, object> ParseObject(string s, ref int pos, int depth, Action<string> onDup)
		{
			if (depth >= 64)
			{
				throw Error(s, pos, $"maximum nesting depth ({64}) exceeded");
			}
			pos++;
			Dictionary<string, object> dictionary = new Dictionary<string, object>(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<object> ParseArray(string s, ref int pos, int depth, Action<string> onDup)
		{
			if (depth >= 64)
			{
				throw Error(s, pos, $"maximum nesting depth ({64}) exceeded");
			}
			pos++;
			List<object> list = new List<object>();
			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<ShooterCandidate> 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<T> RootChain<T>(T root, Func<T, T> parent) where T : class
		{
			HashSet<T> hashSet = new HashSet<T>();
			for (T val = root; val != null; val = parent(val))
			{
				hashSet.Add(val);
			}
			return hashSet;
		}

		public static T FindGraftRoot<T>(T bone, HashSet<T> donorRootChain, Func<T, T> 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<T> DedupeGraftRoots<T>(IEnumerable<T> roots, Func<T, T> parent) where T : class
		{
			List<T> list = new List<T>();
			HashSet<T> hashSet = new HashSet<T>();
			foreach (T item in roots ?? Array.Empty<T>())
			{
				if (item != null && hashSet.Add(item))
				{
					list.Add(item);
				}
			}
			List<T> list2 = new List<T>();
			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<string>? 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<TableLine> 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<string> 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<string> 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<string> 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<string, T> Merge<T>(Dictionary<string, T> builtIn, Dictionary<string, T> overrides)
		{
			Dictionary<string, T> dictionary = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
			if (builtIn != null)
			{
				foreach (KeyValuePair<string, T> item in builtIn)
				{
					dictionary[item.Key] = item.Value;
				}
			}
			if (overrides != null)
			{
				foreach (KeyValuePair<string, T> @override in overrides)
				{
					dictionary[@override.Key] = @override.Value;
				}
			}
			return dictionary;
		}

		public static bool TryResolve<T>(Dictionary<string, T> table, string speciesId, out T value, string defaultKey = null)
		{
			string key;
			return TryResolveKey(table, speciesId, out key, out value, defaultKey);
		}

		public static bool TryResolveKey<T>(Dictionary<string, T> 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<string, T> 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<Candidate> 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;
		}
	}
}

CompanionKit/CompanionKit.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CompanionKit.Core;
using ForgeKit;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("CompanionKit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+294094e110e9aca56665cc10cce0047db95418d1")]
[assembly: AssemblyProduct("CompanionKit")]
[assembly: AssemblyTitle("CompanionKit")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace CompanionKit
{
	[HarmonyPatch(typeof(AmbienceSound), "OnUpdate")]
	public static class AmbienceSoundGuard
	{
		[HarmonyFinalizer]
		private static Exception Finalizer(Exception __exception, AmbienceSound __instance)
		{
			return AmbienceGuard.Handle(__exception, __instance);
		}
	}
	[HarmonyPatch(typeof(AmbienceZone), "OnUpdate")]
	public static class AmbienceZoneGuard
	{
		[HarmonyFinalizer]
		private static Exception Finalizer(Exception __exception, AmbienceZone __instance)
		{
			return AmbienceGuard.Handle(__exception, (AmbienceSound)(object)__instance);
		}
	}
	[HarmonyPatch(typeof(GlobalAudioManager), "ClearPlayers")]
	public static class ClearPlayersGuard
	{
		[HarmonyFinalizer]
		private static Exception Finalizer(Exception __exception)
		{
			if (__exception == null)
			{
				return null;
			}
			CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] GlobalAudioManager.ClearPlayers threw (" + __exception.GetType().Name + ": " + __exception.Message + ") — swallowing so it can't abort a scene load/area switch."));
			return null;
		}
	}
	[HarmonyPatch(typeof(GlobalAudioManager), "GetUsableSoundInstance")]
	public static class GetUsableSoundInstanceGuard
	{
		private static readonly HashSet<Sounds> _detailed = new HashSet<Sounds>();

		private static int _count;

		private static float _heartbeatAt;

		[HarmonyFinalizer]
		private static Exception Finalizer(Exception __exception, GlobalAudioManager __instance, Sounds _sound, ref SplitableSoundSource __result)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			if (__exception == null)
			{
				return null;
			}
			__result = null;
			_count++;
			if (_detailed.Add(_sound))
			{
				CompanionRuntime.Log.LogWarning((object)$"[AMBIENCE-RECON] GetUsableSoundInstance threw ({__exception.GetType().Name}: {__exception.Message}) for sound '{_sound}'. {DescribeSoundState(__instance, _sound)}\n{__exception.StackTrace}");
				_heartbeatAt = Time.unscaledTime;
			}
			else if (Time.unscaledTime - _heartbeatAt > 10f)
			{
				_heartbeatAt = Time.unscaledTime;
				CompanionRuntime.Log.LogWarning((object)$"[AMBIENCE-GUARD] GetUsableSoundInstance still failing — {_count} swallowed so far; latest sound '{_sound}'.");
			}
			return null;
		}

		private static string DescribeSoundState(GlobalAudioManager mgr, Sounds sound)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)mgr == (Object)null)
			{
				return "(no manager instance)";
			}
			try
			{
				string arg = ((!mgr.m_soundPrefabs.ContainsKey(sound)) ? "absent" : (((Object)(object)mgr.m_soundPrefabs[sound] == (Object)null) ? "DESTROYED" : "ok"));
				string arg2 = "absent";
				if (mgr.m_soundInstances.ContainsKey(sound))
				{
					List<SplitableSoundSource> list = mgr.m_soundInstances[sound];
					int num = 0;
					foreach (SplitableSoundSource item in list)
					{
						if (item != null && (Object)(object)item == (Object)null)
						{
							num++;
						}
					}
					arg2 = $"{list.Count} (dead {num})";
				}
				return $"state: prefab={arg} busyKey={mgr.BusySounds.ContainsKey(sound)} instances={arg2} " + $"mixerKey={mgr.MixerGroupPerSound != null && mgr.MixerGroupPerSound.ContainsKey(sound)} poolAlive={(Object)(object)mgr.m_soundPool != (Object)null}";
			}
			catch (Exception ex)
			{
				return "(state probe failed: " + ex.Message + ")";
			}
		}
	}
	public static class AmbienceGuard
	{
		public static Exception Handle(Exception exception, AmbienceSound instance)
		{
			if (exception == null)
			{
				return null;
			}
			CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] '" + PathOf((Component)(object)instance) + "' (" + ((object)instance)?.GetType().Name + ") OnUpdate threw (" + exception.GetType().Name + ": " + exception.Message + ") — disabling it so it stops retrying every frame."));
			try
			{
				if ((Object)(object)instance != (Object)null)
				{
					((Behaviour)instance).enabled = false;
				}
			}
			catch (Exception ex)
			{
				CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] disable failed: " + ex.Message));
			}
			try
			{
				if ((Object)(object)instance != (Object)null && DonorHarvest.RemoveFromAudioRegistries(instance))
				{
					CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] '" + PathOf((Component)(object)instance) + "' also unregistered from GlobalAudioManager — manager-driven .Play() calls would otherwise keep retrying it after the disable."));
				}
			}
			catch (Exception ex2)
			{
				CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] unregister failed: " + ex2.Message));
			}
			return null;
		}

		public static string PathOf(Component c)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)c == (Object)null)
				{
					return "<destroyed or null>";
				}
				string text = ((Object)c.gameObject).name;
				Transform parent = c.transform.parent;
				while ((Object)(object)parent != (Object)null)
				{
					text = ((Object)parent).name + "/" + text;
					parent = parent.parent;
				}
				Scene scene = c.gameObject.scene;
				return ((Scene)(ref scene)).name + ":" + text;
			}
			catch
			{
				return "<unreadable>";
			}
		}
	}
	public sealed class AnchorDressing
	{
		private readonly Func<Character> _current;

		private readonly Func<ICompanionSettings> _cfg;

		private GameObject _voiceSource;

		private CharacterSoundManager _bodyCsm;

		private static readonly WaitForSeconds _hideBurst0 = new WaitForSeconds(0.2f);

		private static readonly WaitForSeconds _hideBurst1 = new WaitForSeconds(0.4f);

		private static readonly WaitForSeconds _hideBurst2 = new WaitForSeconds(0.9f);

		private static readonly WaitForSeconds _hideBurst3 = new WaitForSeconds(1.5f);

		private static readonly WaitForSeconds _hideBurst4 = new WaitForSeconds(2f);

		private static readonly WaitForSeconds _hideSlowWait = new WaitForSeconds(0.5f);

		private static readonly WaitForSeconds _muteWait = new WaitForSeconds(1f);

		private readonly List<Renderer> _rendererBuf = new List<Renderer>();

		private readonly List<AudioSource> _audioBuf = new List<AudioSource>();

		private readonly List<ParticleSystem> _particleBuf = new List<ParticleSystem>();

		private float _hideLogAt;

		private int _hideLogCount;

		private int _stopLogCount;

		private Character Current => _current();

		private ICompanionSettings Cfg => _cfg();

		private bool HasLiveAnchor
		{
			get
			{
				Character val = _current();
				if ((Object)(object)val != (Object)null)
				{
					return val.Alive;
				}
				return false;
			}
		}

		private string TagAnchor => CompanionRuntime.Tag("ANCHOR", Cfg);

		public AnchorDressing(Func<Character> current, Func<ICompanionSettings> cfg)
		{
			_current = current;
			_cfg = cfg;
		}

		public void ResetVoiceSource()
		{
			_voiceSource = null;
		}

		public void ResetBodySound()
		{
			_bodyCsm = null;
		}

		public IEnumerator NeuterWeaponWhenReady(Character anchor)
		{
			for (int i = 0; i < 20; i++)
			{
				yield return (object)new WaitForSeconds(0.5f);
				if ((Object)(object)anchor == (Object)null || (Object)(object)Current != (Object)(object)anchor)
				{
					yield break;
				}
				Weapon currentWeapon = anchor.CurrentWeapon;
				if ((Object)(object)currentWeapon == (Object)null)
				{
					continue;
				}
				if (Cfg.AnchorInvisible)
				{
					Renderer[] componentsInChildren = ((Component)currentWeapon).GetComponentsInChildren<Renderer>(true);
					foreach (Renderer val in componentsInChildren)
					{
						val.enabled = false;
					}
				}
				try
				{
					DamageList damage = currentWeapon.Damage;
					if (damage != null)
					{
						damage.Clear();
					}
					object? obj = typeof(Weapon).GetField("m_baseDamage", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(currentWeapon);
					DamageList val2 = (DamageList)((obj is DamageList) ? obj : null);
					if (val2 != null)
					{
						val2.Clear();
					}
					CompanionRuntime.Log.LogMessage((object)(TagAnchor + " weapon damage zeroed (defense-only anchor; CompanionCombat owns damage)."));
					yield break;
				}
				catch (Exception ex)
				{
					CompanionRuntime.Log.LogWarning((object)(TagAnchor + " weapon neuter failed: " + ex.Message));
					yield break;
				}
			}
			CompanionRuntime.Log.LogWarning((object)(TagAnchor + " no weapon appeared to neuter (anchor may deal its own damage)."));
		}

		public IEnumerator HideSweep(Character anchor)
		{
			WaitForSeconds[] array = (WaitForSeconds[])(object)new WaitForSeconds[5] { _hideBurst0, _hideBurst1, _hideBurst2, _hideBurst3, _hideBurst4 };
			WaitForSeconds[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				yield return array2[i];
				if ((Object)(object)anchor == (Object)null || (Object)(object)Current != (Object)(object)anchor)
				{
					yield break;
				}
				HidePass(anchor);
			}
			while ((Object)(object)anchor != (Object)null && (Object)(object)Current == (Object)(object)anchor)
			{
				yield return _hideSlowWait;
				if ((Object)(object)anchor == (Object)null || (Object)(object)Current != (Object)(object)anchor)
				{
					break;
				}
				HidePass(anchor);
			}
		}

		private void HidePass(Character anchor)
		{
			int num = 0;
			((Component)anchor).GetComponentsInChildren<Renderer>(true, _rendererBuf);
			foreach (Renderer item in _rendererBuf)
			{
				if (item.enabled)
				{
					item.enabled = false;
					num++;
				}
			}
			int num2 = 0;
			((Component)anchor).GetComponentsInChildren<ParticleSystem>(true, _particleBuf);
			foreach (ParticleSystem item2 in _particleBuf)
			{
				if (item2.isPlaying || item2.particleCount > 0)
				{
					item2.Stop(true, (ParticleSystemStopBehavior)0);
					num2++;
				}
			}
			if (num != 0 || num2 != 0)
			{
				_hideLogCount += num;
				_stopLogCount += num2;
				if (Time.time - _hideLogAt > 10f)
				{
					CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} hid {_hideLogCount} renderer(s), stopped {_stopLogCount} particle system(s).");
					_hideLogAt = Time.time;
					_hideLogCount = 0;
					_stopLogCount = 0;
				}
			}
		}

		public void ApplyVoice(CompanionBody body)
		{
			if (Cfg.SpeciesVoice && HasLiveAnchor && !((Object)(object)body == (Object)null) && !((Object)(object)((Component)body).gameObject == (Object)(object)_voiceSource))
			{
				_voiceSource = ((Component)body).gameObject;
				CharacterSoundManager val = ((Component)body).GetComponent<CharacterSoundManager>();
				if ((Object)(object)val == (Object)null)
				{
					val = ((Component)body).GetComponentInChildren<CharacterSoundManager>(true);
				}
				_bodyCsm = val;
				CharacterSoundsPresets val2 = (((Object)(object)val != (Object)null) ? val.m_characterSoundsPresets : null);
				CharacterSoundManager component = ((Component)Current).GetComponent<CharacterSoundManager>();
				int num = ((Component)Current).GetComponentsInChildren<CharacterSoundManager>(true).Length;
				if ((Object)(object)component != (Object)null && (Object)(object)val2 != (Object)null)
				{
					component.m_characterSoundsPresets = val2;
				}
				AnchorVoice anchorVoice = ((Component)Current).GetComponent<AnchorVoice>();
				if ((Object)(object)anchorVoice == (Object)null)
				{
					anchorVoice = ((Component)Current).gameObject.AddComponent<AnchorVoice>();
				}
				anchorVoice.BodySound = val;
				CompanionRuntime.Log.LogMessage((object)(TagAnchor + " voice wired for '" + body.SpeciesId + "': bodyPreset=" + (((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "NONE") + " " + $"anchorRootCSM={(Object)(object)component != (Object)null} anchorChildCSMs={num} — hurt via CharHurt receiver, death via HandleDeath" + (((Object)(object)val2 == (Object)null) ? " (NO body preset — hurt/death vocals unavailable for this species)" : "") + "."));
			}
		}

		public void PlayDeathVocal(Character corpse)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (!Cfg.SpeciesVoice || (Object)(object)_bodyCsm == (Object)null || (Object)(object)corpse == (Object)null)
			{
				return;
			}
			try
			{
				Global.AudioManager.PlaySoundAtPosition(_bodyCsm.GetDeathSound(), ((Component)corpse).transform, 0f, 1f, 1f, 1f, 1f);
				CompanionRuntime.Log.LogMessage((object)(TagAnchor + " species death vocal played."));
			}
			catch (Exception ex)
			{
				CompanionRuntime.Log.LogWarning((object)(TagAnchor + " death vocal failed: " + ex.Message));
			}
		}

		public IEnumerator MuteSweep(Character anchor)
		{
			int muted = 0;
			while ((Object)(object)anchor != (Object)null && (Object)(object)Current == (Object)(object)anchor)
			{
				((Component)anchor).GetComponentsInChildren<AudioSource>(true, _audioBuf);
				foreach (AudioSource item in _audioBuf)
				{
					if (!item.mute)
					{
						item.mute = true;
						muted++;
					}
				}
				if (muted > 0)
				{
					CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} muted {muted} local audio source(s) (weapon whoosh / movement).");
					muted = 0;
				}
				yield return _muteWait;
			}
		}

		public void ApplyHealthBarConfig(Character anchor)
		{
			if (!Cfg.AnchorShowHealthBar)
			{
				CharacterBarManager component = ((Component)anchor).GetComponent<CharacterBarManager>();
				if (!((Object)(object)component == (Object)null))
				{
					component.BarDisplayPrefab = null;
					((Behaviour)component).enabled = false;
				}
			}
		}
	}
	public class AnchorVoice : MonoBehaviour
	{
		public CharacterSoundManager BodySound;

		private Character _anchor;

		private float _lastCryAt;

		private float _lastHealth = float.NaN;

		private const float MinHurtDamage = 9f;

		private void CharHurt(Character _dealer)
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)BodySound == (Object)null)
			{
				return;
			}
			if ((Object)(object)_anchor == (Object)null)
			{
				_anchor = ((Component)this).GetComponent<Character>();
			}
			if ((Object)(object)_anchor != (Object)null)
			{
				float health = _anchor.Health;
				float num = (float.IsNaN(_lastHealth) ? 9f : (_lastHealth - health));
				_lastHealth = health;
				if (num < 9f)
				{
					return;
				}
			}
			if (Time.time - _lastCryAt < 0.7f)
			{
				return;
			}
			_lastCryAt = Time.time;
			try
			{
				Global.AudioManager.PlaySoundAtPosition(BodySound.GetHurtSound(), ((Component)this).transform, 0f, 1f, 1f, 1f, 1f);
			}
			catch
			{
			}
		}
	}
	public sealed class AnchorPhysics
	{
		private readonly struct PairKey : IEquatable<PairKey>
		{
			private readonly int _a;

			private readonly int _b;

			public PairKey(Collider a, Collider b)
			{
				int instanceID = ((Object)a).GetInstanceID();
				int instanceID2 = ((Object)b).GetInstanceID();
				if (instanceID <= instanceID2)
				{
					_a = instanceID;
					_b = instanceID2;
				}
				else
				{
					_a = instanceID2;
					_b = instanceID;
				}
			}

			public bool Equals(PairKey o)
			{
				if (_a == o._a)
				{
					return _b == o._b;
				}
				return false;
			}

			public override bool Equals(object o)
			{
				if (o is PairKey o2)
				{
					return Equals(o2);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (_a * 397) ^ _b;
			}
		}

		private readonly Func<Character> _current;

		private readonly Func<ICompanionSettings> _cfg;

		private static readonly WaitForSeconds _burst0 = new WaitForSeconds(0.2f);

		private static readonly WaitForSeconds _burst1 = new WaitForSeconds(0.4f);

		private static readonly WaitForSeconds _burst2 = new WaitForSeconds(0.9f);

		private static readonly WaitForSeconds _burst3 = new WaitForSeconds(1.5f);

		private Character _stampHost;

		private readonly HashSet<PairKey> _stamped = new HashSet<PairKey>();

		private int _stamps;

		private int _restamps;

		private float _lastRestampAt = -1f;

		private bool _phantomApplied;

		private float _restampLogAt;

		private AnchorCollisionMode _lastMode;

		private bool _modeKnown;

		private readonly List<Character> _players = new List<Character>();

		private float _playersAt = -999f;

		private const float PlayerCacheSeconds = 1f;

		private Character Current => _current();

		private ICompanionSettings Cfg => _cfg();

		private bool HasLiveAnchor
		{
			get
			{
				Character val = _current();
				if ((Object)(object)val != (Object)null)
				{
					return val.Alive;
				}
				return false;
			}
		}

		private string TagPhys => CompanionRuntime.Tag("ANCHORPHYS", Cfg);

		public AnchorPhysics(Func<Character> current, Func<ICompanionSettings> cfg)
		{
			_current = current;
			_cfg = cfg;
		}

		public void Forget()
		{
			_stampHost = null;
			_stamped.Clear();
			_stamps = 0;
			_restamps = 0;
			_lastRestampAt = -1f;
			_phantomApplied = false;
			_players.Clear();
			_playersAt = -999f;
		}

		private List<Character> Players()
		{
			bool flag = _players.Count == 0 || Time.unscaledTime - _playersAt > 1f;
			if (!flag)
			{
				for (int i = 0; i < _players.Count; i++)
				{
					if ((Object)(object)_players[i] == (Object)null)
					{
						flag = true;
						break;
					}
				}
			}
			if (!flag)
			{
				return _players;
			}
			_playersAt = Time.unscaledTime;
			_players.Clear();
			CharacterManager instance = CharacterManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return _players;
			}
			for (int j = 0; j < instance.PlayerCharacters.Count; j++)
			{
				Character character = instance.GetCharacter(instance.PlayerCharacters.Values[j]);
				if ((Object)(object)character != (Object)null)
				{
					_players.Add(character);
				}
			}
			return _players;
		}

		public void Sync()
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Invalid comparison between Unknown and I4
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			if (!HasLiveAnchor)
			{
				return;
			}
			Character current = Current;
			if (_stampHost != current)
			{
				_stampHost = current;
				_stamped.Clear();
				_stamps = 0;
				_restamps = 0;
				_lastRestampAt = -1f;
				_phantomApplied = false;
			}
			AnchorCollisionMode anchorPlayerCollision = Cfg.AnchorPlayerCollision;
			if (!_modeKnown || anchorPlayerCollision != _lastMode)
			{
				if (_modeKnown)
				{
					CompanionRuntime.Log.LogMessage((object)$"{TagPhys} mode {_lastMode} -> {anchorPlayerCollision} (live).");
				}
				_lastMode = anchorPlayerCollision;
				_modeKnown = true;
			}
			if ((int)anchorPlayerCollision == 2)
			{
				ApplyPhantom(current);
				return;
			}
			RestorePhantom(current);
			Collider characterController = (Collider)(object)current.CharacterController;
			Collider charMoveBlockCollider = (Collider)(object)current.CharMoveBlockCollider;
			List<Character> list = Players();
			for (int i = 0; i < list.Count; i++)
			{
				Character val = list[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)current))
				{
					Collider characterController2 = (Collider)(object)val.CharacterController;
					Collider charMoveBlockCollider2 = (Collider)(object)val.CharMoveBlockCollider;
					Converge(anchorPlayerCollision, characterController, charMoveBlockCollider2, "anchorCC<->playerBox");
					Converge(anchorPlayerCollision, characterController2, charMoveBlockCollider, "playerCC<->anchorBox");
					Converge(anchorPlayerCollision, characterController, characterController2, "anchorCC<->playerCC");
					Converge(anchorPlayerCollision, charMoveBlockCollider, charMoveBlockCollider2, "anchorBox<->playerBox");
				}
			}
		}

		private void Converge(AnchorCollisionMode mode, Collider a, Collider b, string label)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected I4, but got Unknown
			bool flag = Ready(a) && Ready(b);
			PairKey item = (flag ? new PairKey(a, b) : default(PairKey));
			bool flag2 = flag && _stamped.Contains(item);
			bool flag3 = flag && Physics.GetIgnoreCollision(a, b);
			AnchorPhysAction val = AnchorPhysicsPolicy.Decide(mode, flag2, flag3, flag);
			switch (val - 1)
			{
			case 0:
				Physics.IgnoreCollision(a, b, true);
				_stamped.Add(item);
				_stamps++;
				CompanionRuntime.Log.LogMessage((object)(TagPhys + " exempt " + label + " — the anchor can no longer push the player."));
				break;
			case 2:
				Physics.IgnoreCollision(a, b, true);
				_restamps++;
				_lastRestampAt = Time.time;
				if (Time.time - _restampLogAt > 5f)
				{
					_restampLogAt = Time.time;
					CompanionRuntime.Log.LogMessage((object)$"{TagPhys} re-stamp: {label} was reset by a collider toggle (restamps={_restamps}).");
				}
				break;
			case 1:
				Physics.IgnoreCollision(a, b, false);
				_stamped.Remove(item);
				CompanionRuntime.Log.LogMessage((object)(TagPhys + " revoked " + label + " (mode=Block) — the anchor blocks the player again."));
				break;
			}
		}

		private static bool Ready(Collider c)
		{
			if ((Object)(object)c != (Object)null && c.enabled)
			{
				return ((Component)c).gameObject.activeInHierarchy;
			}
			return false;
		}

		private void ApplyPhantom(Character anchor)
		{
			CharacterController characterController = anchor.CharacterController;
			if ((Object)(object)characterController != (Object)null && characterController.detectCollisions)
			{
				characterController.detectCollisions = false;
				_phantomApplied = true;
				CompanionRuntime.Log.LogMessage((object)(TagPhys + " PHANTOM: controller detectCollisions=false."));
			}
			BoxCollider charMoveBlockCollider = anchor.CharMoveBlockCollider;
			if ((Object)(object)charMoveBlockCollider != (Object)null && ((Collider)charMoveBlockCollider).enabled)
			{
				((Collider)charMoveBlockCollider).enabled = false;
				_phantomApplied = true;
				CompanionRuntime.Log.LogMessage((object)(TagPhys + " PHANTOM: move-block box disabled — nothing can be blocked by the anchor (enemies may now overlap the pet's model). Flip back to PassPlayer + reloadcfg to restore it."));
			}
		}

		private void RestorePhantom(Character anchor)
		{
			if (_phantomApplied)
			{
				_phantomApplied = false;
				CharacterController characterController = anchor.CharacterController;
				if ((Object)(object)characterController != (Object)null)
				{
					characterController.detectCollisions = true;
				}
				BoxCollider charMoveBlockCollider = anchor.CharMoveBlockCollider;
				if ((Object)(object)charMoveBlockCollider != (Object)null)
				{
					((Collider)charMoveBlockCollider).enabled = true;
				}
				CompanionRuntime.Log.LogMessage((object)(TagPhys + " left PHANTOM — the anchor's blocking volumes are back (it blocks enemies again)."));
			}
		}

		public string Dump()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Invalid comparison between Unknown and I4
			//IL_02d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Invalid comparison between Unknown and I4
			StringBuilder stringBuilder = new StringBuilder();
			AnchorCollisionMode anchorPlayerCollision = Cfg.AnchorPlayerCollision;
			stringBuilder.AppendLine($"{TagPhys} mode={anchorPlayerCollision} glue={Cfg.GlueMode} offsetBehind={Cfg.GlueOffsetBehind:F2}m");
			if (!HasLiveAnchor)
			{
				stringBuilder.Append(TagPhys + " no live anchor.");
				return stringBuilder.ToString();
			}
			Character current = Current;
			stringBuilder.AppendLine(TagPhys + " anchor '" + current.Name + "': " + Describe(current));
			stringBuilder.AppendLine($"{TagPhys} stamps={_stamps} restamps={_restamps} " + string.Format("lastRestamp={0} phantomApplied={1}", (_lastRestampAt < 0f) ? "never" : $"{Time.time - _lastRestampAt:F0}s ago", _phantomApplied));
			Collider characterController = (Collider)(object)current.CharacterController;
			Collider charMoveBlockCollider = (Collider)(object)current.CharMoveBlockCollider;
			List<Character> list = Players();
			for (int i = 0; i < list.Count; i++)
			{
				Character val = list[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)current))
				{
					float num = Vector3.Distance(((Component)current).transform.position, ((Component)val).transform.position);
					stringBuilder.AppendLine($"{TagPhys} player '{val.Name}' (local={val.IsLocalPlayer}, {num:F1}m from the anchor): {Describe(val)}");
					Collider characterController2 = (Collider)(object)val.CharacterController;
					Collider charMoveBlockCollider2 = (Collider)(object)val.CharMoveBlockCollider;
					int ok = 0;
					int total = 0;
					stringBuilder.AppendLine(TagPhys + "   " + Pair(characterController, charMoveBlockCollider2, "anchorCC<->playerBox", ref ok, ref total));
					stringBuilder.AppendLine(TagPhys + "   " + Pair(characterController2, charMoveBlockCollider, "playerCC<->anchorBox", ref ok, ref total));
					stringBuilder.AppendLine(TagPhys + "   " + Pair(characterController, characterController2, "anchorCC<->playerCC", ref ok, ref total));
					stringBuilder.AppendLine(TagPhys + "   " + Pair(charMoveBlockCollider, charMoveBlockCollider2, "anchorBox<->playerBox", ref ok, ref total));
					stringBuilder.AppendLine($"{TagPhys}   ignored {ok}/{total} pair(s)" + (((int)anchorPlayerCollision == 1 && ok < total) ? " — NOT fully exempt: the anchor can still shove this player." : (((int)anchorPlayerCollision == 1) ? " — the anchor cannot move this player." : "")));
				}
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		public string Fragment()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			if (!HasLiveAnchor)
			{
				return "phys=no-anchor";
			}
			AnchorCollisionMode anchorPlayerCollision = Cfg.AnchorPlayerCollision;
			if ((int)anchorPlayerCollision == 2)
			{
				return $"phys=Phantom(applied={_phantomApplied})";
			}
			Character current = Current;
			Collider characterController = (Collider)(object)current.CharacterController;
			Collider charMoveBlockCollider = (Collider)(object)current.CharMoveBlockCollider;
			int ok = 0;
			int total = 0;
			List<Character> list = Players();
			for (int i = 0; i < list.Count; i++)
			{
				Character val = list[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)current))
				{
					Collider characterController2 = (Collider)(object)val.CharacterController;
					Collider charMoveBlockCollider2 = (Collider)(object)val.CharMoveBlockCollider;
					Count(characterController, charMoveBlockCollider2, ref ok, ref total);
					Count(characterController2, charMoveBlockCollider, ref ok, ref total);
					Count(characterController, characterController2, ref ok, ref total);
					Count(charMoveBlockCollider, charMoveBlockCollider2, ref ok, ref total);
				}
			}
			return $"phys={anchorPlayerCollision} ignored={ok}/{total} restamps={_restamps}";
		}

		private static void Count(Collider a, Collider b, ref int ok, ref int total)
		{
			total++;
			if (Ready(a) && Ready(b) && Physics.GetIgnoreCollision(a, b))
			{
				ok++;
			}
		}

		private string Pair(Collider a, Collider b, string label, ref int ok, ref int total)
		{
			total++;
			if (!Ready(a) || !Ready(b))
			{
				return label + " = NOT READY (" + (Ready(a) ? "" : ("a:" + State(a) + " ")) + (Ready(b) ? "" : ("b:" + State(b))) + ") — skipped, will retry";
			}
			bool ignoreCollision = Physics.GetIgnoreCollision(a, b);
			if (ignoreCollision)
			{
				ok++;
			}
			return $"{label} = ignored:{ignoreCollision}";
		}

		private static string State(Collider c)
		{
			if (!((Object)(object)c == (Object)null))
			{
				if (((Component)c).gameObject.activeInHierarchy)
				{
					if (c.enabled)
					{
						return "ready";
					}
					return "disabled";
				}
				return "GO-inactive";
			}
			return "absent";
		}

		private static string Describe(Character c)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			CharacterController characterController = c.CharacterController;
			BoxCollider charMoveBlockCollider = c.CharMoveBlockCollider;
			string text = (((Object)(object)characterController == (Object)null) ? "CC=absent" : $"CC={State((Collider)(object)characterController)} r={characterController.radius:F2} h={characterController.height:F2} layer={LayerMask.LayerToName(((Component)characterController).gameObject.layer)} detect={characterController.detectCollisions}");
			string text2 = (((Object)(object)charMoveBlockCollider == (Object)null) ? "CharMoveBlock=absent" : $"CharMoveBlock={State((Collider)(object)charMoveBlockCollider)} size={charMoveBlockCollider.size} layer={LayerMask.LayerToName(((Component)charMoveBlockCollider).gameObject.layer)}");
			return text + " · " + text2;
		}

		public IEnumerator StampWhenReady(Character anchor)
		{
			Sync();
			WaitForSeconds[] array = (WaitForSeconds[])(object)new WaitForSeconds[4] { _burst0, _burst1, _burst2, _burst3 };
			WaitForSeconds[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				yield return array2[i];
				if ((Object)(object)anchor == (Object)null || (Object)(object)Current != (Object)(object)anchor)
				{
					break;
				}
				Sync();
			}
		}
	}
	public sealed class AnchorStats
	{
		private readonly Func<Character> _current;

		private readonly Func<ICompanionSettings> _cfg;

		private const string StatSourceId = "CK_SpeciesStats";

		private Character _statsHost;

		private float[] _statBaseline;

		private float[] _appliedWant;

		private Character _vitalsHost;

		private Character Current => _current();

		private ICompanionSettings Cfg => _cfg();

		private bool HasLiveAnchor
		{
			get
			{
				Character val = _current();
				if ((Object)(object)val != (Object)null)
				{
					return val.Alive;
				}
				return false;
			}
		}

		private string TagStats => CompanionRuntime.Tag("STATS", Cfg);

		private string TagAnchor => CompanionRuntime.Tag("ANCHOR", Cfg);

		public AnchorStats(Func<Character> current, Func<ICompanionSettings> cfg)
		{
			_current = current;
			_cfg = cfg;
		}

		public void ForgetAnchor()
		{
			_statsHost = null;
			_statBaseline = null;
			_appliedWant = null;
		}

		public void ApplyCreatureStats(CreatureAttributes eff)
		{
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Expected O, but got Unknown
			if (!HasLiveAnchor)
			{
				return;
			}
			CharacterStats stats = Current.Stats;
			if ((Object)(object)stats == (Object)null)
			{
				return;
			}
			Stat[] array = Targets(stats);
			if (_statsHost != Current)
			{
				_statsHost = Current;
				_appliedWant = null;
				_statBaseline = new float[array.Length];
				for (int i = 0; i < array.Length; i++)
				{
					_statBaseline[i] = ((array[i] != null) ? array[i].BaseValue : 0f);
				}
			}
			if (eff == null)
			{
				if (_appliedWant == null)
				{
					return;
				}
				foreach (Stat obj in array)
				{
					if (obj != null)
					{
						obj.RemoveStack("CK_SpeciesStats", false);
					}
				}
				_appliedWant = null;
				CompanionRuntime.Log.LogMessage((object)(TagStats + " species stats cleared from the anchor (ghost defaults restored)."));
				return;
			}
			float[] array2 = Wants(eff);
			if (_appliedWant != null && ApproxSame(array2, _appliedWant))
			{
				return;
			}
			int num = 0;
			for (int k = 0; k < array.Length; k++)
			{
				if (array[k] != null)
				{
					float num2 = array2[k] - _statBaseline[k];
					array[k].RemoveStack("CK_SpeciesStats", false);
					if (Mathf.Abs(num2) > 0.001f)
					{
						array[k].AddStack(new StatStack("CK_SpeciesStats", num2, (Tag[])null), false);
						num++;
					}
				}
			}
			_appliedWant = array2;
			CompanionRuntime.Log.LogMessage((object)$"{TagStats} species defense applied to the anchor ({num} stat stack(s)): {AttributeCapture.Describe(eff)}");
		}

		private static Stat[] Targets(CharacterStats st)
		{
			int num = 9;
			Stat[] array = (Stat[])(object)new Stat[2 * num + 3];
			for (int i = 0; i < num; i++)
			{
				array[i] = ((st.m_damageResistance != null && i < st.m_damageResistance.Length) ? st.m_damageResistance[i] : null);
				array[num + i] = ((st.m_damageProtection != null && i < st.m_damageProtection.Length) ? st.m_damageProtection[i] : null);
			}
			array[2 * num] = st.m_allDamageProtection;
			array[2 * num + 1] = st.m_impactResistance;
			array[2 * num + 2] = st.m_barrierStat;
			return array;
		}

		private static float[] Wants(CreatureAttributes eff)
		{
			int num = 9;
			float[] array = new float[2 * num + 3];
			for (int i = 0; i < num; i++)
			{
				array[i] = eff.Resist[i];
				array[num + i] = eff.Protection[i];
			}
			array[2 * num] = eff.ProtectionAll;
			array[2 * num + 1] = eff.ImpactResistance;
			array[2 * num + 2] = eff.Barrier;
			return array;
		}

		private static bool ApproxSame(float[] a, float[] b)
		{
			if (a.Length != b.Length)
			{
				return false;
			}
			for (int i = 0; i < a.Length; i++)
			{
				if (Mathf.Abs(a[i] - b[i]) > 0.001f)
				{
					return false;
				}
			}
			return true;
		}

		public void DumpCreatureStats()
		{
			if (!HasLiveAnchor || (Object)(object)Current.Stats == (Object)null)
			{
				CompanionRuntime.Log.LogMessage((object)(TagStats + " no live anchor to dump."));
				return;
			}
			if (_appliedWant == null || _statsHost != Current)
			{
				CompanionRuntime.Log.LogMessage((object)(TagStats + " no species stats applied to this anchor (ghost defaults)."));
				return;
			}
			string[] array = new string[2] { "resist", "protection" };
			Stat[] array2 = Targets(Current.Stats);
			int num = 9;
			for (int i = 0; i < array2.Length; i++)
			{
				if (array2[i] != null && (_appliedWant[i] != 0f || _statBaseline[i] != 0f))
				{
					string text = ((i < num) ? $"{array[0]}[{i}]" : ((i < 2 * num) ? $"{array[1]}[{i - num}]" : ((i == 2 * num) ? "protAll" : ((i == 2 * num + 1) ? "impactRes" : "barrier"))));
					CompanionRuntime.Log.LogMessage((object)$"{TagStats}   {text}: baseline={_statBaseline[i]:F1} + stack={_appliedWant[i] - _statBaseline[i]:F1} -> live={array2[i].CurrentValue:F1}");
				}
			}
		}

		public void ApplyVitals(float maxHealth)
		{
			if (!HasLiveAnchor || maxHealth <= 0f)
			{
				return;
			}
			CharacterStats stats = Current.Stats;
			if ((Object)(object)stats == (Object)null)
			{
				return;
			}
			bool flag = (Object)(object)_vitalsHost != (Object)(object)Current;
			if (!flag && Mathf.Approximately(stats.BaseMaxHealth, maxHealth))
			{
				return;
			}
			stats.BaseMaxHealth = maxHealth;
			if (flag)
			{
				_vitalsHost = Current;
				float currentHealth = stats.CurrentHealth;
				stats.SetHealth(maxHealth);
				if (!Mathf.Approximately(currentHealth, maxHealth))
				{
					CompanionRuntime.Log.LogMessage((object)$"{TagStats} fresh anchor vitals: {currentHealth:F0} -> {maxHealth:F0} (full — bug-23 first-apply reset).");
				}
			}
			else if (stats.CurrentHealth > maxHealth)
			{
				stats.SetHealth(maxHealth);
			}
		}

		public bool ApplyTemperatureDrain(float amount)
		{
			if (!HasLiveAnchor || amount <= 0f)
			{
				return false;
			}
			CharacterStats stats = Current.Stats;
			if ((Object)(object)stats == (Object)null)
			{
				return false;
			}
			float currentHealth = stats.CurrentHealth;
			float num = Mathf.Max(currentHealth - amount, 1f);
			if (num < currentHealth)
			{
				stats.SetHealth(num);
			}
			return currentHealth - amount < 1f;
		}

		public string HealthSummary(bool critFired)
		{
			if (!HasLiveAnchor)
			{
				return "no live anchor.";
			}
			CharacterStats stats = Current.Stats;
			float num = ((stats.MaxHealth > 0f) ? (stats.CurrentHealth / stats.MaxHealth * 100f) : 0f);
			return $"hp={stats.CurrentHealth:F0}/{stats.MaxHealth:F0} ({num:F0}%) critFired={critFired}";
		}

		public bool Heal()
		{
			if (!HasLiveAnchor)
			{
				return false;
			}
			Current.Stats.SetHealth(Current.Stats.ActiveMaxHealth);
			return true;
		}

		public bool HealAmount(float amount, out bool reArmCrit)
		{
			reArmCrit = false;
			if (!HasLiveAnchor || amount <= 0f || (Object)(object)Current.Stats == (Object)null)
			{
				return false;
			}
			CharacterStats stats = Current.Stats;
			float currentHealth = stats.CurrentHealth;
			stats.SetHealth(Mathf.Min(currentHealth + amount, stats.ActiveMaxHealth));
			if (stats.CurrentHealth > stats.ActiveMaxHealth * 0.5f)
			{
				reArmCrit = true;
			}
			CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} fed-heal +{stats.CurrentHealth - currentHealth:F0} ({currentHealth:F0} -> {stats.CurrentHealth:F0}/{stats.ActiveMaxHealth:F0}).");
			return true;
		}

		public bool SetHealth(float value, out bool reArmCrit)
		{
			reArmCrit = false;
			if (!HasLiveAnchor || (Object)(object)Current.Stats == (Object)null)
			{
				return false;
			}
			CharacterStats stats = Current.Stats;
			float currentHealth = stats.CurrentHealth;
			stats.SetHealth(Mathf.Clamp(value, 1f, stats.ActiveMaxHealth));
			if (stats.CurrentHealth > stats.ActiveMaxHealth * 0.5f)
			{
				reArmCrit = true;
			}
			CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} dev set-health {currentHealth:F0} -> {stats.CurrentHealth:F0}/{stats.ActiveMaxHealth:F0}.");
			return true;
		}

		public bool TryGetHealth(out float current, out float max)
		{
			if (!HasLiveAnchor)
			{
				current = 0f;
				max = 0f;
				return false;
			}
			CharacterStats stats = Current.Stats;
			current = stats.CurrentHealth;
			max = stats.MaxHealth;
			return true;
		}
	}
	public static class AttributeCapture
	{
		private static readonly string[] TypeNames = new string[9] { "Phys", "Ethereal", "Decay", "Electric", "Frost", "Fire", "Dark", "Light", "Raw" };

		public static CreatureAttributes From(Character src)
		{
			if ((Object)(object)src == (Object)null)
			{
				return null;
			}
			try
			{
				CreatureAttributes val = Read(src);
				if (val != null)
				{
					CompanionRuntime.Log.LogMessage((object)("[STATS] captured '" + src.Name + "': " + Describe(val)));
				}
				return val;
			}
			catch (Exception ex)
			{
				CompanionRuntime.Log.LogWarning((object)("[STATS] capture from '" + src.Name + "' failed (" + ex.Message + ") — config-stat fallback applies."));
				return null;
			}
		}

		private static CreatureAttributes Read(Character src)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Expected O, but got Unknown
			CharacterStats stats = src.Stats;
			if ((Object)(object)stats == (Object)null)
			{
				CompanionRuntime.Log.LogWarning((object)("[STATS] '" + src.Name + "' has no CharacterStats — nothing to capture."));
				return null;
			}
			float num = stats.MovementSpeed;
			if (num < 0.1f)
			{
				num = 1f;
			}
			CreatureAttributes val = new CreatureAttributes
			{
				MaxHealth = stats.BaseMaxHealth,
				MoveSpeed = src.Speed * num,
				ImpactResistance = BaseOf(stats.m_impactResistance),
				Barrier = BaseOf(stats.m_barrierStat),
				ProtectionAll = BaseOf(stats.m_allDamageProtection)
			};
			int num2 = 9;
			for (int i = 0; i < num2; i++)
			{
				if (stats.m_damageResistance != null && i < stats.m_damageResistance.Length)
				{
					val.Resist[i] = BaseOf(stats.m_damageResistance[i]);
				}
				if (stats.m_damageProtection != null && i < stats.m_damageProtection.Length)
				{
					val.Protection[i] = BaseOf(stats.m_damageProtection[i]);
				}
			}
			if (!CaptureWeaponDamage(src, stats, val) && !CaptureHitboxDamage(src, val))
			{
				CompanionRuntime.Log.LogMessage((object)("[STATS] '" + src.Name + "' has no readable weapon or hitbox damage — the pet keeps [Combat] AttackDamage."));
			}
			return val;
		}

		private static bool CaptureWeaponDamage(Character src, CharacterStats st, CreatureAttributes a)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected I4, but got Unknown
			Weapon currentWeapon = src.CurrentWeapon;
			if ((Object)(object)currentWeapon == (Object)null || currentWeapon.Damage == null || currentWeapon.Damage.Count == 0)
			{
				return false;
			}
			foreach (DamageType item in currentWeapon.Damage.List)
			{
				int num = (int)item.Type;
				if (num >= 0 && num < 9 && !(item.Damage <= 0f))
				{
					a.Damage[num] += item.Damage * DealerBonus(st, num);
				}
			}
			a.Impact = currentWeapon.Impact;
			return a.HasDamage;
		}

		private static bool CaptureHitboxDamage(Character src, CreatureAttributes a)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected I4, but got Unknown
			PunctualDamage val = null;
			float num = 0f;
			PunctualDamage[] componentsInChildren = ((Component)src).GetComponentsInChildren<PunctualDamage>(true);
			foreach (PunctualDamage val2 in componentsInChildren)
			{
				float num2 = TotalOf(val2.Damages);
				if (num2 <= 0f)
				{
					num2 = TotalOf(val2.DamagesAI);
				}
				if (num2 > num)
				{
					num = num2;
					val = val2;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			DamageType[] array = ((TotalOf(val.Damages) > 0f) ? val.Damages : val.DamagesAI);
			CharacterStats stats = src.Stats;
			DamageType[] array2 = array;
			foreach (DamageType val3 in array2)
			{
				int num3 = (int)val3.Type;
				if (num3 >= 0 && num3 < 9 && !(val3.Damage <= 0f))
				{
					a.Damage[num3] += val3.Damage * DealerBonus(stats, num3);
				}
			}
			a.Impact = val.Knockback;
			return a.HasDamage;
		}

		private static float DealerBonus(CharacterStats st, int typeIndex)
		{
			if ((Object)(object)st == (Object)null || st.m_damageTypesModifier == null || typeIndex >= st.m_damageTypesModifier.Length)
			{
				return 1f;
			}
			Stat val = st.m_damageTypesModifier[typeIndex];
			if (val == null)
			{
				return 1f;
			}
			float num = val.CurrentValue;
			if (num < 0.01f)
			{
				num = val.BaseValue;
			}
			if (!(num < 0.01f))
			{
				return num;
			}
			return 1f;
		}

		private static float BaseOf(Stat s)
		{
			return s?.BaseValue ?? 0f;
		}

		private static float TotalOf(DamageType[] damages)
		{
			if (damages == null)
			{
				return 0f;
			}
			float num = 0f;
			foreach (DamageType val in damages)
			{
				num += val.Damage;
			}
			return num;
		}

		public static string Describe(CreatureAttributes a)
		{
			if (a == null)
			{
				return "none";
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append($"hp={a.MaxHealth:F0} spd={a.MoveSpeed:F2} dmg=[{PerType(a.Damage)}] impact={a.Impact:F1}");
			stringBuilder.Append(" res=[" + PerType(a.Resist) + "] prot=[" + PerType(a.Protection) + "]");
			if (a.ProtectionAll != 0f)
			{
				stringBuilder.Append($" protAll={a.ProtectionAll:F0}");
			}
			if (a.ImpactResistance != 0f)
			{
				stringBuilder.Append($" impactRes={a.ImpactResistance:F0}");
			}
			if (a.Barrier != 0f)
			{
				stringBuilder.Append($" barrier={a.Barrier:F0}");
			}
			return stringBuilder.ToString();
		}

		private static string PerType(float[] values)
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < values.Length && i < TypeNames.Length; i++)
			{
				if (values[i] != 0f)
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(' ');
					}
					stringBuilder.Append(TypeNames[i]).Append(':').Append(values[i].ToString("F1"));
				}
			}
			if (stringBuilder.Length <= 0)
			{
				return "none";
			}
			return stringBuilder.ToString();
		}
	}
	public static class BodyFactory
	{
		public enum EquipmentStripMode
		{
			Components,
			GameObjects
		}

		private static Character _ghostPrefab;

		private static bool _ghostSearched;

		private static readonly HashSet<string> HumanoidStrip = new HashSet<string>
		{
			"SNPC", "SNPCMoving", "SNPCContainer", "NPCInteraction", "DialogueActor", "DialogueActorLocalize", "DialogueStarter", "DialogueTreeExt", "DialogueTreeController", "DialogueAudio",
			"BasicDialogueSetup", "DialogueSetup", "Merchant", "MerchantPouch", "MerchantRotatingInventory", "MerchantFastTravel", "InteractionDialogue", "InteractionMerchantDialogue", "InteractionTrainerDialogue", "AutoFacing",
			"NPCLookFollow"
		};

		private static readonly HashSet<string> GameplayStrip = new HashSet<string>
		{
			"MeleeSkill", "Hitbox", "PunctualDamage", "WeaponDamage", "CharacterSkillKnowledge", "ItemContainer", "InteractionActivator", "InteractionOpenContainer", "InteractionTriggerBase", "DropTable",
			"Dropable", "GuaranteedDrop", "LootableOnDeath", "StartingEquipment", "CharAIDisable", "CharacterBarManager"
		};

		private static GameObject _holder;

		public static Character FindNearest(Character player, float range, string speciesFilter)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			List<Character> list = new List<Character>();
			CharacterManager.Instance.FindCharactersInRange(((Component)player).transform.position, range, ref list);
			Character result = null;
			float num = float.MaxValue;
			foreach (Character item in list)
			{
				if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)player) && item.IsAI && item.Alive && (string.IsNullOrEmpty(speciesFilter) || Species.NameEquals(item.Name, speciesFilter)))
				{
					float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)item).transform.position);
					if (num2 < num)
					{
						num = num2;
						result = item;
					}
				}
			}
			return result;
		}

		public static Character FindNearestCharacter(Character player, float range, Func<Character, bool> accept)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			List<Character> list = new List<Character>();
			CharacterManager.Instance.FindCharactersInRange(((Component)player).transform.position, range, ref list);
			Character result = null;
			float num = float.MaxValue;
			foreach (Character item in list)
			{
				if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)player) && item.Alive && (accept == null || accept(item)))
				{
					float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)item).transform.position);
					if (num2 < num)
					{
						num = num2;
						result = item;
					}
				}
			}
			return result;
		}

		public static Character FindGhostPrefab()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Invalid comparison between Unknown and I4
			if (_ghostSearched)
			{
				return _ghostPrefab;
			}
			Character val = null;
			Character val2 = null;
			Character[] array = Resources.FindObjectsOfTypeAll<Character>();
			foreach (Character val3 in array)
			{
				if ((Object)(object)val3 == (Object)null || (Object)(object)((Component)val3).gameObject == (Object)null)
				{
					continue;
				}
				string text = ((Object)((Component)val3).gameObject).name.ToLowerInvariant();
				if (!text.Contains("ghost") && !text.Contains("spirit") && !text.Contains("spectral"))
				{
					continue;
				}
				Scene scene = ((Component)val3).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					CompanionRuntime.Log.LogMessage((object)$"[GHOST] candidate '{((Object)((Component)val3).gameObject).name}' faction={val3.Faction} legacyVisual={val3.UseLegacyVisual}");
					if ((Object)(object)val2 == (Object)null)
					{
						val2 = val3;
					}
					if ((Object)(object)val == (Object)null && (int)val3.Faction == 1)
					{
						val = val3;
					}
				}
			}
			_ghostPrefab = (((Object)(object)val != (Object)null) ? val : val2);
			if ((Object)(object)_ghostPrefab == (Object)null)
			{
				CompanionRuntime.Log.LogWarning((object)"[GHOST] no ghost/spirit prefab found in Resources (stand-in unavailable) — will retry.");
				return null;
			}
			_ghostSearched = true;
			CompanionRuntime.Log.LogMessage((object)("[GHOST] using '" + ((Object)((Component)_ghostPrefab).gameObject).name + "' as the stand-in body."));
			return _ghostPrefab;
		}

		public static Character SpawnGhostActive(Character player)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			Character val = FindGhostPrefab();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Vector3 val2 = ((Component)player).transform.position + ((Component)player).transform.forward * 2f;
			if (NavProbe.SampleAtFeet(val2, 2f, out var pos) || NavProbe.SampleAtFeet(((Component)player).transform.position, 1.5f, out pos))
			{
				val2 = pos;
			}
			GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, val2, ((Component)player).transform.rotation);
			Character component = val3.GetComponent<Character>();
			CompanionRuntime.Log.LogMessage((object)$"[GHOST] spawned active '{((Object)val3).name}' (Character={(Object)(object)component != (Object)null}); waiting for visuals.");
			return component;
		}

		public static bool GhostVisualReady(Character ghost)
		{
			if ((Object)(object)ghost != (Object)null && (Object)(object)ghost.Visuals != (Object)null)
			{
				return ghost.Visuals.DefaultVisualsInitialized;
			}
			return false;
		}

		public static void NudgeGhostActive(Character ghost)
		{
			if (!((Object)(object)ghost == (Object)null))
			{
				ghost.DisableAfterInit = false;
				if (!((Component)ghost).gameObject.activeSelf)
				{
					((Component)ghost).gameObject.SetActive(true);
				}
			}
		}

		public static bool ForceGhostVisuals(Character ghost)
		{
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ghost == (Object)null)
			{
				return false;
			}
			GameObject gameObject = ((Component)ghost).gameObject;
			int num = gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true).Length;
			bool flag = (Object)(object)ghost.Visuals == (Object)null;
			bool flag2 = ghost.VisualData == null;
			bool flag3 = (Object)(object)ghost.CharacterVisualsPrefab == (Object)null;
			CompanionRuntime.Log.LogMessage((object)$"[GHOST] force: activeSelf={gameObject.activeSelf} startInit={ghost.m_startInitDone} visualsNull={flag} visualDataNull={flag2} prefabNull={flag3} renderers={num}");
			try
			{
				CharacterVisuals val = ghost.Visuals;
				if ((Object)(object)val == (Object)null && !flag3)
				{
					Transform val2 = Object.Instantiate<Transform>(ghost.CharacterVisualsPrefab, gameObject.transform, false);
					((Object)val2).name = ((Object)ghost.CharacterVisualsPrefab).name;
					val2.localPosition = Vector3.zero;
					val2.localRotation = Quaternion.identity;
					val2.localScale = Vector3.one;
					val = (ghost.m_visualsHolder = ((Component)val2).GetComponent<CharacterVisuals>());
				}
				if ((Object)(object)val != (Object)null)
				{
					val.m_character = ghost;
					if (ghost.VisualData == null)
					{
						CompanionRuntime.Log.LogWarning((object)"[GHOST] VisualData is null — can't build default visuals (would NRE).");
					}
					else if (!val.DefaultVisualsInitialized)
					{
						val.InitDefaultVisuals();
					}
					Animator component = gameObject.GetComponent<Animator>();
					if ((Object)(object)component != (Object)null)
					{
						component.Rebind();
					}
				}
			}
			catch (Exception ex)
			{
				CompanionRuntime.Log.LogWarning((object)("[GHOST] force-visuals failed: " + ex.Message));
			}
			int num2 = gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true).Length;
			CompanionRuntime.Log.LogMessage((object)$"[GHOST] force result: renderers {num}->{num2}");
			return num2 > 0;
		}

		public static CompanionBody FinishGhostPuppet(Character ghost, Character player)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ghost == (Object)null)
			{
				return null;
			}
			GameObject gameObject = ((Component)ghost).gameObject;
			try
			{
				CharacterManager.Instance.Characters.Remove(UID.op_Implicit(ghost.UID));
			}
			catch
			{
			}
			DestroyImmediateIfPresent<PhotonView>(gameObject);
			CharacterAI component = gameObject.GetComponent<CharacterAI>();
			if ((Object)(object)component != (Object)null)
			{
				try
				{
					component.m_aiStatesRoot = null;
				}
				catch
				{
				}
				Object.DestroyImmediate((Object)(object)component);
			}
			DestroyImmediateIfPresent<AISquadMember>(gameObject);
			try
			{
				Object.DestroyImmediate((Object)(object)ghost);
			}
			catch
			{
			}
			if ((Object)(object)gameObject.GetComponent<NavMeshAgent>() == (Object)null)
			{
				gameObject.AddComponent<NavMeshAgent>();
			}
			CompanionBody companionBody = FinishPuppet(gameObject, "Spirit", player);
			if ((Object)(object)companionBody != (Object)null)
			{
				companionBody.YawOffset = 0f;
			}
			return companionBody;
		}

		public static CompanionBody BuildHumanoidPuppet(Character src, Character player, EquipmentStripMode equipStrip = EquipmentStripMode.Components)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			SweepHolderOrphans();
			string name = src.Name;
			CreatureAttributes capturedStats = AttributeCapture.From(src);
			Vector3 val = ((Component)player).transform.position + ((Component)player).transform.forward * 2f;
			if (NavProbe.SampleAtFeet(val, 2f, out var pos) || NavProbe.SampleAtFeet(((Component)player).transform.position, 1.5f, out pos))
			{
				val = pos;
			}
			GameObject val2 = Object.Instantiate<GameObject>(((Component)src).gameObject, Holder().transform);
			try
			{
				val2.transform.SetPositionAndRotation(val, ((Component)src).transform.rotation);
				DestroyImmediateIfPresent<PhotonView>(val2);
				CharacterAI component = val2.GetComponent<CharacterAI>();
				if ((Object)(object)component != (Object)null)
				{
					try
					{
						component.m_aiStatesRoot = null;
					}
					catch
					{
					}
					Object.DestroyImmediate((Object)(object)component);
				}
				DestroyImmediateIfPresent<AISquadMember>(val2);
				CharacterBarManager[] componentsInChildren = val2.GetComponentsInChildren<CharacterBarManager>(true);
				foreach (CharacterBarManager val3 in componentsInChildren)
				{
					Object.DestroyImmediate((Object)(object)val3);
				}
				if (equipStrip == EquipmentStripMode.Components)
				{
					StripEquipmentGameplay(val2);
				}
				else
				{
					StripEquippedItems(val2);
				}
				StripHumanoidMachinery(val2);
				DestroyImmediateIfPresent<Character>(val2);
				val2.transform.SetParent((Transform)null, true);
				CompanionBody companionBody = FinishPuppet(val2, name, player, equipStrip);
				if ((Object)(object)companionBody != (Object)null)
				{
					if ((Object)(object)val2.GetComponent<NavMeshAgent>() == (Object)null)
					{
						val2.AddComponent<NavMeshAgent>();
					}
					companionBody.YawOffset = 0f;
					companionBody.CapturedStats = capturedStats;
				}
				return companionBody;
			}
			catch
			{
				DestroyStranded(val2, "humanoid puppet body");
				throw;
			}
		}

		public static CompanionBody BuildPuppet(Character src, Character player, bool consume, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0, CreatureAttributes precaptured = null)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			SweepHolderOrphans();
			string name = src.Name;
			CreatureAttributes capturedStats = precaptured ?? AttributeCapture.From(src);
			ProjectileCapture.RangedAttackRig rangedAttackRig = ((rangedProjectileFilter != null) ? ProjectileCapture.From(src, rangedProjectileFilter, Holder().transform, rangedSkillPrefabId) : null);
			Vector3 pos = ((Component)player).transform.position + ((Component)player).transform.forward * 2f;
			GameObject go = null;
			try
			{
				go = (consume ? CloneConsuming(src, pos) : CloneNonConsuming(src, pos));
				CompanionBody companionBody = FinishPuppet(go, name, player);
				if ((Object)(object)companionBody != (Object)null)
				{
					companionBody.CapturedStats = capturedStats;
					ProjectileCapture.Attach(rangedAttackRig, companionBody);
				}
				return companionBody;
			}
			catch
			{
				DestroyStranded(go, "no-consume/consume puppet body");
				if (rangedAttackRig != null && (Object)(object)rangedAttackRig.Root != (Object)null)
				{
					CompanionRuntime.Log.LogWarning((object)("[BODYFACTORY] destroyed stranded ranged rig after failed build: '" + ((Object)rangedAttackRig.Root).name + "'"));
					Object.Destroy((Object)(object)rangedAttackRig.Root);
				}
				throw;
			}
		}

		private static CompanionBody FinishPuppet(GameObject go, string speciesId, Character player, EquipmentStripMode? humanoidEquipStrip = null)
		{
			StripBrain(go, humanoidEquipStrip);
			CompanionBody companionBody = go.AddComponent<CompanionBody>();
			go.AddComponent<RigStabilizer>();
			companionBody.Target = ((Component)player).transform;
			companionBody.SpeciesId = speciesId;
			Object.DontDestroyOnLoad((Object)(object)go);
			player.ResetCombat();
			int num = go.GetComponentsInChildren<SkinnedMeshRenderer>(true).Length;
			int num2 = CountRenderReady(go);
			CompanionRuntime.Log.LogMessage((object)$"[CLONE] '{speciesId}' puppet ready (Animator={(Object)(object)go.GetComponent<Animator>() != (Object)null}, renderers={num}, drawReady={num2}).");
			if (num > 0 && num2 == 0)
			{
				CompanionRuntime.Log.LogWarning((object)("[CLONE] '" + speciesId + "' has NO draw-ready renderer (bug-8 signature) — dumping state + attempting auto-repair."));
				VisDump(go, "bug-8 pre-repair");
				VisRepair(go);
			}
			return companionBody;
		}

		private static bool RenderReady(Renderer r)
		{
			if ((Object)(object)r == (Object)null || !r.enabled || !((Component)r).gameObject.activeInHierarchy || r.forceRenderingOff)
			{
				return false;
			}
			if ((Object)(object)r.sharedMaterial == (Object)null)
			{
				return false;
			}
			SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((r is SkinnedMeshRenderer) ? r : null);
			if ((Object)(object)val != (Object)null && (Object)(object)val.sharedMesh == (Object)null)
			{
				return false;
			}
			return true;
		}

		public static int CountRenderReady(GameObject go)
		{
			int num = 0;
			if ((Object)(object)go != (Object)null)
			{
				Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>(true);
				foreach (Renderer r in componentsInChildren)
				{
					if (RenderReady(r))
					{
						num++;
					}
				}
			}
			return num;
		}

		public static void VisDump(GameObject go, string tag)
		{
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)go == (Object)null)
			{
				CompanionRuntime.Log.LogWarning((object)"[VIS] dump: no puppet body.");
				return;
			}
			Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>(true);
			CompanionRuntime.Log.LogMessage((object)$"[VIS] {tag}: '{((Object)go).name}' — {componentsInChildren.Length} renderer(s), {CountRenderReady(go)} draw-ready.");
			Renderer[] array = componentsInChildren;
			foreach (Renderer val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((val is SkinnedMeshRenderer) ? val : null);
					string text = "";
					Material[] sharedMaterials = val.sharedMaterials;
					for (int j = 0; j < sharedMaterials.Length; j++)
					{
						text = text + ((j > 0) ? "," : "") + (((Object)(object)sharedMaterials[j] != (Object)null) ? ((Object)sharedMaterials[j]).name : "NULL");
					}
					string text2 = (((Object)(object)val2 == (Object)null) ? "" : (string.Format(" mesh={0} offscreenUpd={1}", ((Object)(object)val2.sharedMesh != (Object)null) ? ((Object)val2.sharedMesh).name : "NULL", val2.updateWhenOffscreen) + string.Format(" rootBone={0} bones={1}", ((Object)(object)val2.rootBone != (Object)null) ? ((Object)val2.rootBone).name : "null", (val2.bones != null) ? val2.bones.Length : 0)));
					ManualLogSource log = CompanionRuntime.Log;
					string text3 = $"[VIS]   '{((Object)((Component)val).gameObject).name}' [{((object)val).GetType().Name}] enabled={val.enabled} activeInHier={((Component)val).gameObject.activeInHierarchy}";
					string text4 = $" forceOff={val.forceRenderingOff} isVisible={val.isVisible} layer={LayerMask.LayerToName(((Component)val).gameObject.layer)}";
					string arg = text;
					Bounds bounds = val.bounds;
					log.LogMessage((object)(text3 + text4 + text2 + $" mats=[{arg}] boundsSize={((Bounds)(ref bounds)).size}"));
				}
			}
		}

		public static bool VisRepair(GameObject go)
		{
			if ((Object)(object)go == (Object)null)
			{
				CompanionRuntime.Log.LogWarning((object)"[VIS] repair: no puppet body.");
				return false;
			}
			int num = CountRenderReady(go);
			Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				val.enabled = true;
				val.forceRenderingOff = false;
				SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((val is SkinnedMeshRenderer) ? val : null);
				if ((Object)(object)val2 != (Object)null)
				{
					val2.updateWhenOffscreen = true;
					if ((Object)(object)val2.rootBone == (Object)null && (Object)(object)val2.sharedMesh != (Object)null)
					{
						val2.rootBone = ((Component)val2).transform;
						CompanionRuntime.Log.LogWarning((object)("[VIS] repair: '" + ((Object)((Component)val2).gameObject).name + "' rootBone was null/destroyed — fell back to its own transform."));
					}
				}
			}
			if (CountRenderReady(go) == 0)
			{
				Renderer val3 = null;
				Renderer[] componentsInChildren2 = go.GetComponentsInChildren<Renderer>(true);
				foreach (Renderer val4 in componentsInChildren2)
				{
					if (!((Object)(object)val4 == (Object)null))
					{
						SkinnedMeshRenderer val5 = (SkinnedMeshRenderer)(object)((val4 is SkinnedMeshRenderer) ? val4 : null);
						if (!((Object)(object)val5 != (Object)null) || !((Object)(object)val5.sharedMesh == (Object)null))
						{
							val3 = val4;
							break;
						}
					}
				}
				Transform val6 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).transform : null);
				while ((Object)(object)val6 != (Object)null)
				{
					if (!((Component)val6).gameObject.activeSelf)
					{
						((Component)val6).gameObject.SetActive(true);
						CompanionRuntime.Log.LogWarning((object)("[VIS] repair: activated inactive '" + ((Object)val6).name + "' (no renderer would draw without it)."));
					}
					val6 = (((Object)(object)val6 == (Object)(object)go.transform) ? null : val6.parent);
				}
			}
			int num2 = CountRenderReady(go);
			CompanionRuntime.Log.LogMessage((object)$"[VIS] repair: draw-ready {num} -> {num2}.");
			return num2 > 0;
		}

		private static GameObject CloneConsuming(Character src, Vector3 pos)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			return Object.Instantiate<GameObject>(((Component)src).gameObject, pos, ((Component)src).transform.rotation);
		}

		private static GameObject CloneNonConsuming(Character src, Vector3 pos)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(((Component)src).gameObject, Holder().transform);
			try
			{
				val.transform.SetPositionAndRotation(pos, ((Component)src).transform.rotation);
				DestroyImmediateIfPresent<PhotonView>(val);
				CharacterAI component = val.GetComponent<CharacterAI>();
				if ((Object)(object)component != (Object)null)
				{
					try
					{
						component.m_aiStatesRoot = null;
					}
					catch
					{
					}
					Object.DestroyImmediate((Object)(object)component);
				}
				DestroyImmediateIfPresent<AISquadMember>(val);
				CharacterBarManager[] componentsInChildren = val.GetComponentsInChildren<CharacterBarManager>(true);
				foreach (CharacterBarManager val2 in componentsInChildren)
				{
					Object.DestroyImmediate((Object)(object)val2);
				}
				StripEquippedItems(val);
				DestroyImmediateIfPresent<Character>(val);
				val.transform.SetParent((Transform)null, true);
				return val;
			}
			catch
			{
				DestroyStranded(val, "no-consume clone");
				throw;
			}
		}

		private static void StripEquippedItems(GameObject go)
		{
			Item[] componentsInChildren = go.GetComponentsInChildren<Item>(true);
			foreach (Item val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					try
					{
						Object.DestroyImmediate((Object)(object)((Component)val).gameObject);
					}
					catch
					{
					}
				}
			}
		}

		private static void StripEquipmentGameplay(GameObject go)
		{
			int num = 0;
			Item[] componentsInChildren = go.GetComponentsInChildren<Item>(true);
			foreach (Item val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					try
					{
						Object.DestroyImmediate((Object)(object)val);
						num++;
					}
					catch
					{
					}
				}
			}
			if (num > 0)
			{
				CompanionRuntime.Log.LogMessage((object)$"[CLONE] humanoid equipment strip: {num} Item component(s) removed (GameObjects kept).");
			}
		}

		private static void StripHumanoidMachinery(GameObject go)
		{
			List<string> list = new List<string>();
			Component[] componentsInChildren = go.GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && HumanoidStrip.Contains(((object)val).GetType().Name))
				{
					list.Add(((object)val).GetType().Name);
					try
					{
						Object.DestroyImmediate((Object)(object)val);
					}
					catch
					{
					}
				}
			}
			CompanionRuntime.Log.LogMessage((object)((list.Count > 0) ? ("[CLONE] humanoid machinery strip: " + string.Join(", ", list.ToArray()) + ".") : "[CLONE] humanoid machinery strip: nothing matched (clean NPC)."));
		}

		private static void StripBrain(GameObject go, EquipmentStripMode? humanoidEquipStrip = null)
		{
			CharacterAI component = go.GetComponent<CharacterAI>();
			if ((Object)(object)component != (Object)null)
			{
				try
				{
					component.m_aiStatesRoot = null;
				}
				catch
				{
				}
				Object.Destroy((Object)(object)component);
			}
			DestroyIfPresent<AISquadMember>(go);
			DestroyIfPresent<PhotonView>(go);
			Rigidbody component2 = go.GetComponent<Rigidbody>();
			if ((Object)(object)component2 != (Object)null)
			{
				Object.Destroy((Object)(object)component2);
			}
			if (humanoidEquipStrip == EquipmentStripMode.Components)
			{
				StripEquipmentGameplay(go);
			}
			else
			{
				StripEquippedItems(go);
			}
			if (humanoidEquipStrip.HasValue)
			{
				StripHumanoidMachinery(go);
			}
			Character component3 = go.GetComponent<Character>();
			if ((Object)(object)component3 != (Object)null)
			{
				((Behaviour)component3).enabled = false;
			}
			CharacterController component4 = go.GetComponent<CharacterController>();
			if ((Object)(object)component4 != (Object)null)
			{
				((Collider)component4).enabled = false;
			}
			AdvancedMover component5 = go.GetComponent<AdvancedMover>();
			if ((Object)(object)component5 != (Object)null)
			{
				((Behaviour)component5).enabled = false;
			}
			RigidbodySuspender component6 = go.GetComponent<RigidbodySuspender>();
			if ((Object)(object)component6 != (Object)null)
			{
				((Behaviour)component6).enabled = false;
			}
			Collider[] componentsInChildren = go.GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren)
			{
				val.enabled = false;
			}
			Component[] componentsInChildren2 = go.GetComponentsInChildren<Component>(true);
			foreach (Component val2 in componentsInChildren2)
			{
				if (!((Object)(object)val2 == (Object)null) && GameplayStrip.Contains(((object)val2).GetType().Name))
				{
					try
					{
						Object.DestroyImmediate((Object)(object)val2);
					}
					catch
					{
					}
				}
			}
		}

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

		private static void SweepHolderOrphans()
		{
			if (!((Object)(object)_holder == (Object)null))
			{
				Transform transform = _holder.transform;
				for (int num = transform.childCount - 1; num >= 0; num--)
				{
					GameObject gameObject = ((Component)transform.GetChild(num)).gameObject;
					CompanionRuntime.Log.LogWarning((object)("[BODYFACTORY] destroyed stranded clone after failed build: holder orphan '" + ((Object)gameObject).name + "'"));
					Object.Destroy((Object)(object)gameObject);
				}
			}
		}

		private static void DestroyStranded(GameObject go, string what)
		{
			if (!((Object)(object)go == (Object)null))
			{
				CompanionRuntime.Log.LogWarning((object)("[BODYFACTORY] destroyed stranded clone after failed build: " + what + " '" + ((Object)go).name + "'"));
				Object.Destroy((Object)(object)go);
			}
		}

		private static void DestroyIfPresent<T>(GameObject go) where T : Component
		{
			T component = go.GetComponent<T>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
		}

		private static void DestroyImmediateIfPresent<T>(GameObject go) where T : Component
		{
			T component = go.GetComponent<T>();
			if ((Object)(object)component != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component);
			}
		}
	}
	public sealed class BodyTemplate
	{
		public GameObject Dormant;

		public string Key;

		public string SpeciesId;

		public CreatureAttributes Captured;

		public bool Substituted;
	}
	public static class BodyTemplateCache
	{
		private static readonly Dictionary<string, BodyTemplate> _cache = new Dictionary<string, BodyTemplate>(StringComparer.OrdinalIgnoreCase);

		private const int CacheWarnThreshold = 24;

		private static GameObject _holder;

		private static readonly HashSet<string> _warnedSubs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		public static int Count
		{
			get
			{
				Prune();
				return _cache.Count;
			}
		}

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

		public static bool TryResolve(string speciesId, out BodyTemplate template)
		{
			Prune();
			template = null;
			if (string.IsNullOrEmpty(speciesId))
			{
				return false;
			}
			string text = default(string);
			if (!SpeciesTable.TryResolveKey<BodyTemplate>(_cache, speciesId, ref text, ref template, (string)null) || template == null)
			{
				return false;
			}
			if (!StatAdoption.IsSameSpecies(speciesId, template.SpeciesId))
			{
				WarnSubstitutionOnce(speciesId, template.SpeciesId);
			}
			return true;
		}

		public static bool TryResolveExact(string speciesId, out BodyTemplate template)
		{
			Prune();
			template = null;
			if (string.IsNullOrEmpty(speciesId))
			{
				return false;
			}
			if (!_cache.TryGetValue(speciesId.Trim(), out template) || template == null)
			{
				return false;
			}
			if (template.Substituted)
			{
				WarnSubstitutionOnce(speciesId, template.SpeciesId);
				template = null;
				return false;
			}
			return true;
		}

		private static void WarnSubstitutionOnce(string asked, string got)
		{
			if (_warnedSubs.Add(asked + "→" + got))
			{
				CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] SUBSTITUTION: asked for '" + asked + "', but the cached body is a '" + got + "' — NOT the same creature (different stats, possibly a different model; e.g. Elite Gargoyle Mage 1700hp vs Gargoyle 750hp). A PET may still wear it as a fallback body, but it will never be recorded as its identity (Bug 31). A SPAWN refuses it outright. If '" + asked + "' matters, it needs its own donor row + a capture; see Bug 28."));
			}
		}

		public static BodyTemplate Capture(Character src, string key)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)src == (Object)null || string.IsNullOrEmpty(key))
			{
				return null;
			}
			GameObject val = null;
			try
			{
				val = Object.Instantiate<GameObject>(((Component)src).gameObject, Holder().transform);
				((Object)val).name = "CK_BodyTemplate_" + key;
				val.transform.localPosition = Vector3.zero;
				PhotonView component = val.GetComponent<PhotonView>();
				if ((Object)(object)component != (Object)null)
				{
					component.viewID = 0;
				}
				AISquadMember[] componentsInChildren = val.GetComponentsInChildren<AISquadMember>(true);
				foreach (AISquadMember val2 in componentsInChildren)
				{
					if ((Object)(object)val2 != (Object)null && (Object)(object)val2.AISquad != (Object)null)
					{
						val2.AISquad = null;
					}
				}
				string text = RagdollRig.RestoreJoints(((Component)src).gameObject, val, delegate(string w)
				{
					CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] ragdoll ('" + key + "'): " + w));
				});
				if (text != null)
				{
					CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] ragdoll ('" + key + "'): " + text + "."));
				}
				RigCheck(((Component)src).gameObject, val, key);
				string text2 = DonorHarvest.IdentityFor(key, src);
				BodyTemplate bodyTemplate = new BodyTemplate
				{
					Dormant = val,
					Key = key,
					SpeciesId = text2,
					Captured = AttributeCapture.From(src),
					Substituted = !StatAdoption.IsSameSpecies(key, text2)
				};
				if (_cache.TryGetValue(key, out var value) && (Object)(object)value?.Dormant != (Object)null && (Object)(object)value.Dormant != (Object)(object)val)
				{
					Object.Destroy((Object)(object)value.Dormant);
				}
				_cache[key] = bodyTemplate;
				CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] cached body template '" + key + "' (species '" + bodyTemplate.SpeciesId + "'" + (bodyTemplate.Substituted ? (" — SUBSTITUTE: this is NOT a '" + key + "'") : "") + ", stats " + ((bodyTemplate.Captured != null) ? "captured" : "UNREADABLE — config fallback at build") + ")."));
				if (bodyTemplate.Substituted)
				{
					CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] '" + key + "' was captured from a '" + text2 + "' donor — the two are different creatures. A spawn of this key will now REFUSE the cache and harvest honestly; a pet may still wear it as a fallback body but will never record it as its identity (Bug 31). Fix: give '" + key + "' its own donor row, or pin it (Bug 28)."));
				}
				if (_cache.Count > 0 && _cache.Count % 24 == 0)
				{
					CompanionRuntime.Log.LogWarning((object)($"[TEMPLATE] cache now holds {_cache.Count} resident body template(s) — " + "each pins its mesh/texture assets in memory. 'templateclear' frees them (safe when no re-form is mid-build)."));
				}
				return bodyTemplate;
			}
			catch (Exception ex)
			{
				CompanionRuntime.Log.LogError((object)("[TEMPLATE] capture of '" + key + "' threw — destroying the orphan clone: " + ex.Message));
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
				return null;
			}
		}

		private static void RigCheck(GameObject donor, GameObject clone, string key)
		{
			Action<string> warn = delegate(string w)
			{
				CompanionRuntime.Log.LogWarning((object)("[RIG] ('" + key + "') " + w));
			};
			SkeletonRig.Report report = SkeletonRig.Audit(donor, clone, warn);
			CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] rig ('" + key + "'): " + report.Summary + "."));
			if (report.Healthy)
			{
				return;
			}
			SkeletonRig.LogForensics(report, donor, warn);
			if (!SkeletonRig.RepairEnabled)
			{
				CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] rig ('" + key + "'): external references present and [Rig] RepairSkinnedBones=false (log-only) — puppets from this template will mis-render."));
				return;
			}
			string text = SkeletonRig.Repair(donor, clone, report, warn);
			SkeletonRig.Report report2 = SkeletonRig.Audit(null, clone, warn);
			CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] rig repair ('" + key + "'): " + text + " — post: " + report2.Summary + "."));
			if (!report2.Healthy)
			{
				CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] rig ('" + key + "'): still unhealthy after the repair pass — keeping the template anyway (no chain to advance here; a mis-skinned body beats none)."));
			}
		}

		public static CompanionBody PuppetFrom(BodyTemplate template, Character player, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0)
		{
			if ((Object)(object)template?.Dormant == (Object)null || (Object)(object)player == (Object)null)
			{
				return null;
			}
			try
			{
				Character component = template.Dormant.GetComponent<Character>();
				if ((Object)(object)component == (Object)null)
				{
					CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] '" + template.Key + "' has no Character component — dropping it."));
					Drop(template.Key);
					return null;
				}
				CompanionBody companionBody = BodyFactory.BuildPuppet(component, player, consume: false, rangedProjectileFilter, rangedSkillPrefabId, template.Captured);
				if ((Object)(object)companionBody != (Object)null)
				{
					if (!string.IsNullOrEmpty(template.SpeciesId))
					{
						companionBody.SpeciesId = template.SpeciesId;
					}
					if (companionBody.CapturedStats == null)
					{
						companionBody.CapturedStats = template.Captured;
					}
					CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] built puppet from cached '" + template.Key + "' — no scene load needed."));
				}
				return companionBody;
			}
			catch (Exception ex)
			{
				CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] puppet build from '" + template.Key + "' failed: " + ex.Message));
				return null;
			}
		}

		public static int ClearAllAndForgetMisses()
		{
			int result = Clear();
			ExpeditionOrchestrator.ForgetMisses();
			_warnedSubs.Clear();
			return result;
		}

		public static int Clear()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			_cache.Clear();
			if ((Object)(object)_holder == (Object)null)
			{
				return 0;
			}
			List<GameObject> list = new List<GameObject>();
			foreach (Transform item in _holder.transform)
			{
				Transform val = item;
				list.Add(((Component)val).gameObject);
			}
			foreach (GameObject item2 in list)
			{
				if ((Object)(object)item2 != (Object)null)
				{
					Object.Destroy((Object)(object)item2);
				}
			}
			return list.Count;
		}

		public static string Dump()
		{
			Prune();
			if (_cache.Count == 0)
			{
				return "[TEMPLATE] body-template cache empty.";
			}
			StringBuilder stringBuilder = new StringBuilder($"[TEMPLATE] {_cache.Count} cached body template(s):");
			foreach (KeyValuePair<string, BodyTemplate> item in _cache)
			{
				stringBuilder.Append("\n  '" + item.Key + "' -> species '" + item.Value.SpeciesId + "' stats=" + ((item.Value.Captured != null) ? "captured" : "none") + (item.Value.Substituted ? ("  *** SUBSTITUTE — NOT a '" + item.Key + "' (spawns refuse it; Bug 31) ***") : ""));
			}
			return stringBuilder.ToString();
		}

		public static string Probe()
		{
			Prune();
			if (_cache.Count == 0)
			{
				return "[TEMPLATE] body-template cache empty — nothing to probe.";
			}
			StringBuilder stringBuilder = new StringBuilder($"[TEMPLATE] mesh probe over {_cache.Count} cached template(s):");
			foreach (KeyValuePair<string, BodyTemplate> item in _cache)
			{
				string text;
				try
				{
					SkinnedMeshRenderer val = (((Object)(object)item.Value.Dormant != (Object)null) ? item.Value.Dormant.GetComponentInChildren<SkinnedMeshRenderer>(true) : null);
					text = (((Object)(object)val == (Object)null) ? "no SkinnedMeshRenderer" : (((Object)(object)val.sharedMesh == (Object)null) ? "SkinnedMeshRenderer with no sharedMesh" : $"mesh '{((Object)val.sharedMesh).name}' isReadable={val.sharedMesh.isReadable}"));
				}
				catch (Exception ex)
				{
					text = "probe threw: " + ex.GetType().Name + ": " + ex.Message;
				}
				stringBuilder.Append("\n  '" + item.Key + "' (species '" + item.Value.SpeciesId + "') -> " + text);
			}
			return stringBuilder.ToString();
		}

		private static void Drop(string key)
		{
			_cache.Remove(key);
		}

		private static void Prune()
		{
			List<string> list = null;
			foreach (KeyValuePair<string, BodyTemplate> item in _cache)
			{
				if ((Object)(object)item.Value?.Dormant == (Object)null)
				{
					List<string> obj = list ?? new List<string>();
					list = obj;
					obj.Add(item.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (string item2 in list)
			{
				_cache.Remove(item2);
				CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] cached body template '" + item2 + "' was destroyed — dropped from the cache."));
			}
		}
	}
	[HarmonyPatch(typeof(CharacterManager), "CharacterHasBeenDestroyed")]
	internal static class CharacterRegistryGuard
	{
		private static readonly HashSet<string> _warnedUids = new HashSet<string>();

		private static int _guarded;

		[HarmonyPrefix]
		private static bool Prefix(CharacterManager __instance, Character _character)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)__instance == (Object)null || _character == null)
				{
					return true;
				}
				string text = UID.op_Implicit(_character.UID);
				if (string.IsNullOrEmpty(text))
				{
					return true;
				}
				if (!__instance.m_characters.ContainsKey(text))
				{
					return true;
				}
				Character val = __instance.m_characters[text];
				if (val == _character)
				{
					return true;
				}
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				__instance.m_characterToInitialized.Remove(_character);
				_guarded++;
				if (_warnedUids.Add(text))
				{
					ManualLogSource log = CompanionRuntime.Log;
					if (log != null)
					{
						log.LogWarning((object)$"[CHAR-GUARD] guarded m_characters eviction: destroyed copy of '{text}' (go='{SafeGoName(_character)}') is not the registered instance ('{SafeGoName(val)}') — the live NPC stays registered (donor-unload asymmetry, DonorPhotonGuard's registry sibling; {_guarded} guarded total, further hits for this UID silent).");
					}
				}
				return false;
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = CompanionRuntime.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[CHAR-GUARD] prefix failed (" + ex.Message + ") — falling through to vanilla."));
				}
				return true;
			}
		}

		private static string SafeGoName(Character c)
		{
			try
			{
				return ((Object)(object)c != (Object)null && (Object)(object)((Component)c).gameObject != (Object)null) ? ((Object)((Component)c).gameObject).name : "?";
			}
			catch
			{
				return "?";
			}
		}
	}
	public static class CkConfig
	{
		public static class Rig
		{
			public static ConfigEntry<bool> RepairSkinnedBones;

			internal static void Bind(ConfigFile cfg)
			{
				RepairSkinnedBones = cfg.Bind<bool>("Rig", "RepairSkinnedBones", true, "Repair SkinnedMeshRenderer bones that reference transforms OUTSIDE the harvested creature's own hierarchy (they die with the donor scene and the spawn renders as a stretched vertical line — finding F2, Elder Medyse). Runs at template capture while the donor is still loaded: rebind to a same-named internal bone where one exists, else graft the foreign skeleton subtree under the clone. false = log-only (the '[TEMPLATE] rig' audit line still fires).");
			}
		}

		public static class Expedition
		{
			public static ConfigEntry<bool> CaptureOnSceneEntry;

			public static ConfigEntry<string> AutoWarmAtBoot;

			public static ConfigEntry<string> AlwaysWarmSpecies;

			public static ConfigEntry<float> AutoWarmRetrySeconds;

			public static ConfigEntry<float> ReturnRetrySeconds;

			internal static void Bind(ConfigFile cfg)
			{
				CaptureOnSceneEntry = cfg.Bind<bool>("Expedition", "CaptureOnSceneEntry", false, "When you enter an OVERSIZED scene (region terrain/town — the expedition tier) that still has uncached donor-table species, run the expedition capture payload IN PLACE at player-ready (the scene is already loaded, so it costs zero loading screens) and record the scene in ck_expeditions.txt. Never fires for ordinary scenes (one string check); a fully cached region costs one key sweep. DEFAULT OFF as of the public release: this is the only path on which the kit does heavy work the player never asked for (it clones every uncached donor species in the region on EVERY region/town entry), and it is the suspected source of the un-root-caused 'mystery live Pearlbird' (a live donor engaging the player in combat ~1.2 km away — spawnkit-testplan.md watch-for). Turn it on to pay expedition costs opportunistically instead of via an explicit 'expedition <species>'.");
				AutoWarmAtBoot = cfg.Bind<string>("Expedition", "AutoWarmAtBoot", "needed", "One-time template-cache warm at the FIRST gameplay player-ready of each launch. 'needed' = only when the active companion's species has expedition-tier donors, NO additive donor, and no cached template — one hands-off round trip to its first candidate (a species with any wild/additive source never triggers anything). 'all' = re-dump every ck_expeditions.txt scene that still has >=1 uncached species (sequential trips). 'off' = never. Unknown values warn and behave as off. AlwaysWarmSpecies is evaluated on top of this, ungated by the mode.");
				AlwaysWarmSpecies = cfg.Bind<string>("Expedition", "AlwaysWarmSpecies", "", "Comma-separated species warmed at the same once-per-launch boot evaluation REGARDLESS of AutoWarmAtBoot (the release-packaging knob — e.g. 'Pearlbird', whose only donor scene is expedition-tier, ships listed here so a body is waiting before its first re-form). Each listed species with expedition-tier donors and no cached template gets its first candidate scene warmed; species sharing a scene share ONE trip (deduped against the mode's trips too); a species with an additive donor is skipped with a notice (the normal harvest chain covers it); an unrecognized name warns loudly.");
				AutoWarmRetrySeconds = cfg.Bind<float>("Expedition", "AutoWarmRetrySeconds", 60f, "The boot auto-warm can fire while a vanilla area load or a companion donor harvest is still in flight, and an expedition trip can't START then. Rather than abandoning the whole launch's warm pass, the auto-warm polls (once a second, unscaled) until the loader settles, then re-attempts — for at most this many seconds. 0 = never wait (blocked at boot = give up, the pre-fix behavior); the once-per-launch latch always means ONE pass (a settled launch OR an exhausted wait), never one attempt.");
				ReturnRetrySeconds = cfg.Bind<float>("Expedition", "ReturnRetrySeconds", 20f, "Safety net for the expedition's RETURN leg (Bug 25 — an expedition that fails to come home leaves you in the donor region, and the game bakes that into the save). RequestSwitchArea has no failure signal, so if the return was asked for and this many seconds later no load has started and we are still standing in the donor scene, the return is re-requested (twice at most, then the watchdog makes one final rescue attempt). This measures request → load-START, not load duration, so a healthy 15-60s region load never trips it. 0 = never retry (not recommended; the watchdog rescue still applies).");
			}
		}
	}
	public class Companion
	{
		public CompanionBody Body;

		public CompanionAnchor Anchor;

		public CommandStance Stance;

		private readonly ICompanionSettings _settings;

		private readonly OwnerRef _owner;

		private ICompanionSettings Cfg => _settings ?? CompanionRuntime.DefaultSettings;

		public Companion(ICompanionSettings settings = null)
		{
			_settings = settings;
			_owner = new OwnerRef();
			Anchor = new CompanionAnchor(settings);
			Stance = new CommandStance();
		}

		public CompanionCombat AdoptBody(CompanionBody body, bool enableCombat)
		{
			if ((Object)(object)body == (Object)null)
			{
				return null;
			}
			Body = body;
			body.Owner = _owner.Get;
			if (body.Settings == null)
			{
				body.Settings = _settings;
			}
			CompanionCombat companionCombat = null;
			if (enableCombat)
			{
				companionCombat = ((Component)body).gameObject.AddComponent<CompanionCombat>();
				companionCombat.Anchor = Anchor;
				companionCombat.Stance = Stance;
				companionCombat.Settings = _settings;
				companionCombat.Owner = _owner.Get;
			}
			Anchor.AttachBody(body);
			return companionCombat;
		}

		public void Tick(Character player, MonoBehaviour host)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Invalid comparison between Unknown and I4
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)Body == (Object)null))
			{
				bool bodyFighting = (Object)(object)Body.CombatTarget != (Object)null;
				Anchor.Upkeep(player, host, bodyFighting);
				Anchor.ApplyVoice(Body);
				Transform val = (Anchor.HasLiveAnchor ? ((Component)Anchor.Current).transform : null);
				Transform val2 = (((int)Cfg.GlueMode == 2) ? null : val);
				if ((Object)(object)Body.FollowOverride != (Object)(object)val2)
				{
					Body.FollowOverride = val2;
				}
				if ((int)Cfg.GlueMode == 0 && (Object)(object)Body.CombatTarget != (Object)null)
				{
					Anchor.PinTo(((Component)Body).transform.position);
				}
			}
		}

		public void Despawn()
		{
			Anchor.DetachBody();
			Anchor.DestroyCurrent();
			Stance.Reset();
		}
	}
	public sealed class CommandStance
	{
		private Character _commanded;

		public bool Passive { get; private set; }

		public Character CommandedTarget
		{
			get
			{
				if ((Object)(object)_commanded != (Object)null && _commanded.Alive)
				{
					return _commanded;
				}
				_commanded = null;
				return null;
			}
		}

		public void CommandEngage(Character target)
		{
			Passive = false;
			_commanded = target;
		}

		public void CommandDisengage()
		{
			Passive = true;
			_commanded = null;
		}

		public void Reset()
		{
			Passive = false;
			_commanded = null;
		}

		public void DropCommanded()
		{
			_commanded = null;
		}
	}
	public sealed class OwnerRef
	{
		private readonly Func<Character> _resolve;

		private Character _cached;

		public OwnerRef(Func<Character> resolve = null)
		{
			_resolve = resolve ?? ((Func<Character>)delegate
			{
				CharacterManager instance = CharacterManager.Instance;
				return (instance == null) ? null : instance.GetFirstLocalCharacter();
			});
		}

		public Character Get()
		{
			if ((Object)(object)_cached == (Object)null)
			{
				_cached = _resolve();
			}
			return _cached;
		}
	}
	public sealed class CompanionAnchor
	{
		private const int InstantiationTypeNetwork = 3;

		private readonly ICompanionSettings _cfg;

		private readonly AnchorStats _stats;

		private readonly AnchorDressing _dressing;

		private readonly AnchorPhysics _physics;

		public Action OnAnchorDeath;

		public Action OnAnchorCriticallyHurt;

		private float _deadUntil;

		private bool _critFired;

		private bool _wasInCombat;

		private float _leashReconAt;

		private CharacterAI _ai;

		private bool _glueWasEngaged;

		private bool _agentWarpNoted;

		private float _glueJumpLog;

		private Character _assertedLock;

		private float _unifyLogAt;

		private Character _unifySkipLogged;

		private float _lastHp;

		private float _hpDriftLogAt;

		private CompanionBody _body;

		private ICompanionSettings Cfg => _cfg ?? CompanionRuntime.DefaultSettings;

		private string TagAnchor => CompanionRuntime.Tag("ANCHOR", Cfg);

		private string TagGlue => CompanionRuntime.Tag("GLUE", Cfg);

		public Character Current { get; private set; }

		public bool HasLiveAnchor
		{
			get
			{
				if ((Object)(object)Current != (Object)null)
				{
					return Current.Alive;
				}
				return false;
			}
		}

		public CharacterAI AI
		{
			get
			{
				if (!((Object)(object)_ai != (Object)null))
				{
					return _ai = (((Object)(object)Current != (Object)null) ? ((Component)Current).GetComponent<CharacterAI>() : null);
				}
				return _ai;
			}
		}

		public Character LastAssertedTarget => _assertedLock;

		public CompanionAnchor(ICompanionSettings cfg = null)
		{
			_cfg = cfg;
			_stats = new AnchorStats(() => Current, () => Cfg);
			_dressing = new AnchorDressing(() => Current, () => Cfg);
			_physics = new AnchorPhysics(() => Current, () => Cfg);
		}

		public void AttachBody(CompanionBody body)
		{
			if (_body != body)
			{
				DetachBody();
				_body = body;
				if ((Object)(object)body != (Object)null)
				{
					body.OnAfterMove += GlueTick;
					body.CompanionRef = AnchorTransformOrNull;
				}
			}
		}

		public void DetachBody()
		{
			if (!((Object)(object)_body == (Object)null))
			{
				_body.OnAfterMove -= GlueTick;
				if