Decompiled source of CompanionKit v0.4.20

plugins/CompanionKit.Core.dll

Decompiled 6 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("0.4.20.0")]
[assembly: AssemblyInformationalVersion("0.4.20+091b206910305beb491301afe1db01b8cd7b8e72")]
[assembly: AssemblyProduct("CompanionKit.Core")]
[assembly: AssemblyTitle("CompanionKit.Core")]
[assembly: AssemblyMetadata("BuildStamp", "091b2069 2026-08-28")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CompanionKit.Core
{
	public static class AgentFit
	{
		public const float MaxBaseOffset = 3f;

		public static float BaseOffset(float pivotY, float boundsMinY)
		{
			float num = pivotY - boundsMinY;
			if (float.IsNaN(num) || num < 0f)
			{
				return 0f;
			}
			if (!(num > 3f))
			{
				return num;
			}
			return 3f;
		}
	}
	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 bool LeashApplies(AnchorGlueMode mode, bool hasBody, bool inCombat, bool externallyDriven)
		{
			if (!externallyDriven)
			{
				return !Engaged(mode, hasBody, inCombat);
			}
			return 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 struct AnchorReplicaPlan
	{
		public bool LocalOwner;

		public bool NeutralizeLifetime;

		public bool RetuneNetConvergence;

		public bool HideRenderers;

		public bool MuteAudio;

		public bool NeuterWeapon;

		public bool KillHealthBar;

		public bool ExemptPlayerCollision;

		public string LogScope;
	}
	public static class AnchorReplicaPolicy
	{
		public const string ForeignScope = "foreign anchor";

		public const string OwnScope = "own-pet anchor replica";

		public static AnchorReplicaPlan For(bool localOwner)
		{
			return new AnchorReplicaPlan
			{
				LocalOwner = localOwner,
				NeutralizeLifetime = true,
				RetuneNetConvergence = true,
				HideRenderers = true,
				MuteAudio = true,
				NeuterWeapon = true,
				KillHealthBar = true,
				ExemptPlayerCollision = true,
				LogScope = (localOwner ? "own-pet anchor replica" : "foreign anchor")
			};
		}
	}
	public static class AnchorVitals
	{
		public static float RestoreHealth(double frac, double max)
		{
			if (double.IsNaN(frac) || double.IsInfinity(frac))
			{
				frac = 1.0;
			}
			if (!(max > 0.0) || double.IsInfinity(max))
			{
				return 0f;
			}
			if (frac >= 1.0)
			{
				return (float)max;
			}
			double num = Math.Round(((frac < 0.0) ? 0.0 : frac) * max);
			if (num < 1.0)
			{
				num = 1.0;
			}
			if (num > max)
			{
				num = max;
			}
			return (float)num;
		}
	}
	public static class AnimParamLatch
	{
		public static bool IsAnswer(int parameterCount)
		{
			return parameterCount > 0;
		}

		public static bool ShouldProbe(bool latched, int parameterCount)
		{
			if (!latched)
			{
				return IsAnswer(parameterCount);
			}
			return false;
		}

		public static bool Latch(bool latched, int parameterCount)
		{
			if (!latched)
			{
				return IsAnswer(parameterCount);
			}
			return true;
		}

		public static bool ShouldLatchCensus(bool found, bool allAnswered)
		{
			return found || allAnswered;
		}
	}
	public enum AuraCaptureState
	{
		NotNeeded,
		Pending,
		InFlight,
		Captured,
		Failed
	}
	public static class AuraBook
	{
		public const int SlotBase = 100;

		public const string KeyPrefix = "aura.";

		public static bool IsAuraKey(string key)
		{
			if (!string.IsNullOrEmpty(key) && key.StartsWith("aura.", StringComparison.OrdinalIgnoreCase))
			{
				return key.Length > "aura.".Length;
			}
			return false;
		}

		public static string ValidateRegistration(string key, int slot, IEnumerable<string> existingKeys, IEnumerable<int> existingSlots)
		{
			if (!IsAuraKey(key))
			{
				return "aura key '" + (key ?? "<null>") + "' is not legal — keys must be 'aura.<name>'.";
			}
			if (slot < 100)
			{
				return $"aura '{key}' slot {slot} is below the reserved aura band (must be >= {100} — " + "slots below it belong to consumer spell tables).";
			}
			if (existingKeys != null)
			{
				foreach (string existingKey in existingKeys)
				{
					if (string.Equals(existingKey, key, StringComparison.OrdinalIgnoreCase))
					{
						return "aura key '" + key + "' is already registered.";
					}
				}
			}
			if (existingSlots != null)
			{
				foreach (int existingSlot in existingSlots)
				{
					if (existingSlot == slot)
					{
						return $"aura '{key}' slot {slot} is already taken by another aura.";
					}
				}
			}
			return null;
		}

		public static List<string> FilterCandidates(string filter)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrEmpty(filter))
			{
				return list;
			}
			string[] array = filter.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					list.Add(text);
				}
			}
			return list;
		}

		public static bool MatchesFilter(string nodeName, IReadOnlyList<string> candidates)
		{
			if (string.IsNullOrEmpty(nodeName) || candidates == null)
			{
				return false;
			}
			for (int i = 0; i < candidates.Count; i++)
			{
				if (nodeName.IndexOf(candidates[i], StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		public static int SelectCaptureDepth(IReadOnlyList<string> leafToRootNames, IReadOnlyList<string> candidates)
		{
			if (leafToRootNames == null)
			{
				return -1;
			}
			for (int i = 0; i < leafToRootNames.Count; i++)
			{
				if (MatchesFilter(leafToRootNames[i], candidates))
				{
					return i;
				}
			}
			return -1;
		}

		public static string CaptureKey(string speciesKey, string filter)
		{
			return (speciesKey ?? "").Trim().ToLowerInvariant() + "|" + (filter ?? "").Trim().ToLowerInvariant();
		}
	}
	public static class BodyFxDescribe
	{
		public const string NoRenderer = "NONE";

		private static string Or(string s, string fallback)
		{
			if (!string.IsNullOrEmpty(s))
			{
				return s;
			}
			return fallback;
		}

		public static string Played(string spellKey, string verb, string target, string source)
		{
			return "played '" + Or(spellKey, "?") + "' via " + Or(verb, "?") + " on " + Or(target, "?") + " src=" + Or(source, "?");
		}

		public static string Binding(string componentType, string node, string rendererName)
		{
			return Or(componentType, "?") + "@" + Or(node, "?") + " -> renderer=" + Or(rendererName, "NONE");
		}

		public static string Bindings(IEnumerable<string> fragments)
		{
			if (fragments == null)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (string fragment in fragments)
			{
				if (!string.IsNullOrEmpty(fragment))
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(fragment);
				}
			}
			return stringBuilder.ToString();
		}
	}
	public static class BodyFxMath
	{
		public const float MinSeconds = 0.5f;

		public const float MaxSeconds = 10f;

		public const float EncaseAuthoredHeight = 2f;

		public const float EncaseMinFactor = 0.3f;

		public const float EncaseMaxFactor = 4f;

		public const float TopYFloor = 0.2f;

		public const float TopYDefault = 1.5f;

		public static float ClampSeconds(float seconds)
		{
			if (!(seconds > 0.5f))
			{
				return 0.5f;
			}
			if (!(seconds > 10f))
			{
				return seconds;
			}
			return 10f;
		}

		public static float EncaseScale(float sizeX, float sizeY, float sizeZ, float userScale)
		{
			float num = Math.Max(SaneDim(sizeX), Math.Max(SaneDim(sizeY), SaneDim(sizeZ)));
			float num2 = ((userScale > 0f) ? userScale : 1f);
			float num3 = num / 2f * num2;
			if (!(num3 > 0.3f))
			{
				return 0.3f;
			}
			if (!(num3 > 4f))
			{
				return num3;
			}
			return 4f;
		}

		public static bool FlattenForward(float fx, float fz, out float nx, out float nz)
		{
			if (!float.IsNaN(fx) && !float.IsNaN(fz) && EffigyPinMath.HorizontalFacing(fx, fz, out nx, out nz))
			{
				return true;
			}
			nx = 0f;
			nz = 1f;
			return false;
		}

		public static float TopY(float boundsMaxY, float bodyY)
		{
			float num = boundsMaxY - bodyY;
			if (float.IsNaN(num) || float.IsInfinity(num))
			{
				return 1.5f;
			}
			if (!(num < 0.2f))
			{
				return num;
			}
			return 0.2f;
		}

		public static string Signature(int source, string statusName, int skillItemId, int attach, float offX, float offY, float offZ, float scale, float seconds, int soundId)
		{
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			return string.Join("|", source.ToString(invariantCulture), statusName ?? "", skillItemId.ToString(invariantCulture), attach.ToString(invariantCulture), offX.ToString("G9", invariantCulture), offY.ToString("G9", invariantCulture), offZ.ToString("G9", invariantCulture), scale.ToString("G9", invariantCulture), seconds.ToString("G9", invariantCulture), soundId.ToString(invariantCulture));
		}

		public static string Signature(int source, string statusName, int skillItemId, int attach, float offX, float offY, float offZ, float scale, float seconds, int soundId, string speciesKey, string subtreeFilter)
		{
			string text = Signature(source, statusName, skillItemId, attach, offX, offY, offZ, scale, seconds, soundId);
			if (string.IsNullOrEmpty(speciesKey) && string.IsNullOrEmpty(subtreeFilter))
			{
				return text;
			}
			return text + "|" + speciesKey + "|" + subtreeFilter;
		}

		private static float SaneDim(float v)
		{
			if (float.IsNaN(v) || float.IsInfinity(v) || v < 0f)
			{
				return 0f;
			}
			return v;
		}

		public static bool SelfPlayNeedsRebind(bool rendererResolved, bool bodySmrAvailable)
		{
			return !rendererResolved && bodySmrAvailable;
		}
	}
	public enum ReapVerdict
	{
		Keep,
		Warn,
		Reap
	}
	public static class BodyReaperPolicy
	{
		public const float DefaultGraceSeconds = 90f;

		public const float DefaultCondemnSeconds = 20f;

		public static ReapVerdict Decide(float ageSeconds, bool claimedNow, float unclaimedForSeconds, bool reapEnabled, float graceSeconds = 90f, float condemnSeconds = 20f, bool everClaimed = true, bool warnedAlready = false)
		{
			if (claimedNow)
			{
				return ReapVerdict.Keep;
			}
			if (ageSeconds < graceSeconds)
			{
				return ReapVerdict.Keep;
			}
			if (unclaimedForSeconds < condemnSeconds)
			{
				return ReapVerdict.Warn;
			}
			if (!everClaimed && !warnedAlready)
			{
				return ReapVerdict.Warn;
			}
			if (!reapEnabled)
			{
				return ReapVerdict.Warn;
			}
			return ReapVerdict.Reap;
		}
	}
	public enum CombatStyle
	{
		Chase,
		Opposite
	}
	public sealed class CombatStationTuning
	{
		public float RingFraction = 0.6f;

		public float LineAngleDeg = 20f;

		public float RestationMeters = 3f;

		public float RestationSeconds = 2f;

		public float ArriveMeters = 0.8f;

		public int MaxRestations = 6;

		public float FarMeters = 3f;

		public float ProgressMeters = 1f;

		public float EnemyFastMetersPerSecond;
	}
	public struct CombatStationState
	{
		public bool Has;

		public float X;

		public float Z;

		public float EnemyAtX;

		public float EnemyAtZ;

		public float LastAt;

		public int Restations;

		public int Reaches;

		public bool CapFallback;

		public string LastWhy;

		public float LastStationDist;

		public int Stalls;

		public int FastHolds;

		public float LastFastHoldAt;
	}
	public static class CombatStation
	{
		public const string WhyFirst = "first";

		public const string WhyCorridor = "corridor";

		public const string WhyCap = "cap";

		public const string WhyNear = "near";

		public const string WhyFar = "far";

		public const string WhyReach = "reach";

		public const string WhyStalled = "stalled";

		public const float SettleSlackMeters = 0.5f;

		public const float ReachSlackMeters = 0.5f;

		public static bool Decide(ref CombatStationState s, float enemyX, float enemyZ, float ownerX, float ownerZ, float petX, float petZ, float attackRange, float now, CombatStationTuning t)
		{
			return Decide(ref s, enemyX, enemyZ, ownerX, ownerZ, petX, petZ, attackRange, now, t, float.NaN);
		}

		public static bool Decide(ref CombatStationState s, float enemyX, float enemyZ, float ownerX, float ownerZ, float petX, float petZ, float attackRange, float now, CombatStationTuning t, float enemySpeed)
		{
			if (t == null)
			{
				t = new CombatStationTuning();
			}
			if (s.CapFallback)
			{
				return false;
			}
			float num = attackRange * t.RingFraction;
			if (!s.Has)
			{
				Place(ref s, enemyX, enemyZ, ownerX, ownerZ, petX, petZ, num);
				s.Has = true;
				s.LastStationDist = Dist(s.X, s.Z, petX, petZ);
				s.Stalls = 0;
				s.Restations = 0;
				s.LastAt = now;
				s.LastWhy = "first";
				return true;
			}
			float num2 = Dist(petX, petZ, enemyX, enemyZ);
			bool flag = num2 > attackRange + t.FarMeters;
			bool num3 = InCorridor(enemyX, enemyZ, ownerX, ownerZ, petX, petZ, t.LineAngleDeg, num);
			bool num4 = num2 <= num + 0.5f;
			float num5 = Dist(enemyX, enemyZ, s.EnemyAtX, s.EnemyAtZ);
			bool flag2 = !num4 && num5 < t.RestationMeters;
			bool flag3 = Dist(petX, petZ, s.X, s.Z) <= t.ArriveMeters + 0.5f;
			bool flag4 = flag3 && num2 > attackRange + 0.5f;
			bool flag5 = num3 && !flag2;
			if (flag && t.EnemyFastMetersPerSecond > 0f && !float.IsNaN(enemySpeed) && enemySpeed > t.EnemyFastMetersPerSecond)
			{
				flag = false;
				flag4 = false;
				if (!flag5 && now - s.LastAt >= t.RestationSeconds && now - s.LastFastHoldAt >= t.RestationSeconds)
				{
					s.FastHolds++;
					s.LastFastHoldAt = now;
				}
			}
			if (!flag && !flag4 && !flag5)
			{
				return false;
			}
			string text = (flag5 ? "corridor" : (flag ? "far" : "reach"));
			if (now - s.LastAt < t.RestationSeconds)
			{
				return false;
			}
			Ideal(enemyX, enemyZ, ownerX, ownerZ, petX, petZ, num, out var x, out var z);
			if (text == "far" && !flag3)
			{
				if (Dist(x, z, petX, petZ) > s.LastStationDist - t.ProgressMeters)
				{
					s.Stalls++;
					if (s.Stalls >= 2)
					{
						s.CapFallback = true;
						s.LastWhy = "stalled";
						s.LastAt = now;
						return false;
					}
				}
				else
				{
					s.Stalls = 0;
				}
			}
			if (Dist(x, z, petX, petZ) <= t.ArriveMeters || (!flag3 && Dist(x, z, s.X, s.Z) <= t.ArriveMeters))
			{
				s.LastAt = now;
				s.LastWhy = "near";
				return false;
			}
			bool flag6 = text != "reach";
			if (flag6 && s.Restations >= t.MaxRestations)
			{
				s.CapFallback = true;
				s.LastWhy = "cap";
				s.LastAt = now;
				return false;
			}
			s.X = x;
			s.Z = z;
			s.LastStationDist = Dist(x, z, petX, petZ);
			s.EnemyAtX = enemyX;
			s.EnemyAtZ = enemyZ;
			if (flag6)
			{
				s.Restations++;
			}
			else
			{
				s.Reaches++;
			}
			s.LastAt = now;
			s.LastWhy = text;
			return true;
		}

		public static void Reset(ref CombatStationState s)
		{
			s = default(CombatStationState);
		}

		public static float CorridorAngleDeg(float enemyX, float enemyZ, float ownerX, float ownerZ, float petX, float petZ)
		{
			float num = ownerX - enemyX;
			float num2 = ownerZ - enemyZ;
			float num3 = petX - enemyX;
			float num4 = petZ - enemyZ;
			float num5 = (float)Math.Sqrt(num * num + num2 * num2);
			float num6 = (float)Math.Sqrt(num3 * num3 + num4 * num4);
			if (num5 < 0.0001f || num6 < 0.0001f)
			{
				return 180f;
			}
			float num7 = (num * num3 + num2 * num4) / (num5 * num6);
			if (num7 > 1f)
			{
				num7 = 1f;
			}
			else if (num7 < -1f)
			{
				num7 = -1f;
			}
			return (float)(Math.Acos(num7) * 180.0 / Math.PI);
		}

		public static float CorridorBlockAngleDeg(float enemyX, float enemyZ, float ownerX, float ownerZ, float petX, float petZ)
		{
			float num = enemyX - ownerX;
			float num2 = enemyZ - ownerZ;
			float num3 = petX - ownerX;
			float num4 = petZ - ownerZ;
			float num5 = (float)Math.Sqrt(num * num + num2 * num2);
			float num6 = (float)Math.Sqrt(num3 * num3 + num4 * num4);
			if (num5 < 0.0001f || num6 < 0.0001f)
			{
				return 180f;
			}
			float num7 = (num * num3 + num2 * num4) / (num5 * num6);
			if (num7 > 1f)
			{
				num7 = 1f;
			}
			else if (num7 < -1f)
			{
				num7 = -1f;
			}
			return (float)(Math.Acos(num7) * 180.0 / Math.PI);
		}

		public static bool InCorridor(float enemyX, float enemyZ, float ownerX, float ownerZ, float petX, float petZ, float lineAngleDeg, float depthMeters)
		{
			if (CorridorBlockAngleDeg(enemyX, enemyZ, ownerX, ownerZ, petX, petZ) >= lineAngleDeg)
			{
				return false;
			}
			return Dist(ownerX, ownerZ, enemyX, enemyZ) - Dist(ownerX, ownerZ, petX, petZ) > depthMeters;
		}

		private static void Place(ref CombatStationState s, float ex, float ez, float ox, float oz, float px, float pz, float ring)
		{
			Ideal(ex, ez, ox, oz, px, pz, ring, out s.X, out s.Z);
			s.EnemyAtX = ex;
			s.EnemyAtZ = ez;
		}

		private static void Ideal(float ex, float ez, float ox, float oz, float px, float pz, float ring, out float x, out float z)
		{
			float num = ex - ox;
			float num2 = ez - oz;
			float num3 = (float)Math.Sqrt(num * num + num2 * num2);
			if (num3 < 0.0001f)
			{
				num = px - ex;
				num2 = pz - ez;
				num3 = (float)Math.Sqrt(num * num + num2 * num2);
				if (num3 < 0.0001f)
				{
					num = 1f;
					num2 = 0f;
					num3 = 1f;
				}
			}
			x = ex + num / num3 * ring;
			z = ez + num2 / num3 * ring;
		}

		private static float Dist(float ax, float az, float bx, float bz)
		{
			float num = ax - bx;
			float num2 = az - bz;
			return (float)Math.Sqrt(num * num + num2 * num2);
		}
	}
	public static class CombatSweep
	{
		public static bool ShouldDrop(bool alive, float distToPlayer, float combatLeash)
		{
			if (alive)
			{
				return distToPlayer > combatLeash;
			}
			return true;
		}
	}
	public enum CombatTargetSource
	{
		None,
		Commanded,
		OwnerFocus,
		AnchorDefend,
		PlayerEngaged
	}
	public struct CombatTargetFacts
	{
		public bool StancePassive;

		public bool CommandedExists;

		public float CommandedDistance;

		public bool AssistOnOwnerHit;

		public bool OwnerFocusExists;

		public float OwnerFocusDistance;

		public float OwnerFocusRange;

		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.AssistOnOwnerHit && f.OwnerFocusExists && f.OwnerFocusDistance <= f.OwnerFocusRange)
			{
				if (f.OwnerFocusDistance > f.CombatLeashDistance)
				{
					return Clear("beyond-combat-leash (stale, dropped)");
				}
				return new CombatTargetDecision
				{
					Source = CombatTargetSource.OwnerFocus,
					Reason = "owner-focus"
				};
			}
			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 enum CommandMode
	{
		Engaged,
		Follow,
		Stay
	}
	public static class CommandCycle
	{
		public static CommandMode Next(CommandMode current)
		{
			return current switch
			{
				CommandMode.Engaged => CommandMode.Follow, 
				CommandMode.Follow => CommandMode.Stay, 
				_ => CommandMode.Engaged, 
			};
		}
	}
	public struct EquipState
	{
		public int ItemId;

		public float Durability;

		public float MaxDurability;

		public bool IsBroken => Durability <= 0f;

		public float Ratio
		{
			get
			{
				if (!(MaxDurability > 0f))
				{
					return 1f;
				}
				return Clamp01(Durability / MaxDurability);
			}
		}

		public static float Clamp01(float v)
		{
			if (!(v < 0f))
			{
				if (!(v > 1f))
				{
					return v;
				}
				return 1f;
			}
			return 0f;
		}
	}
	public static class EquipCodec
	{
		public static string Pack(string key, EquipState s, Action<string> warn = null)
		{
			if (s.ItemId == 0)
			{
				return "";
			}
			string text = (key ?? "").Trim();
			bool flag = text.Length == 0 || text.IndexOf(':') >= 0;
			if (!flag)
			{
				string text2 = text;
				for (int i = 0; i < text2.Length; i++)
				{
					if (char.IsControl(text2[i]))
					{
						flag = true;
						break;
					}
				}
			}
			if (flag)
			{
				warn?.Invoke("equipment key '" + key + "' is empty or carries ':'/control characters — stored as 'item'.");
				text = "item";
			}
			if (!Finite(s.Durability) || !Finite(s.MaxDurability))
			{
				warn?.Invoke($"equipment '{text}' has a non-finite durability ({s.Durability}/{s.MaxDurability}) — not persisted.");
				return "";
			}
			return text + ":" + s.ItemId.ToString(CultureInfo.InvariantCulture) + ":" + s.Durability.ToString("R", CultureInfo.InvariantCulture) + ":" + s.MaxDurability.ToString("R", CultureInfo.InvariantCulture);
		}

		public static bool TryParse(string cell, out string key, out EquipState state, Action<string> warn = null)
		{
			key = "";
			state = default(EquipState);
			if (string.IsNullOrEmpty(cell))
			{
				return false;
			}
			string[] array = cell.Split(new char[1] { ':' });
			if (array.Length != 4)
			{
				warn?.Invoke("malformed equipment cell '" + cell + "' (want key:itemId:dur:max) — read as nothing equipped.");
				return false;
			}
			if (!int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result <= 0)
			{
				warn?.Invoke("unreadable equipment item id '" + array[1] + "' — read as nothing equipped.");
				return false;
			}
			if (!TryFinite(array[2], out var v) || !TryFinite(array[3], out var v2) || v2 <= 0f)
			{
				warn?.Invoke("unreadable equipment durability '" + array[2] + "/" + array[3] + "' — read as nothing equipped.");
				return false;
			}
			if (v < 0f)
			{
				warn?.Invoke("negative equipment durability " + array[2] + " — clamped to 0 (broken).");
				v = 0f;
			}
			if (v > v2)
			{
				warn?.Invoke("equipment durability " + array[2] + " above max " + array[3] + " — clamped to max.");
				v = v2;
			}
			key = array[0];
			state = new EquipState
			{
				ItemId = result,
				Durability = v,
				MaxDurability = v2
			};
			return true;
		}

		private static bool Finite(float v)
		{
			if (!float.IsNaN(v))
			{
				return !float.IsInfinity(v);
			}
			return false;
		}

		private static bool TryFinite(string s, out float v)
		{
			if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v))
			{
				return Finite(v);
			}
			return false;
		}
	}
	public struct WearPolicy
	{
		public float PerDamage;

		public float MinPerHit;

		public static WearPolicy Default => new WearPolicy
		{
			PerDamage = 0.05f,
			MinPerHit = 1f
		};
	}
	public static class EquipmentWear
	{
		public static float Loss(float preMitigationTotal, WearPolicy p)
		{
			if (preMitigationTotal <= 0f || float.IsNaN(preMitigationTotal))
			{
				return 0f;
			}
			float num = preMitigationTotal * ((p.PerDamage > 0f) ? p.PerDamage : 0f);
			if (p.MinPerHit > 0f && num < p.MinPerHit)
			{
				num = p.MinPerHit;
			}
			return num;
		}

		public static EquipState AfterHit(EquipState s, float preMitigationTotal, WearPolicy p)
		{
			if (s.ItemId == 0 || s.IsBroken)
			{
				return s;
			}
			float num = s.Durability - Loss(preMitigationTotal, p);
			s.Durability = ((num < 0f) ? 0f : num);
			return s;
		}
	}
	public struct ResistGrant
	{
		public float Phys;

		public float AllOther;

		public bool IsZero
		{
			get
			{
				if (Phys == 0f)
				{
					return AllOther == 0f;
				}
				return false;
			}
		}
	}
	public static class EquipmentEffects
	{
		public static void Apply(CreatureAttributes eff, ResistGrant grant, bool broken)
		{
			if (eff == null || broken || grant.IsZero)
			{
				return;
			}
			for (int i = 0; i < eff.Resist.Length; i++)
			{
				float num = ((i == 0) ? grant.Phys : grant.AllOther);
				if (num != 0f)
				{
					float num2 = eff.Resist[i] + num;
					eff.Resist[i] = ((num2 < -100f) ? (-100f) : ((num2 > 100f) ? 100f : num2));
				}
			}
		}
	}
	public enum RepairVerdict
	{
		Ok,
		AlreadyFull,
		NoReagent,
		NothingEquipped
	}
	public static class EquipmentRepair
	{
		public static RepairVerdict Evaluate(EquipState s, int reagentHeld, int reagentCost)
		{
			if (s.ItemId == 0)
			{
				return RepairVerdict.NothingEquipped;
			}
			if (s.Durability >= s.MaxDurability)
			{
				return RepairVerdict.AlreadyFull;
			}
			if (reagentHeld < reagentCost)
			{
				return RepairVerdict.NoReagent;
			}
			return RepairVerdict.Ok;
		}

		public static EquipState Repaired(EquipState s)
		{
			s.Durability = s.MaxDurability;
			return s;
		}
	}
	public enum BodilessAnchorPolicy
	{
		DestroyAnchor,
		UpkeepAnchor
	}
	public static class BodilessAnchorRule
	{
		public enum Action
		{
			None,
			Destroy,
			Upkeep
		}

		public static Action Decide(BodilessAnchorPolicy policy, bool anchorExists)
		{
			if (policy == BodilessAnchorPolicy.UpkeepAnchor)
			{
				return Action.Upkeep;
			}
			if (!anchorExists)
			{
				return Action.None;
			}
			return Action.Destroy;
		}
	}
	public static class HostTag
	{
		public const int MaxLength = 4;

		public static string Derive(string guidOrName)
		{
			if (string.IsNullOrEmpty(guidOrName))
			{
				return null;
			}
			int num = guidOrName.LastIndexOf('.');
			string obj = ((num >= 0 && num < guidOrName.Length - 1) ? guidOrName.Substring(num + 1) : guidOrName);
			StringBuilder stringBuilder = new StringBuilder(4);
			string text = obj;
			foreach (char c in text)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToUpperInvariant(c));
					if (stringBuilder.Length >= 4)
					{
						break;
					}
				}
			}
			if (stringBuilder.Length != 0)
			{
				return stringBuilder.ToString();
			}
			return null;
		}
	}
	public static class CompanionSceneMath
	{
		public static void SpotAhead(double px, double pz, double fx, double fz, double aheadMeters, out double x, out double z)
		{
			double num = Math.Sqrt(fx * fx + fz * fz);
			if (num < 1E-06)
			{
				x = px;
				z = pz;
			}
			else
			{
				x = px + fx / num * aheadMeters;
				z = pz + fz / num * aheadMeters;
			}
		}

		public static bool Arrived(double moverX, double moverZ, double spotX, double spotZ, double arriveDistance)
		{
			double num = moverX - spotX;
			double num2 = moverZ - spotZ;
			return num * num + num2 * num2 <= arriveDistance * arriveDistance;
		}
	}
	public static class ContaminationPolicy
	{
		public static int Total(int photonViews, int netControls, int characterAi, int charAiDisable)
		{
			return photonViews + netControls + characterAi + charAiDisable;
		}

		public static bool ShouldRebuild(int contamination)
		{
			return contamination > 0;
		}

		public static string Describe(int photonViews, int netControls, int characterAi, int charAiDisable, int characters)
		{
			return "pv=" + photonViews + " ncc=" + netControls + " charAI=" + characterAi + " caid=" + charAiDisable + " character=" + characters;
		}
	}
	public static class CoopCaptureNormalizer
	{
		public static float Divisor(bool isGuestInRoom, int playersInLobby, float[] maxHealthMultipliers)
		{
			if (!isGuestInRoom || playersInLobby < 2)
			{
				return 1f;
			}
			if (maxHealthMultipliers == null || maxHealthMultipliers.Length == 0)
			{
				return 1f;
			}
			int num = playersInLobby - 2;
			if (num > maxHealthMultipliers.Length - 1)
			{
				num = maxHealthMultipliers.Length - 1;
			}
			float num2 = maxHealthMultipliers[num];
			if (float.IsNaN(num2) || float.IsInfinity(num2) || num2 <= 0f)
			{
				return 1f;
			}
			return num2;
		}

		public static float Normalize(float capturedMaxHealth, float divisor)
		{
			if (!(divisor > 0f) || float.IsNaN(divisor) || float.IsInfinity(divisor))
			{
				return capturedMaxHealth;
			}
			return capturedMaxHealth / divisor;
		}
	}
	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 StatusResistance;

		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,
				StatusResistance = StatusResistance
			};
			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()
		{
			return Flatten(null);
		}

		public string Flatten(Action<string> warn)
		{
			StringBuilder stringBuilder = new StringBuilder();
			Scalar(stringBuilder, "hp", MaxHealth, warn);
			Scalar(stringBuilder, "spd", MoveSpeed, warn);
			Scalar(stringBuilder, "imp", Impact, warn);
			Scalar(stringBuilder, "impres", ImpactResistance, warn);
			Scalar(stringBuilder, "bar", Barrier, warn);
			Scalar(stringBuilder, "protall", ProtectionAll, warn);
			Scalar(stringBuilder, "statres", StatusResistance, warn);
			Sparse(stringBuilder, "res", Resist, warn);
			Sparse(stringBuilder, "prot", Protection, warn);
			Sparse(stringBuilder, "dmg", Damage, warn);
			return stringBuilder.ToString();
		}

		public static CreatureAttributes Parse(string s)
		{
			return Parse(s, null);
		}

		public static CreatureAttributes Parse(string s, Action<string> warn)
		{
			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)
			{
				if (text.Trim().Length == 0)
				{
					continue;
				}
				int num = text.IndexOf('=');
				if (num <= 0)
				{
					warn?.Invoke("attributes: unreadable segment '" + text + "' (expected key=value) — skipped.");
					continue;
				}
				string text2 = text.Substring(0, num).Trim();
				string text3 = text.Substring(num + 1).Trim();
				switch (text2)
				{
				case "hp":
					flag |= TryF(text3, ref creatureAttributes.MaxHealth, warn, text2);
					break;
				case "spd":
					flag |= TryF(text3, ref creatureAttributes.MoveSpeed, warn, text2);
					break;
				case "imp":
					flag |= TryF(text3, ref creatureAttributes.Impact, warn, text2);
					break;
				case "impres":
					flag |= TryF(text3, ref creatureAttributes.ImpactResistance, warn, text2);
					break;
				case "bar":
					flag |= TryF(text3, ref creatureAttributes.Barrier, warn, text2);
					break;
				case "protall":
					flag |= TryF(text3, ref creatureAttributes.ProtectionAll, warn, text2);
					break;
				case "statres":
					flag |= TryF(text3, ref creatureAttributes.StatusResistance, warn, text2);
					break;
				case "res":
					flag |= ParseSparse(text3, creatureAttributes.Resist, warn, text2);
					break;
				case "prot":
					flag |= ParseSparse(text3, creatureAttributes.Protection, warn, text2);
					break;
				case "dmg":
					flag |= ParseSparse(text3, creatureAttributes.Damage, warn, text2);
					break;
				default:
					warn?.Invoke("attributes: unknown key '" + text2 + "' — ignored.");
					break;
				}
			}
			if (!flag)
			{
				warn?.Invoke("attributes: nothing readable in '" + s + "' — no capture restored.");
			}
			if (!flag)
			{
				return null;
			}
			return creatureAttributes;
		}

		private static bool IsFinite(float v)
		{
			if (!float.IsNaN(v))
			{
				return !float.IsInfinity(v);
			}
			return false;
		}

		private static void Scalar(StringBuilder sb, string key, float v, Action<string> warn)
		{
			if (v == 0f)
			{
				return;
			}
			if (!IsFinite(v))
			{
				warn?.Invoke($"attributes: non-finite '{key}' ({v}) — omitted.");
				return;
			}
			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, Action<string> warn)
		{
			bool flag = false;
			for (int i = 0; i < values.Length; i++)
			{
				if (values[i] == 0f)
				{
					continue;
				}
				if (!IsFinite(values[i]))
				{
					warn?.Invoke($"attributes: non-finite '{key}[{i}]' ({values[i]}) — omitted.");
					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, Action<string> warn, string key)
		{
			bool result = false;
			string[] array = list.Split(new char[1] { ',' });
			foreach (string text in array)
			{
				if (text.Trim().Length == 0)
				{
					continue;
				}
				int num = text.IndexOf(':');
				int result2;
				if (num <= 0)
				{
					warn?.Invoke("attributes: unreadable '" + key + "' entry '" + text + "' (expected index:value) — skipped.");
				}
				else if (!int.TryParse(text.Substring(0, num), NumberStyles.Integer, CultureInfo.InvariantCulture, out result2))
				{
					warn?.Invoke("attributes: unreadable '" + key + "' index '" + text.Substring(0, num) + "' — skipped.");
				}
				else if (result2 < 0 || result2 >= into.Length)
				{
					warn?.Invoke($"attributes: '{key}' index {result2} out of range (0..{into.Length - 1}) — skipped.");
				}
				else
				{
					float v = into[result2];
					if (TryF(text.Substring(num + 1), ref v, warn, $"{key}[{result2}]"))
					{
						into[result2] = v;
						result = true;
					}
				}
			}
			return result;
		}

		private static bool TryF(string s, ref float v, Action<string> warn, string field)
		{
			if (!float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				warn?.Invoke("attributes: unreadable '" + field + "' value '" + s + "' — skipped.");
				return false;
			}
			if (!IsFinite(result))
			{
				warn?.Invoke("attributes: non-finite '" + field + "' value '" + s + "' — skipped.");
				return false;
			}
			v = result;
			return true;
		}
	}
	public static class DamageRider
	{
		public static float Amount(float total, float fraction)
		{
			if (!(fraction > 0f) || !(total > 0f))
			{
				return 0f;
			}
			return total * fraction;
		}
	}
	public static class DisplayChainRisk
	{
		private static readonly string[] Tokens = new string[4] { "waterpro", "waterreflection", "waterrender", "aquas" };

		public static bool IsWaterReflectionRigName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			for (int i = 0; i < Tokens.Length; i++)
			{
				if (name.IndexOf(Tokens[i], StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}
	}
	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
	{
		private static readonly string[] OversizedSceneNames = new string[14]
		{
			"ChersoneseNewTerrain", "Emercar", "HallowedMarshNewTerrain", "Abrassar", "AntiqueField", "Caldera", "CierzoNewTerrain", "Berg", "Monsoon", "Levant",
			"Harmattan", "NewSirocco", "CierzoDestroyed", "CierzoTutorial"
		};

		public static IReadOnlyList<string> OversizedScenes => OversizedSceneNames;

		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();
			bool flag = true;
			while (flag)
			{
				flag = false;
				if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase))
				{
					text = text.Substring(0, text.Length - "(Clone)".Length).Trim();
					flag = true;
					continue;
				}
				if (!text.EndsWith(")", StringComparison.Ordinal))
				{
					break;
				}
				int num = text.LastIndexOf('(');
				if (num <= 0)
				{
					break;
				}
				bool flag2 = num + 1 < text.Length - 1;
				for (int i = num + 1; i < text.Length - 1 && flag2; i++)
				{
					if (!char.IsDigit(text[i]))
					{
						flag2 = false;
					}
				}
				if (!flag2)
				{
					break;
				}
				text = text.Substring(0, num).Trim();
				flag = true;
			}
			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[] oversizedSceneNames = OversizedSceneNames;
			for (int i = 0; i < oversizedSceneNames.Length; i++)
			{
				if (string.Equals(oversizedSceneNames[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 SpeciesFloats
	{
		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)
		{
			string matchedKey;
			return ForKeyed(pairs, speciesId, out matchedKey);
		}

		public static float ForKeyed(List<KeyValuePair<string, float>> pairs, string speciesId, out string matchedKey)
		{
			matchedKey = null;
			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))
					{
						matchedKey = pair.Key;
						return pair.Value;
					}
					if (Species.NameMatches(speciesId, pair.Key) && (text == null || pair.Key.Length > text.Length))
					{
						text = pair.Key;
						result = pair.Value;
					}
				}
			}
			matchedKey = text;
			return result;
		}

		public static List<KeyValuePair<string, float>> Merge(List<KeyValuePair<string, float>> baseList, List<KeyValuePair<string, float>> overrides)
		{
			List<KeyValuePair<string, float>> list = new List<KeyValuePair<string, float>>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (overrides != null)
			{
				foreach (KeyValuePair<string, float> @override in overrides)
				{
					if (!string.IsNullOrEmpty(@override.Key))
					{
						hashSet.Add(@override.Key);
					}
				}
			}
			if (baseList != null)
			{
				foreach (KeyValuePair<string, float> @base in baseList)
				{
					if (string.IsNullOrEmpty(@base.Key) || !hashSet.Contains(@base.Key))
					{
						list.Add(@base);
					}
				}
			}
			if (overrides != null)
			{
				list.AddRange(overrides);
			}
			return list;
		}

		public static float ForLayered(List<KeyValuePair<string, float>> baseList, List<KeyValuePair<string, float>> overrides, string speciesId, out string matchedKey)
		{
			float num = ForKeyed(overrides, speciesId, out matchedKey);
			if (!float.IsNaN(num))
			{
				return num;
			}
			return ForKeyed(baseList, speciesId, out matchedKey);
		}
	}
	public static class SpeciesYaw
	{
		public static List<KeyValuePair<string, float>> Parse(string spec)
		{
			return SpeciesFloats.Parse(spec);
		}

		public static float For(List<KeyValuePair<string, float>> pairs, string speciesId)
		{
			return SpeciesFloats.For(pairs, speciesId);
		}

		public static Dictionary<string, float> ParseTable(string text, Action<string> warn = null)
		{
			Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
			foreach (SpeciesTable.TableLine item in SpeciesTable.Lines(text))
			{
				float num = SpeciesTable.FloatOr(item.Fields, 0, float.NaN, warn, item.Key);
				if (!float.IsNaN(num))
				{
					dictionary[item.Key] = num;
				}
			}
			return dictionary;
		}

		public static float Resolve(List<KeyValuePair<string, float>> embeddedPairs, List<KeyValuePair<string, float>> cfgPairs, IReadOnlyDictionary<string, float> sessionExact, string speciesId)
		{
			if (!string.IsNullOrEmpty(speciesId) && sessionExact != null && sessionExact.TryGetValue(speciesId, out var value))
			{
				return value;
			}
			return SpeciesFloats.For(SpeciesFloats.Merge(embeddedPairs, cfgPairs), speciesId);
		}
	}
	public static class SpeciesSlope
	{
		public static Dictionary<string, float> ParseTable(string text, Action<string> warn = null)
		{
			Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
			foreach (SpeciesTable.TableLine item in SpeciesTable.Lines(text))
			{
				float num = BoolField(item.Fields, warn, item.Key);
				if (!float.IsNaN(num))
				{
					dictionary[item.Key] = num;
				}
			}
			return dictionary;
		}

		public static float BoolField(string[] fields, Action<string> warn, string key)
		{
			return BoolField(fields, warn, key, "slope-tilt");
		}

		public static float BoolField(string[] fields, Action<string> warn, string key, string what)
		{
			if (fields == null || fields.Length == 0)
			{
				return float.NaN;
			}
			string text = fields[0].Trim();
			if (text.Length == 0)
			{
				return float.NaN;
			}
			if (text.Equals("true", StringComparison.OrdinalIgnoreCase) || text == "1")
			{
				return 1f;
			}
			if (text.Equals("false", StringComparison.OrdinalIgnoreCase) || text == "0")
			{
				return 0f;
			}
			warn?.Invoke("'" + key + "': unparsable " + what + " flag '" + text + "' (want true/false/1/0) — row skipped.");
			return float.NaN;
		}

		public static float Resolve(List<KeyValuePair<string, float>> pairs, string speciesId)
		{
			return SpeciesFloats.For(pairs, speciesId);
		}
	}
	public static class SpeciesBackpedal
	{
		public static Dictionary<string, float> ParseTable(string text, Action<string> warn = null)
		{
			Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
			foreach (SpeciesTable.TableLine item in SpeciesTable.Lines(text))
			{
				float num = SpeciesSlope.BoolField(item.Fields, warn, item.Key, "backpedal");
				if (!float.IsNaN(num))
				{
					dictionary[item.Key] = num;
				}
			}
			return dictionary;
		}

		public static float Resolve(List<KeyValuePair<string, float>> pairs, string speciesId)
		{
			return SpeciesFloats.For(pairs, speciesId);
		}
	}
	public static class DuplicateViewPolicy
	{
		public const int SceneBakedMax = 999;

		public static bool ShouldVeto(int viewId, bool oldAlive, string oldScene, string newScene, string activeScene)
		{
			if (!oldAlive)
			{
				return false;
			}
			if (viewId < 1 || viewId > 999)
			{
				return false;
			}
			if (string.IsNullOrEmpty(activeScene) || oldScene != activeScene)
			{
				return false;
			}
			return newScene != activeScene;
		}
	}
	public struct EffigyHarvestFacts
	{
		public bool Enabled;

		public bool ExpeditionBusy;

		public bool HarvestBusy;

		public bool HasAdditiveDonor;

		public bool HasExpeditionDonor;
	}
	public enum EffigyHarvestStep
	{
		Go,
		Disabled,
		NoDonor,
		ExpeditionOnly,
		Backoff,
		ExpeditionBusy,
		HarvestBusy
	}
	public sealed class EffigyHarvestGate
	{
		public const float DefaultRetrySeconds = 600f;

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

		private readonly Dictionary<string, int> _failures = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		public float RetrySeconds { get; set; }

		public EffigyHarvestGate(float retrySeconds = 600f)
		{
			RetrySeconds = retrySeconds;
		}

		public EffigyHarvestStep Decide(string species, in EffigyHarvestFacts facts, float now)
		{
			if (string.IsNullOrEmpty(species))
			{
				return EffigyHarvestStep.NoDonor;
			}
			if (!facts.Enabled)
			{
				return EffigyHarvestStep.Disabled;
			}
			if (!facts.HasAdditiveDonor)
			{
				if (!facts.HasExpeditionDonor)
				{
					return EffigyHarvestStep.NoDonor;
				}
				return EffigyHarvestStep.ExpeditionOnly;
			}
			if (_nextAttemptAt.TryGetValue(species, out var value) && now < value)
			{
				return EffigyHarvestStep.Backoff;
			}
			if (facts.ExpeditionBusy)
			{
				return EffigyHarvestStep.ExpeditionBusy;
			}
			if (facts.HarvestBusy)
			{
				return EffigyHarvestStep.HarvestBusy;
			}
			_nextAttemptAt[species] = now + RetrySeconds;
			return EffigyHarvestStep.Go;
		}

		public void ReportResult(string species, bool success, float now)
		{
			if (!string.IsNullOrEmpty(species))
			{
				if (success)
				{
					_nextAttemptAt.Remove(species);
					_failures.Remove(species);
				}
				else
				{
					_nextAttemptAt[species] = now + RetrySeconds;
					_failures.TryGetValue(species, out var value);
					_failures[species] = value + 1;
				}
			}
		}

		public int Failures(string species)
		{
			if (string.IsNullOrEmpty(species) || !_failures.TryGetValue(species, out var value))
			{
				return 0;
			}
			return value;
		}

		public float SecondsUntilRetry(string species, float now)
		{
			if (string.IsNullOrEmpty(species) || !_nextAttemptAt.TryGetValue(species, out var value))
			{
				return 0f;
			}
			float num = value - now;
			if (!(num > 0f))
			{
				return 0f;
			}
			return num;
		}

		public void Reset()
		{
			_nextAttemptAt.Clear();
			_failures.Clear();
		}
	}
	public enum EffigyBodyRung
	{
		None,
		Real,
		Ghost
	}
	public enum EffigySetResult
	{
		Refused,
		Created,
		Updated,
		SpeciesChanged
	}
	public enum EffigyStep
	{
		None,
		Acquire,
		Upgrade,
		Despawn
	}
	public sealed class EffigyRow
	{
		internal float NextAttemptAt;

		internal float AnchorUnresolvedAt;

		public string OwnerUid { get; internal set; }

		public string Species { get; internal set; }

		public int Tier { get; internal set; }

		public string Extension { get; internal set; } = "";

		public bool StancePassive { get; internal set; }

		public bool AnchorResolved { get; internal set; }

		public EffigyBodyRung Body { get; internal set; }
	}
	public sealed class EffigyLedger
	{
		public const float DefaultAcquireRetrySeconds = 3f;

		public const float DefaultUpgradeCheckSeconds = 10f;

		public const float AnchorLossGraceSeconds = 8f;

		private readonly float _acquireRetrySeconds;

		private readonly float _upgradeCheckSeconds;

		private readonly Dictionary<string, EffigyRow> _rows = new Dictionary<string, EffigyRow>(StringComparer.Ordinal);

		public int Count => _rows.Count;

		public int LiveBodies
		{
			get
			{
				int num = 0;
				foreach (KeyValuePair<string, EffigyRow> row in _rows)
				{
					if (row.Value.Body != EffigyBodyRung.None)
					{
						num++;
					}
				}
				return num;
			}
		}

		public EffigyLedger(float acquireRetrySeconds = 3f, float upgradeCheckSeconds = 10f)
		{
			_acquireRetrySeconds = acquireRetrySeconds;
			_upgradeCheckSeconds = upgradeCheckSeconds;
		}

		public List<EffigyRow> RowsSnapshot()
		{
			List<EffigyRow> list = new List<EffigyRow>(_rows.Count);
			foreach (KeyValuePair<string, EffigyRow> row in _rows)
			{
				list.Add(row.Value);
			}
			return list;
		}

		public bool TryGet(string ownerUid, out EffigyRow row)
		{
			return _rows.TryGetValue(ownerUid ?? "", out row);
		}

		public EffigySetResult Set(string ownerUid, string species, int tier)
		{
			return Set(ownerUid, species, tier, "");
		}

		public EffigySetResult Set(string ownerUid, string species, int tier, string extension)
		{
			if (string.IsNullOrEmpty(ownerUid) || string.IsNullOrEmpty(species))
			{
				return EffigySetResult.Refused;
			}
			tier = NetProtocol.ClampTier(tier);
			extension = extension ?? "";
			if (!_rows.TryGetValue(ownerUid, out EffigyRow value))
			{
				_rows[ownerUid] = new EffigyRow
				{
					OwnerUid = ownerUid,
					Species = species,
					Tier = tier,
					Extension = extension
				};
				return EffigySetResult.Created;
			}
			bool num = !string.Equals(value.Species, species, StringComparison.Ordinal);
			value.Species = species;
			value.Tier = tier;
			value.Extension = extension;
			if (num)
			{
				value.NextAttemptAt = 0f;
			}
			if (!num)
			{
				return EffigySetResult.Updated;
			}
			return EffigySetResult.SpeciesChanged;
		}

		public bool SetStance(string ownerUid, bool passive)
		{
			if (!_rows.TryGetValue(ownerUid ?? "", out EffigyRow value) || value.StancePassive == passive)
			{
				return false;
			}
			value.StancePassive = passive;
			return true;
		}

		public bool Clear(string ownerUid)
		{
			return _rows.Remove(ownerUid ?? "");
		}

		public int ClearAll()
		{
			int count = _rows.Count;
			_rows.Clear();
			return count;
		}

		public void MarkAnchor(string ownerUid, bool resolved, float now)
		{
			if (_rows.TryGetValue(ownerUid ?? "", out EffigyRow value))
			{
				if (value.AnchorResolved && !resolved)
				{
					value.AnchorUnresolvedAt = now;
				}
				value.AnchorResolved = resolved;
			}
		}

		public void MarkBody(string ownerUid, EffigyBodyRung rung)
		{
			if (_rows.TryGetValue(ownerUid ?? "", out EffigyRow value))
			{
				value.Body = rung;
				if (rung == EffigyBodyRung.None)
				{
					value.NextAttemptAt = 0f;
				}
			}
		}

		public EffigyStep Reconcile(EffigyRow row, bool enabled, int maxBodies, float now)
		{
			if (row == null)
			{
				return EffigyStep.None;
			}
			if (!enabled)
			{
				if (row.Body == EffigyBodyRung.None)
				{
					return EffigyStep.None;
				}
				return EffigyStep.Despawn;
			}
			if (!row.AnchorResolved)
			{
				if (row.Body == EffigyBodyRung.None)
				{
					return EffigyStep.None;
				}
				if (!(now - row.AnchorUnresolvedAt >= 8f))
				{
					return EffigyStep.None;
				}
				return EffigyStep.Despawn;
			}
			switch (row.Body)
			{
			case EffigyBodyRung.None:
				if (LiveBodies >= maxBodies)
				{
					return EffigyStep.None;
				}
				if (now < row.NextAttemptAt)
				{
					return EffigyStep.None;
				}
				row.NextAttemptAt = now + _acquireRetrySeconds;
				return EffigyStep.Acquire;
			case EffigyBodyRung.Ghost:
				if (now < row.NextAttemptAt)
				{
					return EffigyStep.None;
				}
				row.NextAttemptAt = now + _upgradeCheckSeconds;
				return EffigyStep.Upgrade;
			default:
				return EffigyStep.None;
			}
		}
	}
	public static class EffigyPinMath
	{
		public enum PinMove
		{
			Weld,
			Glide,
			Snap
		}

		public readonly struct PinStep
		{
			public readonly PinMove Mode;

			public readonly float MaxStep;

			public readonly float Distance;

			public PinStep(PinMove mode, float maxStep, float distance)
			{
				Mode = mode;
				MaxStep = maxStep;
				Distance = distance;
			}
		}

		public const float MaxAnimForward = 8f;

		public const float MinFacingSqr = 0.0001f;

		public static bool HorizontalFacing(float fx, float fz, out float nx, out float nz)
		{
			float num = fx * fx + fz * fz;
			if (num < 0.0001f)
			{
				nx = 0f;
				nz = 0f;
				return false;
			}
			float num2 = 1f / (float)Math.Sqrt(num);
			nx = fx * num2;
			nz = fz * num2;
			return true;
		}

		public static float HorizontalSpeed(float dx, float dz, float dt)
		{
			if (dt <= 0.0001f)
			{
				return 0f;
			}
			return (float)Math.Sqrt((double)dx * (double)dx + (double)dz * (double)dz) / dt;
		}

		public static PinStep CatchUpStep(float distance, float deadBand, float catchUpSpeed, float snapDistance, float dt)
		{
			if (!(distance > 0f))
			{
				return new PinStep(PinMove.Weld, 0f, 0f);
			}
			if (snapDistance > 0f && distance >= snapDistance)
			{
				return new PinStep(PinMove.Snap, distance, distance);
			}
			if (distance <= deadBand)
			{
				return new PinStep(PinMove.Weld, 0f, distance);
			}
			float num = ((dt > 0f && catchUpSpeed > 0f) ? (catchUpSpeed * dt) : 0f);
			if (num > distance)
			{
				num = distance;
			}
			return new PinStep(PinMove.Glide, num, distance);
		}

		public static float AnimForward(float speed, bool moving)
		{
			if (!moving)
			{
				return 0f;
			}
			if (speed < 0f)
			{
				return 0f;
			}
			if (!(speed > 8f))
			{
				return speed;
			}
			return 8f;
		}
	}
	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 ExpeditionCoop
	{
		public struct GuestGateState
		{
			public bool Enabled;

			public bool IsGuest;

			public bool HostExpeditionActive;

			public bool GateArmed;

			public bool ContinueAlreadySet;

			public bool GameplayLoading;

			public bool AllPlayersDone;

			public bool MasterLoadingDisplayed;
		}

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

		public static bool ShouldGuestContinue(in GuestGateState s)
		{
			if (s.Enabled && s.IsGuest && s.HostExpeditionActive && s.GateArmed && !s.ContinueAlreadySet && !s.GameplayLoading && s.AllPlayersDone)
			{
				return s.MasterLoadingDisplayed;
			}
			return false;
		}
	}
	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 bool DonorSceneVisited;
		}

		public enum ListOutcome
		{
			Warm,
			SkippedAdditive,
			SkippedCached,
			DeferredUnvisited,
			Unknown,
			SkippedActiveCompanion
		}

		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 Func<string, bool> SceneVisited;
		}

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

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

			public List<string> DeferredScenes = new List<string>();

			public bool AnyDeferred => DeferredScenes.Count > 0;
		}

		public struct StorySafety
		{
			public bool InPrologue;

			public bool CurrentSceneFirstLoad;
		}

		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 bool StoryHold(in StorySafety s, out string reason)
		{
			if (s.InPrologue)
			{
				reason = "the prologue/tutorial is active";
				return true;
			}
			if (s.CurrentSceneFirstLoad)
			{
				reason = "the current scene is being visited for the FIRST time (departing would save it mid-story and consume its one-shot beats)";
				return true;
			}
			reason = null;
			return false;
		}

		public static string NeededScene(in SpeciesResolve r)
		{
			if (!r.HasExpeditionScenes || r.HasAdditiveDonor || r.HasCachedTemplate || !r.DonorSceneVisited || 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.Unknown;
			}
			else if (!r.DonorSceneVisited)
			{
				result.Outcome = ListOutcome.DeferredUnvisited;
				result.Scene = r.FirstExpeditionScene;
			}
			else
			{
				result.Outcome = ListOutcome.Warm;
				result.Scene = r.FirstExpeditionScene;
			}
			return result;
		}

		public static Plan Decide(PlanInput input)
		{
			Plan plan = new Plan();
			if (input == null)
			{
				return plan;
			}
			Func<string, bool> func = input.SceneVisited ?? ((Func<string, bool>)((string _) => true));
			switch (input.Mode)
			{
			case Mode.Needed:
			{
				if (!input.ActivePet.HasValue)
				{
					break;
				}
				SpeciesResolve r = input.ActivePet.Value;
				string text = NeededScene(in r);
				if (text != null)
				{
					ExpeditionLog.Append(plan.Scenes, text);
				}
				else if (!r.DonorSceneVisited)
				{
					string text2 = NeededSceneIgnoringVisited(in r);
					if (text2 != null)
					{
						ExpeditionLog.Append(plan.DeferredScenes, text2);
					}
				}
				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))
					{
						if (func(manifestScene))
						{
							ExpeditionLog.Append(plan.Scenes, manifestScene);
						}
						else
						{
							ExpeditionLog.Append(plan.DeferredScenes, manifestScene);
						}
					}
				}
				break;
			}
			if (input.AlwaysWarm != null)
			{
				bool flag = !input.ActivePet.HasValue;
				if (!flag)
				{
					foreach (SpeciesResolve item2 in input.AlwaysWarm)
					{
						if (string.Equals(item2.Species, input.ActivePet.Value.Species, StringComparison.OrdinalIgnoreCase))
						{
							flag = true;
							break;
						}
					}
				}
				foreach (SpeciesResolve item3 in input.AlwaysWarm)
				{
					SpeciesResolve r2 = item3;
					if (!flag)
					{
						plan.ListDecisions.Add(new ListDecision
						{
							Species = r2.Species,
							Outcome = ListOutcome.SkippedActiveCompanion
						});
						continue;
					}
					ListDecision item = DecideListed(in r2);
					plan.ListDecisions.Add(item);
					if (item.Outcome == ListOutcome.Warm)
					{
						ExpeditionLog.Append(plan.Scenes, item.Scene);
					}
					else if (item.Outcome == ListOutcome.DeferredUnvisited)
					{
						ExpeditionLog.Append(plan.DeferredScenes, item.Scene);
					}
				}
			}
			for (int num = plan.DeferredScenes.Count - 1; num >= 0; num--)
			{
				foreach (string scene in plan.Scenes)
				{
					if (string.Equals(scene, plan.DeferredScenes[num], StringComparison.OrdinalIgnoreCase))
					{
						plan.DeferredScenes.RemoveAt(num);
						break;
					}
				}
			}
			return plan;
		}

		private static string NeededSceneIgnoringVisited(in SpeciesResolve r)
		{
			if (!r.HasExpeditionScenes || r.HasAdditiveDonor || r.HasCachedTemplate || string.IsNullOrEmpty(r.FirstExpeditionScene))
			{
				return null;
			}
			return r.FirstExpeditionScene;
		}

		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 bool Force;
		}

		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
				};
			}
			if (parts.Length >= 3 && string.Equals(parts[1], "force", StringComparison.OrdinalIgnoreCase))
			{
				return new Command
				{
					Kind = Kind.Target,
					Force = true,
					Target = string.Join(" ", parts, 2, parts.Length - 2)
				};
			}
			return new Command
			{
				Kind = Kind.Target,
				Target = string.Join(" ", parts, 1, parts.Length - 1)
			};
		}
	}
	public static class FacingPolicy
	{
		public static bool ForceEnemyHeading(bool hasCombatTarget, bool planted)
		{
			if (hasCombatTarget)
			{
				return LookTargetPolicy.Choose(hasCombatTarget, planted, stationActive: true, pointingHold: false, loafActive: false, approachActive: false, settled: false) == LookTarget.Enemy;
			}
			return false;
		}

		public static bool FaceDriveDestination(bool loafActive, bool approachActive)
		{
			return LookTargetPolicy.Choose(hasCombat: false, planted: false, stationActive: false, pointingHold: false, loafActive, approachActive, settled: false) == LookTarget.Travel;
		}

		public static bool ForcePointHeading(bool pointingHold)
		{
			return LookTargetPolicy.Choose(hasCombat: false, planted: false, stationActive: false, pointingHold, loafActive: false, approachActive: false, settled: false) == LookTarget.FacePoint;
		}
	}
	public enum FollowMode
	{
		Hold,
		Direct,
		Replace,
		Agent
	}
	public enum HoldReason
	{
		None,
		NoTargetOrAgent,
		VoidStaging,
		AgentSuspended
	}
	public enum ReplaceDestination
	{
		Goal,
		Owner
	}
	public sealed class FollowTuning
	{
		public float ZoneReplaceDistance = 50f;

		public float ReplaceThrottleSeconds = 0.4f;

		public float LeashWarpThrottleSeconds = 0.75f;

		public float RoofSanityMeters = 1.5f;

		public float RepathIntervalSeconds = 0.2f;

		public float RepathHysteresisSq = 0.16f;

		public float GoalSampleRadius = 2f;

		public float OwnerSampleRadius = 2f;

		public float LoafSampleRadius = 2f;

		public float PlantHysteresisMeters = 1.5f;

		public float CantReachSlack = 2f;

		public float CantReachGraceSeconds = 1.5f;

		public float LeashWarpSearchRadius = 8f;

		public float LoafReengageGraceSeconds = 1.5f;

		public float LoafArriveDistance = 0.6f;

		public float CatchUpNearMeters = 5f;

		public float CatchUpRampMeters = 4f;

		public float FollowAngularSpeed = 240f;

		public float FollowAcceleration = 12f;

		public float BrakeWithinMeters = 2.5f;

		public float CombatAngularSpeed = 540f;

		public float CombatAcceleration = 40f;

		public float WarpBehindMeters = 12.5f;

		public float WarpBehindStepMeters = 5f;

		public float WarpBehindMaxMeters = 22.5f;

		public float WarpSideMeters = -0.5f;

		public float OffscreenMargin = 0.08f;

		public float ApproachFarMeters = 5f;

		public float ApproachArriveMeters = 0.9f;

		public float ApproachRefuseEnterMeters = 1.5f;

		public float ApproachRefuseExitMeters = 2f;

		public float TurnSpeed = 0.08f;

		public float StationArriveMeters = 0.8f;
	}
	public struct FollowFacts
	{
		public bool HasTargetAndAgent;

		public bool TargetSane;

		public bool AgentAbandoned;

		public float DistToOwner;

		public bool HasCombatTarget;

		public bool HasFollowOverride;

		public bool HasSceneSpot;

		public bool HasStaySpot;

		public bool GoalIsOwner;

		public bool GoalOnMesh;

		public bool AgentOnMesh;

		public bool AgentEnabled;

		public bool AgentSuspendedExternally;

		public float Now;

		public float LastReplaceAt;

		public float LeashGraceUntil;

		public float FaceUntil;

		public float BaseSpeed;

		public float FollowSpeedFloor;

		public float FollowRefDist;

		public float CatchUpSpeed;

		public float LeashDistance;

		public float CombatLeashDistance;

		public bool SuppressLeashWarp;
	}
	public struct FollowDecision
	{
		public FollowMode Mode;

		public HoldReason Hold;

		public bool PreReplaceOwner;

		public bool ReplaceNow;

		public ReplaceDestination ReplaceDest;

		public bool ReplaceMeasurable;

		public bool LoafEligible;

		public float DriveSpeed;
	}
	public struct AgentDriveFacts
	{
		public bool HasCombatTarget;

		public float GoalDist;

		public float AttackRange;

		public bool WasPlanted;

		public float Now;

		public float FaceUntil;

		public bool LoafActive;

		public bool ApproachActive;

		public bool SceneSpotActive;

		public bool StaySpotActive;

		public bool HasPath;

		public float LastRepathAt;

		public float DestMovedSq;

		public float LeashDist;

		public float LeashGraceUntil;

		public bool PathPending;

		public bool PathComplete;

		public float LastWarpAt;

		public float CantReachSince;

		public float StopDistance;

		public float LeashDistance;

		public float CombatLeashDistance;

		public bool SuppressLeashWarp;

		public bool HasStation;

		public float StationDist;

		public float StationArriveMeters;
	}
	public enum WarpReason
	{
		None,
		Leash,
		CantReach
	}
	public struct AgentDrivePlan
	{
		public bool Planted;

		public bool HoldForCombat;

		public bool PointingHold;

		public bool AgentStopped;

		public float StoppingDistance;

		public bool Repath;

		public bool GraceEnded;

		public bool LeashWarp;

		public WarpReason LeashWarpReason;

		public bool CantReachNow;

		public bool ClearWarpBlocked;
	}
	public static class FollowPolicy
	{
		public static bool NeedsGoalSample(in FollowFacts f)
		{
			if (f.HasTargetAndAgent && f.TargetSane)
			{
				return !f.AgentAbandoned;
			}
			return false;
		}

		public static bool ShouldReArmAgent(in FollowFacts f)
		{
			if (f.GoalOnMesh && !f.AgentEnabled)
			{
				return !f.AgentSuspendedExternally;
			}
			return false;
		}

		public static FollowDecision Decide(in FollowFacts f, FollowTuning t)
		{
			FollowDecision result = new FollowDecision
			{
				DriveSpeed = FollowSpeed(f.FollowRefDist, f.BaseSpeed, f.FollowSpeedFloor, f.CatchUpSpeed, f.HasCombatTarget, t),
				LoafEligible = (!f.HasCombatTarget && !f.HasFollowOverride && !f.HasSceneSpot && !f.HasStaySpot && f.Now >= f.FaceUntil + t.LoafReengageGraceSeconds)
			};
			if (!f.HasTargetAndAgent)
			{
				result.Mode = FollowMode.Hold;
				result.Hold = HoldReason.NoTargetOrAgent;
				return result;
			}
			if (!f.TargetSane)
			{
				result.Mode = FollowMode.Hold;
				result.Hold = HoldReason.VoidStaging;
				return result;
			}
			if (f.AgentSuspendedExternally)
			{
				result.Mode = FollowMode.Hold;
				result.Hold = HoldReason.AgentSuspended;
				return result;
			}
			bool flag = f.Now < f.LeashGraceUntil;
			float val = ((f.HasCombatTarget || flag) ? f.CombatLeashDistance : f.LeashDistance);
			float num = Math.Max(t.ZoneReplaceDistance, val);
			bool flag2 = f.Now - f.LastReplaceAt > t.ReplaceThrottleSeconds;
			if (f.AgentAbandoned)
			{
				result.Mode = FollowMode.Direct;
				bool flag3 = flag;
				bool flag4 = !f.HasCombatTarget && f.DistToOwner > (flag3 ? f.CombatLeashDistance : f.LeashDistance);
				result.PreReplaceOwner = !f.SuppressLeashWarp && (f.DistToOwner > num || flag4) && flag2;
				return result;
			}
			if (!f.GoalOnMesh)
			{
				float num2 = (f.GoalIsOwner ? num : t.ZoneReplaceDistance);
				result.Mode = FollowMode.Direct;
				result.PreReplaceOwner = !f.SuppressLeashWarp && f.DistToOwner > num2 && flag2;
				return result;
			}
			bool flag5 = !f.SuppressLeashWarp && f.DistToOwner > num;
			if (!f.AgentOnMesh || flag5)
			{
				result.Mode = FollowMode.Replace;
				result.ReplaceNow = flag2;
				result.ReplaceDest = (flag5 ? ReplaceDestination.Owner : ReplaceDestination.Goal);
				result.ReplaceMeasurable = ReplaceConvergence.IsMeasurable(flag5, f.GoalIsOwner);
				return result;
			}
			result.Mode = FollowMode.Agent;
			return result;
		}

		public static float FollowSpeed(float distToRef, float baseSpeed, float floor, float catchUp, bool hasCombatTarget, FollowTuning t)
		{
			if (hasCombatTarget)
			{
				return baseSpeed;
			}
			float num = Math.Max(baseSpeed, floor);
			if (catchUp <= 0f)
			{
				return num;
			}
			float num2 = Math.Max(floor, catchUp);
			num = Math.Min(num, num2);
			if (distToRef <= t.CatchUpNearMeters)
			{
				return num;
			}
			float num3 = Math.Max(t.CatchUpRampMeters, 0.001f);
			float num4 = Math.Min(1f, (distToRef - t.CatchUpNearMeters) / num3);
			return num + (num2 - num) * num4;
		}

		public static bool ApproachDestination(float ownerX, float ownerZ, bool hasDir, float dirX, float dirZ, float distToOwner, float behindMeters, FollowTuning t, out float x, out float z)
		{
			return ApproachDestination(ownerX, ownerZ, hasDir, dirX, dirZ, distToOwner, behindMeters, float.NaN, float.NaN, wasApproaching: false, t, out x, out z);
		}

		public static bool ApproachDestination(float ownerX, float ownerZ, bool hasDir, float dirX, float dirZ, float distToOwner, float behindMeters, float petX, float petZ, bool wasApproaching, FollowTuning t, out float x, out float z)
		{
			bool refused;
			return ApproachDestination(ownerX, ownerZ, hasDir, dirX, dirZ, distToOwner, behindMeters, petX, petZ, wasApproaching, wasRefused: false, t, out x, out z, out refused);
		}

		public static bool ApproachDestination(float ownerX, float ownerZ, bool hasDir, float dirX, float dirZ, float distToOwner, float behindMeters, float petX, float petZ, bool wasApproaching, bool wasRefused, FollowTuning t, out float x, out float z, out bool refused)
		{
			x = ownerX;
			z = ownerZ;
			refused = false;
			if (!hasDir || behindMeters <= 0f)
			{
				return false;
			}
			float num = (float)Math.Sqrt(dirX * dirX + dirZ * dirZ);
			if (num < 1E-05f)
			{
				return false;
			}
			x = ownerX - dirX / num * behindMeters;
			z = ownerZ - dirZ / num * behindMeters;
			if (!float.IsNaN(petX) && !float.IsNaN(petZ))
			{
				float num2 = x - petX;
				float num3 = z - petZ;
				float num4 = ownerX - petX;
				float num5 = ownerZ - petZ;
				float num6 = num2 * num2 + num3 * num3;
				if (num6 > 1E-06f)
				{
					float num7 = (num4 * num2 + num5 * num3) / num6;
					float num8 = petX + num2 * num7;
					float num9 = petZ + num3 * num7;
					float num10 = (ownerX - num8) * (ownerX - num8) + (ownerZ - num9) * (ownerZ - num9);
					float num11 = (wasRefused ? t.ApproachRefuseExitMeters : t.ApproachRefuseEnterMeters);
					if (num7 > 0f && num7 < 1f && num10 < num11 * num11)
					{
						refused = true;
						return false;
					}
				}
				if (wasApproaching)
				{
					float num12 = x - petX;
					float num13 = z - petZ;
					return num12 * num12 + num13 * num13 > t.ApproachArriveMeters * t.ApproachArriveMeters;
				}
			}
			return distToOwner > t.ApproachFarMeters;
		}

		public static float OwnerScaledSpeed(float speed, float ownerMultiplier)
		{
			if (float.IsNaN(ownerMultiplier) || float.IsInfinity(ownerMultiplier) || ownerMultiplier < 1f)
			{
				ownerMultiplier = 1f;
			}
			return speed * ownerMultiplier;
		}

		public static void AgentDynamics(bool hasCombatTarget, FollowTuning t, out float angularSpeed, out float acceleration)
		{
			AgentDynamics(hasCombatTarget, 0f, t, out angularSpeed, out acceleration);
		}

		public static void AgentDynamics(bool hasCombatTarget, float driveSpeed, FollowTuning t, out float angularSpeed, out float acceleration)
		{
			angularSpeed = (hasCombatTarget ? t.CombatAngularSpeed : t.FollowAngularSpeed);
			acceleration = (hasCombatTarget ? t.CombatAcceleration : t.FollowAcceleration);
			if (!hasCombatTarget && driveSpeed > 0f && t.BrakeWithinMeters > 0f)
			{
				acceleration = Math.Max(acceleration, driveSpeed * driveSpeed / (2f * t.BrakeWithinMeters));
			}
		}

		public static void WarpOffset(float fx, float fz, float rx, float rz, FollowTuning t, out float ox, out float oz)
		{
			WarpOffset(fx, fz, rx, rz, t.WarpBehindMeters, t, out ox, out oz);
		}

		public static int WarpBehindLadder(FollowTuning t, float[] into)
		{
			int num = 0;
			for (float num2 = t.WarpBehindMeters; num2 <= t.WarpBehindMaxMeters + 0.001f; num2 += Math.Max(t.WarpBehindStepMeters, 0.5f))
			{
				if (num >= into.Length)
				{
					break;
				}
				into[num++] = num2;
			}
			return num;
		}

		public static bool IsOffscreen(float vx, float vy, float vz, FollowTuning t)
		{
			if (!(vz <= 0f) && !(vx < 0f - t.OffscreenMargin) && !(vx > 1f + t.OffscreenMargin) && !(vy < 0f - t.OffscreenMargin))
			{
				return vy > 1f + t.OffscreenMargin;
			}
			return true;
		}

		public static void WarpOffset(float fx, float fz, float rx, float rz, float behind, FollowTuning t, out float ox, out float oz)
		{
			ox = (oz = 0f);
			float num = (float)Math.Sqrt(fx * fx + fz * fz);
			if (!(num < 0.0001f))
			{
				fx /= num;
				fz /= num;
				float num2 = (float)Math.Sqrt(rx * rx + rz * rz);
				if (num2 < 0.0001f)
				{
					rx = fz;
					rz = 0f - fx;
					num2 = 1f;
				}
				rx /= num2;
				rz /= num2;
				ox = (0f - fx) * behind + rx * t.WarpSideMeters;
				oz = (0f - fz) * behind + rz * t.WarpSideMeters;
			}
		}

		public static AgentDrivePlan PlanAgentDrive(in AgentDriveFacts f, FollowTuning t)
		{
			AgentDrivePlan result = new AgentDrivePlan
			{
				Planted = f.WasPlanted
			};
			if (f.HasCombatTarget && f.HasStation)
			{
				float num = ((f.StationArriveMeters > 0f) ? f.StationArriveMeters : t.StationArriveMeters);
				if (f.StationDist <= num)
				{
					result.Planted = true;
				}
				else if (f.StationDist > num + t.PlantHysteresisMeters)
				{
					result.Planted = false;
				}
			}
			else if (f.HasCombatTarget && f.GoalDist <= f.AttackRange)
			{
				result.Planted = true;
			}
			else if (!f.HasCombatTarget || f.GoalDist > f.AttackRange + t.PlantHysteresisMeters)
			{
				result.Planted = false;
			}
			result.HoldForCombat = f.HasCombatTarget && result.Planted;
			result.PointingHold = !f.HasCombatTarget && f.Now < f.FaceUntil;
			result.AgentStopped = result.HoldForCombat || result.PointingHold;
			result.StoppingDistance = ((f.LoafActive || f.SceneSpotActive || f.ApproachActive || f.StaySpotActive || (f.HasCombatTarget && f.HasStation)) ? t.LoafArriveDistance : f.StopDistance);
			result.Repath = !result.HoldForCombat && f.Now - f.LastRepathAt > t.RepathIntervalSeconds && (!f.HasPath || f.DestMovedSq > t.RepathHysteresisSq);
			bool flag = !f.HasCombatTarget && f.Now < f.LeashGraceUntil;
			if (flag && f.LeashDist <= f.LeashDistance)
			{
				result.GraceEnded = true;
				flag = false;
			}
			float num2 = ((f.HasCombatTarget || flag) ? f.CombatLeashDistance : f.LeashDistance);
			bool flag2 = (result.CantReachNow = !f.PathPending && !f.PathComplete);
			bool flag3 = flag2 && f.CantReachSince >= 0f && f.Now - f.CantReachSince >= t.CantReachGraceSeconds;
			bool flag4 = f.LeashDist > num2;
			bool flag5 = !f.HasCombatTarget && !flag && !f.StaySpotActive && flag3 && f.LeashDist > f.StopDistance + t.CantReachSlack;
			bool flag6 = (flag4 || flag5) && f.Now - f.LastWarpAt > t.LeashWarpThrottleSeconds;
			result.LeashWarp = flag6 && !f.SuppressLeashWarp;
			result.LeashWarpReason = (result.LeashWarp ? (flag4 ? WarpReason.Leash : WarpReason.CantReach) : WarpReason.None);
			result.ClearWarpBlocked = !flag6 && f.LeashDist <= num2 && !flag2;
			return result;
		}
	}
	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 sealed class HarvestPacing
	{
		public const float DefaultRetryCooldownSeconds = 60f;

		public const int DefaultMaxCyclesPerSession = 9;

		public const int DefaultMaxCooldownRearmsPerEpisode = 3;

		private float _nextAt;

		private string _scene;

		private int _rearms;

		private bool _parkWarned;

		public float RetryCooldownSeconds { get; }

		public int MaxCyclesPerSession { get; }

		public int MaxCooldownRearmsPerEpisode { get; }

		public bool HasAttempted => _scene != null;

		public string LastScene => _scene;

		public HarvestPacing(float retryCooldownSeconds = 60f, int maxCyclesPerSession = 9, int maxCooldownRearmsPerEpisode = 3)
		{
			RetryCooldownSeconds = retryCooldownSeconds;
			MaxCyclesPerSession = maxCyclesPerSession;
			MaxCooldownRearmsPerEpisode = maxCooldownRearmsPerEpisode;
		}

		public bool TryEarlyRearm(string sceneNow, float now)
		{
			if (_scene == null || SameScene(sceneNow, _scene) || now >= _nextAt)
			{
				return false;
			}
			_nextAt = 0f;
			_rearms = 0;
			_parkWarned = false;
			return true;
		}

		public bool IsCooldownRearm(string sceneNow)
		{
			if (_scene != null)
			{
				return SameScene(sceneNow, _scene);
			}
			return false;
		}

		private static bool SameScene(string a, string b)
		{
			return string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
		}

		public bool Allowed(int cyclesThisSession, string sceneNow)
		{
			if (cyclesThisSession < MaxCyclesPerSession)
			{
				if (IsCooldownRearm(sceneNow))
				{
					return _rearms < MaxCooldownRearmsPerEpisode;
				}
				return true;
			}
			return false;
		}

		public bool TryPark(bool allowed, bool wantsBody, float now)
		{
			if (allowed || _parkWarned || !wantsBody || now < _nextAt)
			{
				return false;
			}
			_parkWarned = true;
			_nextAt = float.MaxValue;
			return true;
		}

		public bool ReadyToAttempt(bool allowed, bool wantsBody, float now)
		{
			if (allowed && wantsBody)
			{
				return now >= _nextAt;
			}
			return false;
		}

		public void NoteAttempt(string sceneNow, float now)
		{
			if (IsCooldownRearm(sceneNow))
			{
				_rearms++;
			}
			_nextAt = now + RetryCooldownSeconds;
			_scene = sceneNow;
		}
	}
	public static class HostilityRepair
	{
		public const float DefaultCooldownSeconds = 5f;

		public const float CheckerSilentSeconds = 2f;

		public static bool IsStale(bool exists, bool alive)
		{
			if (exists)
			{
				return !alive;
			}
			return true;
		}

		public static bool CheckerWedged(bool coroutineLive, int engagedCount, float now, float lastSignalAt, float silentSeconds)
		{
			if (!coroutineLive)
			{
				return false;
			}
			if (engagedCount <= 0)
			{
				return false;
			}
			if (lastSignalAt < 0f)
			{
				return false;
			}
			if (now < lastSignalAt)
			{
				return false;
			}
			return now - lastSignalAt >= silentSeconds;
		}

		public static int OverhangToTrim(int parallelCount, int checkingCount)
		{
			if (parallelCount <= checkingCount)
			{
				return 0;
			}
			return parallelCount - checkingCount;
		}

		public static bool RepairDue(float now, float lastRepairAt, float cooldownSeconds)
		{
			if (lastRepairAt < 0f)
			{
				return true;
			}
			if (cooldownSeconds <= 0f)
			{
				return true;
			}
			if (now < lastRepairAt)
			{
				return true;
			}
			return now - lastRepairAt >= cooldownSeconds;
		}
	}
	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)
			{
				if (double.IsNaN(num) || num > 2147483647.0 || num < -2147483648.0)
				{
					key = default(ItemKey);
					return false;
				}
				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))
			{

plugins/CompanionKit.dll

Decompiled 6 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
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 AggroKit;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CompanionKit.Core;
using DonorKit;
using ForgeKit;
using HarmonyLib;
using NetKit;
using NetKit.Core;
using Photon;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[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.4.20.0")]
[assembly: AssemblyInformationalVersion("0.4.20+091b206910305beb491301afe1db01b8cd7b8e72")]
[assembly: AssemblyProduct("CompanionKit")]
[assembly: AssemblyTitle("CompanionKit")]
[assembly: AssemblyMetadata("BuildStamp", "091b2069 2026-08-28")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace CompanionKit;

public static class AnchorAnimSpy
{
	private const float IntervalSeconds = 2f;

	private static float _nextAt;

	private static readonly string[] StateProbes = new string[15]
	{
		"Attack", "Attack1", "Attack2", "AttackA", "AttackB", "Idle", "Move", "Movement", "Locomotion", "Run",
		"Walk", "Block", "Death", "Unsheathe", "Sheathe"
	};

	private static ModLog Log => CompanionRuntime.Log;

	internal static void Tick()
	{
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		ConfigEntry<bool> anchorAnimSpy = CkConfig.Effigy.AnchorAnimSpy;
		if (anchorAnimSpy == null || !anchorAnimSpy.Value || Time.unscaledTime < _nextAt)
		{
			return;
		}
		_nextAt = Time.unscaledTime + 2f;
		CharacterManager instance = CharacterManager.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			return;
		}
		int num = 0;
		try
		{
			foreach (Character value in instance.Characters.Values)
			{
				if (!((Object)(object)value == (Object)null) && value.Alive && AnchorSentinel.IsAnchorUid(UID.op_Implicit(value.UID)) && !CompanionAnchor.IsAnchor(value))
				{
					DumpAnchor(value);
					num++;
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("[ANIMSPY] enumeration threw: " + ex.Message));
			return;
		}
		if (num == 0)
		{
			Log.LogMessage((object)"[ANIMSPY] no foreign anchor replicas on this machine right now (run this on the NON-owner during a pet fight — the owner's own anchor is skipped).");
		}
	}

	private static void DumpAnchor(Character c)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0174: Unknown result type (might be due to invalid IL or missing references)
		AnchorSentinel.TryParseOwner(UID.op_Implicit(c.UID), out var ownerUid);
		string text = CompanionAnchor.ViewIdOf(c);
		Animator componentInChildren = ((Component)c).GetComponentInChildren<Animator>(true);
		if ((Object)(object)componentInChildren == (Object)null)
		{
			Log.LogMessage((object)("[ANIMSPY] anchor viewID=" + text + " owner='" + ownerUid + "': NO Animator found under the replica."));
			return;
		}
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("[ANIMSPY] anchor viewID=" + text + " owner='" + ownerUid + "' controller=" + (((Object)(object)componentInChildren.runtimeAnimatorController != (Object)null) ? ((Object)componentInChildren.runtimeAnimatorController).name : "none") + " " + $"layers={componentInChildren.layerCount}");
		stringBuilder.Append(" | params: ");
		AnimatorControllerParameter[] parameters = componentInChildren.parameters;
		if (parameters == null || parameters.Length == 0)
		{
			stringBuilder.Append("none");
		}
		else
		{
			for (int i = 0; i < parameters.Length; i++)
			{
				AnimatorControllerParameter val = parameters[i];
				if (i > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(val.name).Append('=').Append(ParamValue(componentInChildren, val));
			}
		}
		Log.LogMessage((object)stringBuilder.ToString());
		for (int j = 0; j < componentInChildren.layerCount; j++)
		{
			AnimatorStateInfo currentAnimatorStateInfo = componentInChildren.GetCurrentAnimatorStateInfo(j);
			StringBuilder stringBuilder2 = new StringBuilder();
			stringBuilder2.Append($"[ANIMSPY]   layer {j} '{componentInChildren.GetLayerName(j)}' state fullPathHash={((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash} " + $"shortNameHash={((AnimatorStateInfo)(ref currentAnimatorStateInfo)).shortNameHash} normTime={((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime:F2} " + $"inTransition={componentInChildren.IsInTransition(j)} loop={((AnimatorStateInfo)(ref currentAnimatorStateInfo)).loop}");
			string text2 = ProbeNames(componentInChildren, j);
			if (text2.Length > 0)
			{
				stringBuilder2.Append(" matches: ").Append(text2);
			}
			Log.LogMessage((object)stringBuilder2.ToString());
		}
	}

	private static string ParamValue(Animator anim, AnimatorControllerParameter p)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Expected I4, but got Unknown
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Invalid comparison between Unknown and I4
		AnimatorControllerParameterType type = p.type;
		switch (type - 1)
		{
		default:
			if ((int)type != 9)
			{
				break;
			}
			return anim.GetBool(p.nameHash) + "(trig)";
		case 0:
			return anim.GetFloat(p.nameHash).ToString("F2") + "(f)";
		case 2:
			return anim.GetInteger(p.nameHash) + "(i)";
		case 3:
			return anim.GetBool(p.nameHash) + "(b)";
		case 1:
			break;
		}
		return "?";
	}

	private static string ProbeNames(Animator anim, int layer)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		StringBuilder stringBuilder = new StringBuilder();
		AnimatorStateInfo currentAnimatorStateInfo = anim.GetCurrentAnimatorStateInfo(layer);
		string[] stateProbes = StateProbes;
		foreach (string text in stateProbes)
		{
			if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsName(text))
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append('/');
				}
				stringBuilder.Append(text);
			}
		}
		return stringBuilder.ToString();
	}
}
[HarmonyPatch(typeof(Character), "SendPerformAttackTrivial", new Type[]
{
	typeof(int),
	typeof(int),
	typeof(bool)
})]
internal static class AnchorAttackMirror
{
	internal static int RpcSwings;

	[HarmonyPostfix]
	private static void Postfix(Character __instance, int _type)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!((Object)(object)__instance == (Object)null) && AnchorSentinel.IsAnchorUid(UID.op_Implicit(__instance.UID)) && CompanionEffigy.TryGetBodyForAnchor(__instance, out var body))
			{
				EffigySwingMirror component = ((Component)body).GetComponent<EffigySwingMirror>();
				if (!((Object)(object)component == (Object)null))
				{
					RpcSwings++;
					component.Mirror(_type);
				}
			}
		}
		catch (Exception ex)
		{
			ModLog log = CompanionRuntime.Log;
			if (log != null)
			{
				log.LogWarning((object)("[PIN] attack mirror threw (swallowed): " + ex.Message));
			}
		}
	}
}
internal static class WeaponNeuter
{
	private static readonly FieldInfo BaseDamageField = typeof(Weapon).GetField("m_baseDamage", BindingFlags.Instance | BindingFlags.NonPublic);

	internal static void Apply(Weapon w)
	{
		if ((Object)(object)w == (Object)null)
		{
			return;
		}
		DamageList damage = w.Damage;
		if (damage != null)
		{
			damage.Clear();
		}
		if (!(BaseDamageField == null))
		{
			object? value = BaseDamageField.GetValue(w);
			DamageList val = (DamageList)((value is DamageList) ? value : null);
			if (val != null)
			{
				val.Clear();
			}
		}
	}
}
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
			{
				WeaponNeuter.Apply(currentWeapon);
				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 static class AnchorSentinel
{
	public const string Prefix = "CKA1:";

	private const int SuffixLen = 8;

	public static string MakeUid(string ownerUid)
	{
		return "CKA1:" + Guid.NewGuid().ToString("N").Substring(0, 8) + ":" + ownerUid;
	}

	public static bool IsAnchorUid(string uid)
	{
		if (!string.IsNullOrEmpty(uid))
		{
			return uid.StartsWith("CKA1:", StringComparison.Ordinal);
		}
		return false;
	}

	public static bool TryParseOwner(string uid, out string ownerUid)
	{
		ownerUid = null;
		if (!IsAnchorUid(uid) || uid.Length < "CKA1:".Length + 8 + 1)
		{
			return false;
		}
		ownerUid = uid.Substring("CKA1:".Length + 8 + 1);
		return true;
	}
}
[HarmonyPatch(typeof(CharacterManager), "InstantiateNetworkCharacter")]
internal static class AnchorRecognizer
{
	[HarmonyPostfix]
	private static void Postfix(GameObject __result, string _uid)
	{
		if ((Object)(object)__result == (Object)null || !AnchorSentinel.IsAnchorUid(_uid))
		{
			return;
		}
		Character component = __result.GetComponent<Character>();
		if ((Object)(object)component == (Object)null)
		{
			return;
		}
		if ((Object)(object)Plugin.Instance == (Object)null)
		{
			ModLog log = CompanionRuntime.Log;
			if (log != null)
			{
				log.LogWarning((object)("[ANCHOR] sentinel anchor recognized with no plugin instance (uid '" + _uid + "') — dressing/defuse SKIPPED; this was believed structurally impossible, escalate."));
			}
		}
		else
		{
			((MonoBehaviour)Plugin.Instance).StartCoroutine(AnchorReplicaDress.DressReplica(component, _uid));
			CompanionEffigy.PokeReconcile();
		}
	}
}
internal static class AnchorReplicaDress
{
	private static readonly WaitForSeconds _sweepWait = new WaitForSeconds(0.5f);

	private const int ScopeCommitSweeps = 4;

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

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

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

	private static readonly ICompanionSettings _fallback = new CompanionSettingsDefaults();

	private static ICompanionSettings Cfg => CompanionRuntime.Fallback ?? _fallback;

	internal static float NetLerpSpeed => Mathf.Clamp(CkConfig.Effigy.AnchorNetLerpSpeed?.Value ?? 4f, 0f, 20f);

	internal static float NetMoveSpeed => Mathf.Clamp(CkConfig.Effigy.AnchorNetMoveSpeed?.Value ?? 1f, 0f, 10f);

	internal static IEnumerator DressReplica(Character anchor, string uid)
	{
		yield return null;
		yield return null;
		if ((Object)(object)anchor == (Object)null || CompanionAnchor.IsAnchor(anchor))
		{
			yield break;
		}
		AnchorSentinel.TryParseOwner(uid, out var ownerUid);
		AnchorReplicaPlan plan = AnchorReplicaPolicy.For(false);
		string censusHeld = StatusDisplayCensus(anchor);
		bool weaponNeutered = false;
		bool barKilled = false;
		bool lifetimeCancelled = false;
		bool nccRetuned = false;
		bool scopeCommitted = false;
		bool correctionLogged = false;
		bool weaponNeuterPending = false;
		int sweeps = 0;
		string lastViewId = "n/a";
		float bakedFuse = 0f;
		string neuterError = null;
		int hidden = 0;
		int muted = 0;
		int stopped = 0;
		float logAt = 0f;
		while ((Object)(object)anchor != (Object)null)
		{
			ICompanionSettings cfg = Cfg;
			bool flag = CompanionEffigy.IsLocalOwner(ownerUid);
			lastViewId = CompanionAnchor.ViewIdOf(anchor);
			if (!scopeCommitted)
			{
				sweeps++;
				if (flag || OwnerResolvable(ownerUid) || sweeps >= 4)
				{
					plan = AnchorReplicaPolicy.For(flag);
					scopeCommitted = true;
					if (flag)
					{
						CompanionRuntime.Log.LogMessage((object)("[ANCHOR] own-pet companion anchor replica recognized (owner UID '" + ownerUid + "', viewID=" + CompanionAnchor.ViewIdOf(anchor) + ") — this machine OWNS that pet and its anchor is proxied on the master; running the same convergent dressing (hide/mute/neuter/health-bar/collision) on the replica."));
					}
					else
					{
						CompanionRuntime.Log.LogMessage((object)("[ANCHOR] foreign companion anchor recognized (owner UID '" + ownerUid + "', viewID=" + CompanionAnchor.ViewIdOf(anchor) + ") — running convergent dressing (hide/mute/neuter/health-bar/collision) on this machine."));
					}
					CompanionRuntime.Log.LogMessage((object)("[ANCHOR] " + plan.LogScope + " status-display census: " + censusHeld));
				}
			}
			else if (flag != plan.LocalOwner)
			{
				plan = AnchorReplicaPolicy.For(flag);
				if (!correctionLogged)
				{
					correctionLogged = true;
					CompanionRuntime.Log.LogMessage((object)("[ANCHOR] anchor replica ownership re-resolved for owner UID '" + ownerUid + "' (viewID=" + CompanionAnchor.ViewIdOf(anchor) + ") → " + (flag ? "OWN pet (proxied on the master)" : "foreign") + " — the recognition line above said otherwise (the owner Character resolved late); the dressing itself is identical either way. Logged once per replica."));
				}
			}
			string logScope = plan.LogScope;
			if (anchor.Lifetime > 0f)
			{
				bakedFuse = anchor.Lifetime;
				anchor.Lifetime = -1f;
			}
			if (bakedFuse > 0f && !lifetimeCancelled && scopeCommitted)
			{
				lifetimeCancelled = true;
				CompanionRuntime.Log.LogMessage((object)($"[ANCHOR] {logScope} lifetime neutralized (baked {bakedFuse:F0}s " + "summon self-despawn cancelled on this replica — the countdown runs per-machine and the owner's neutralization does not replicate)."));
			}
			float netLerpSpeed = NetLerpSpeed;
			float netMoveSpeed = NetMoveSpeed;
			if (netLerpSpeed > 0f || netMoveSpeed > 0f)
			{
				CharacterControl characterControl = anchor.CharacterControl;
				NetworkCharacterControl val = (NetworkCharacterControl)(object)((characterControl is NetworkCharacterControl) ? characterControl : null);
				if ((Object)(object)val != (Object)null)
				{
					if (netLerpSpeed > 0f)
					{
						val.LerpSpeed = netLerpSpeed;
					}
					if (netMoveSpeed > 0f)
					{
						val.MoveSpeed = netMoveSpeed;
					}
					if (!nccRetuned && scopeCommitted)
					{
						nccRetuned = true;
						CompanionRuntime.Log.LogMessage((object)("[ANCHOR] " + logScope + " net convergence retuned " + $"(LerpSpeed={netLerpSpeed:F2}, MoveSpeed={netMoveSpeed:F2}; vanilla 1/0.2) — " + "the replica now tracks its streamed position closely enough for a pinned body."));
					}
				}
			}
			if (cfg.AnchorInvisible)
			{
				((Component)anchor).GetComponentsInChildren<Renderer>(true, _rendererBuf);
				foreach (Renderer item in _rendererBuf)
				{
					if (item.enabled)
					{
						item.enabled = false;
						hidden++;
					}
				}
				((Component)anchor).GetComponentsInChildren<ParticleSystem>(true, _particleBuf);
				foreach (ParticleSystem item2 in _particleBuf)
				{
					if (item2.isPlaying || item2.particleCount > 0)
					{
						item2.Stop(true, (ParticleSystemStopBehavior)0);
						stopped++;
					}
				}
			}
			if (cfg.SpeciesVoice)
			{
				((Component)anchor).GetComponentsInChildren<AudioSource>(true, _audioBuf);
				foreach (AudioSource item3 in _audioBuf)
				{
					if (!item3.mute)
					{
						item3.mute = true;
						muted++;
					}
				}
			}
			Weapon currentWeapon = anchor.CurrentWeapon;
			if ((Object)(object)currentWeapon != (Object)null)
			{
				if (cfg.AnchorInvisible)
				{
					Renderer[] componentsInChildren = ((Component)currentWeapon).GetComponentsInChildren<Renderer>(true);
					foreach (Renderer val2 in componentsInChildren)
					{
						if (val2.enabled)
						{
							val2.enabled = false;
							hidden++;
						}
					}
				}
				if (currentWeapon.Damage != null && currentWeapon.Damage.Count > 0)
				{
					try
					{
						WeaponNeuter.Apply(currentWeapon);
						weaponNeutered = true;
						weaponNeuterPending = true;
					}
					catch (Exception ex)
					{
						neuterError = ex.Message;
					}
				}
			}
			if (neuterError != null && scopeCommitted)
			{
				CompanionRuntime.Log.LogWarning((object)("[ANCHOR] " + logScope + " weapon neuter failed: " + neuterError));
				neuterError = null;
			}
			if (weaponNeuterPending && scopeCommitted)
			{
				weaponNeuterPending = false;
				CompanionRuntime.Log.LogMessage((object)("[ANCHOR] " + logScope + " weapon damage zeroed — its mirrored swings on this machine can no longer RPC phantom damage to the master."));
			}
			if (!barKilled && !cfg.AnchorShowHealthBar)
			{
				CharacterBarManager component = ((Component)anchor).GetComponent<CharacterBarManager>();
				if ((Object)(object)component != (Object)null)
				{
					component.BarDisplayPrefab = null;
					((Behaviour)component).enabled = false;
					barKilled = true;
				}
			}
			if ((int)cfg.AnchorPlayerCollision == 1)
			{
				StampPlayerPairs(anchor);
			}
			if (scopeCommitted && (hidden > 0 || muted > 0 || stopped > 0) && Time.time - logAt > 10f)
			{
				CompanionRuntime.Log.LogMessage((object)($"[ANCHOR] {logScope} dressing: hid {hidden} renderer(s), " + $"muted {muted} audio source(s), stopped {stopped} particle system(s)" + (weaponNeutered ? ", weapon zeroed" : "") + (barKilled ? ", health bar off" : "") + "."));
				logAt = Time.time;
				hidden = 0;
				muted = 0;
				stopped = 0;
			}
			yield return _sweepWait;
		}
		if (!scopeCommitted)
		{
			CompanionRuntime.Log.LogMessage((object)("[ANCHOR] anchor replica destroyed before its scope resolved " + $"(owner UID '{ownerUid}', viewID={lastViewId}, {sweeps} sweep(s)) — dressing ran, ownership never " + "resolved, so it is unknown whether this was an own-pet or a foreign anchor."));
		}
	}

	private static bool OwnerResolvable(string ownerUid)
	{
		try
		{
			if (string.IsNullOrEmpty(ownerUid) || (Object)(object)CharacterManager.Instance == (Object)null)
			{
				return false;
			}
			Character character = CharacterManager.Instance.GetCharacter(ownerUid);
			return (Object)(object)character != (Object)null && (Object)(object)character.OwnerPlayerSys != (Object)null;
		}
		catch
		{
			return false;
		}
	}

	private static string StatusDisplayCensus(Character anchor)
	{
		StringBuilder stringBuilder = new StringBuilder();
		MonoBehaviour[] componentsInChildren = ((Component)anchor).GetComponentsInChildren<MonoBehaviour>(true);
		foreach (MonoBehaviour val in componentsInChildren)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			string name = ((object)val).GetType().Name;
			if (name.IndexOf("Status", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Bond", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Bar", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Display", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(name).Append('(').Append(((Behaviour)val).enabled ? "on" : "off")
					.Append(')');
			}
		}
		if (stringBuilder.Length != 0)
		{
			return stringBuilder.ToString();
		}
		return "none";
	}

	private static void StampPlayerPairs(Character anchor)
	{
		CharacterManager instance = CharacterManager.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			return;
		}
		Collider characterController = (Collider)(object)anchor.CharacterController;
		Collider charMoveBlockCollider = (Collider)(object)anchor.CharMoveBlockCollider;
		for (int i = 0; i < instance.PlayerCharacters.Count; i++)
		{
			Character character = instance.GetCharacter(instance.PlayerCharacters.Values[i]);
			if (!((Object)(object)character == (Object)null) && !((Object)(object)character == (Object)(object)anchor))
			{
				Stamp(characterController, (Collider)(object)character.CharMoveBlockCollider);
				Stamp((Collider)(object)character.CharacterController, charMoveBlockCollider);
				Stamp(characterController, (Collider)(object)character.CharacterController);
				Stamp(charMoveBlockCollider, (Collider)(object)character.CharMoveBlockCollider);
			}
		}
	}

	private static void Stamp(Collider a, Collider b)
	{
		if (!((Object)(object)a == (Object)null) && !((Object)(object)b == (Object)null) && a.enabled && b.enabled && ((Component)a).gameObject.activeInHierarchy && ((Component)b).gameObject.activeInHierarchy && !Physics.GetIgnoreCollision(a, b))
		{
			Physics.IgnoreCollision(a, b, true);
		}
	}
}
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 _zeroHost;

	private int _zeroReads;

	private bool _zeroWarned;

	private float _zeroFirstReadAt;

	private bool _acceptedZeroBaseline;

	private StatApplyGate _notedGate;

	private Character _vitalsHost;

	private float _healthFraction = 1f;

	private Func<bool> _healthEnabled;

	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 StatApplyGate LastGate { get; private set; }

	public bool VitalsPending { get; private set; }

	private bool HealthPersistenceOn
	{
		get
		{
			if (_healthEnabled != null)
			{
				return _healthEnabled();
			}
			return false;
		}
	}

	public float HealthFraction => _healthFraction;

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

	public void ForgetAnchor()
	{
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		_statsHost = null;
		_statBaseline = null;
		_appliedWant = null;
		_vitalsHost = null;
		_zeroHost = null;
		_zeroReads = 0;
		_zeroWarned = false;
		_zeroFirstReadAt = 0f;
		_acceptedZeroBaseline = false;
		_notedGate = (StatApplyGate)0;
		LastGate = (StatApplyGate)0;
		VitalsPending = false;
	}

	private void Gate(StatApplyGate gate)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		LastGate = gate;
		if (gate != _notedGate)
		{
			_notedGate = gate;
			if (PendingApplyPolicy.IsPending(gate))
			{
				CompanionRuntime.Log.LogMessage((object)(TagStats + " species-stat apply WAITING: " + PendingApplyPolicy.Describe(gate) + " — retrying per frame (V88 breadcrumb)."));
			}
		}
	}

	public void ApplyCreatureStats(CreatureAttributes eff)
	{
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_033f: Unknown result type (might be due to invalid IL or missing references)
		//IL_034a: Expected O, but got Unknown
		if (!HasLiveAnchor)
		{
			Gate((StatApplyGate)4);
			return;
		}
		CharacterStats stats = Current.Stats;
		if ((Object)(object)stats == (Object)null)
		{
			Gate((StatApplyGate)5);
			return;
		}
		Stat[] array = Targets(stats);
		if (_statsHost != Current)
		{
			float[] array2 = new float[array.Length];
			bool flag = false;
			bool flag2 = false;
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i] != null)
				{
					flag2 = true;
					array2[i] = array[i].BaseValue;
					if (Mathf.Abs(array2[i]) > 0.001f)
					{
						flag = true;
					}
				}
			}
			if (flag2 && !flag)
			{
				if (_zeroHost != Current)
				{
					_zeroHost = Current;
					_zeroReads = 0;
					_zeroWarned = false;
					_zeroFirstReadAt = Time.unscaledTime;
				}
				_zeroReads++;
				if ((int)ZeroBaselinePolicy.Decide(_zeroReads, (double)(Time.unscaledTime - _zeroFirstReadAt), 8, 1.0) == 0)
				{
					if (!_zeroWarned)
					{
						_zeroWarned = true;
						CompanionRuntime.Log.LogWarning((object)(TagStats + " the anchor's stat baseline read ALL ZERO — its CharacterStats are not initialised yet. Skipping the species-stat apply and re-snapshotting next tick (a zero baseline would double every applied stat for this anchor's life). " + $"(warned once per ANCHOR; accepting the zero baseline after {8} reads " + $"AND {1.0:F0}s, then self-correcting if it ever populates)"));
					}
					Gate((StatApplyGate)6);
					return;
				}
				_acceptedZeroBaseline = true;
				CompanionRuntime.Log.LogWarning((object)(TagStats + $" the anchor's stat baseline has read ALL ZERO {_zeroReads} times " + $"in a row over {Time.unscaledTime - _zeroFirstReadAt:F1}s — treating it as a GENUINE zero baseline and " + "applying species stats against it (V88: the old guard skipped forever here, leaving the pet on ghost defenses for good). Still watching: if these BaseValues ever populate, the baseline is re-snapshotted and the stacks re-applied against the truth."));
			}
			else
			{
				_acceptedZeroBaseline = false;
			}
			_statsHost = Current;
			_appliedWant = null;
			_statBaseline = array2;
		}
		else if (_acceptedZeroBaseline)
		{
			float[] array3 = new float[array.Length];
			bool flag3 = false;
			for (int j = 0; j < array.Length; j++)
			{
				if (array[j] != null)
				{
					array3[j] = array[j].BaseValue;
					if (Mathf.Abs(array3[j]) > 0.001f)
					{
						flag3 = true;
					}
				}
			}
			if (ZeroBaselinePolicy.ShouldResnapshotAfterAccept(_acceptedZeroBaseline, flag3))
			{
				_acceptedZeroBaseline = false;
				_statBaseline = array3;
				_appliedWant = null;
				CompanionRuntime.Log.LogWarning((object)(TagStats + " the anchor's stat baseline POPULATED after we accepted it as genuinely zero — re-snapshotting the baseline and re-applying the species stats against the real BaseValues (review F1: without this, every stat would have stayed doubled for this anchor's life)."));
			}
		}
		if (eff == null)
		{
			Gate((StatApplyGate)7);
			if (_appliedWant == null)
			{
				return;
			}
			foreach (Stat obj in array)
			{
				if (obj != null)
				{
					obj.RemoveStack("CK_SpeciesStats", false);
				}
			}
			_appliedWant = null;
			Gate((StatApplyGate)3);
			CompanionRuntime.Log.LogMessage((object)(TagStats + " species stats cleared from the anchor (ghost defaults restored)."));
			return;
		}
		float[] array4 = Wants(eff);
		if (_appliedWant != null && ApproxSame(array4, _appliedWant))
		{
			Gate((StatApplyGate)2);
			return;
		}
		int num = 0;
		for (int l = 0; l < array.Length; l++)
		{
			if (array[l] != null)
			{
				float num2 = array4[l] - _statBaseline[l];
				array[l].RemoveStack("CK_SpeciesStats", false);
				if (Mathf.Abs(num2) > 0.001f)
				{
					array[l].AddStack(new StatStack("CK_SpeciesStats", num2, (Tag[])null), false);
					num++;
				}
			}
		}
		_appliedWant = array4;
		Gate((StatApplyGate)1);
		string text = ((eff.StatusResistance > 95.001f) ? $" [statusRes CAPPED {eff.StatusResistance:F0} -> {95f:F0}: 100 is the engine's status-refusal sentinel]" : "");
		CompanionRuntime.Log.LogMessage((object)$"{TagStats} species defense applied to the anchor ({num} stat stack(s)): {AttributeCapture.Describe(eff)}{text}");
	}

	private static Stat[] Targets(CharacterStats st)
	{
		int num = 9;
		Stat[] array = (Stat[])(object)new Stat[2 * num + 4];
		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;
		array[2 * num + 3] = st.m_allStatusEffectBuildUpResistance;
		return array;
	}

	private static float[] Wants(CreatureAttributes eff)
	{
		int num = 9;
		float[] array = new float[2 * num + 4];
		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;
		array[2 * num + 3] = StatusResistPolicy.Cap(eff.StatusResistance);
		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()
	{
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Invalid comparison between Unknown and I4
		if (!HasLiveAnchor || (Object)(object)Current.Stats == (Object)null)
		{
			CompanionRuntime.Log.LogMessage((object)(TagStats + " no live anchor to dump."));
			return;
		}
		if (_appliedWant == null || _statsHost != Current)
		{
			bool flag = !PhotonNetwork.isNonMasterClientInRoom;
			CompanionRuntime.Log.LogMessage((object)(TagStats + " no species stats applied to this anchor (ghost defaults). gate=" + PendingApplyPolicy.Describe(LastGate) + ((!VitalsPending) ? "" : (flag ? "; vitals also PENDING (max health not established on this anchor)" : "; vitals proxied to the master (guest box — not locally establishable, not pending)")) + (((int)LastGate == 6) ? $"; zero-baseline reads={_zeroReads}" : "")));
			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" : ((i == 2 * num + 2) ? "barrier" : "statusRes")))));
				CompanionRuntime.Log.LogMessage((object)$"{TagStats}   {text}: baseline={_statBaseline[i]:F1} + stack={_appliedWant[i] - _statBaseline[i]:F1} -> live={array2[i].CurrentValue:F1}");
			}
		}
	}

	public void EnableHealthPersistence(Func<bool> enabled)
	{
		_healthEnabled = enabled;
	}

	public void SeedHealthFraction(float f)
	{
		_healthFraction = Mathf.Clamp01(f);
	}

	public void ResetHealthFraction()
	{
		_healthFraction = 1f;
	}

	public void CaptureHealthFraction()
	{
		if (!HealthPersistenceOn || !HasLiveAnchor || (Object)(object)_vitalsHost != (Object)(object)Current)
		{
			return;
		}
		CharacterStats stats = Current.Stats;
		if (!((Object)(object)stats == (Object)null))
		{
			float maxHealth = stats.MaxHealth;
			if (!(maxHealth <= 0f))
			{
				_healthFraction = Mathf.Clamp01(stats.CurrentHealth / maxHealth);
			}
		}
	}

	public void ApplyVitals(float maxHealth)
	{
		if (maxHealth <= 0f)
		{
			return;
		}
		if (!HasLiveAnchor)
		{
			VitalsPending = true;
			return;
		}
		CharacterStats stats = Current.Stats;
		if ((Object)(object)stats == (Object)null)
		{
			VitalsPending = true;
			return;
		}
		VitalsPending = false;
		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;
			float num = (HealthPersistenceOn ? _healthFraction : 1f);
			float num2 = AnchorVitals.RestoreHealth((double)num, (double)maxHealth);
			stats.SetHealth(num2);
			if (!Mathf.Approximately(currentHealth, num2))
			{
				CompanionRuntime.Log.LogMessage((object)($"{TagStats} fresh anchor vitals: {currentHealth:F0} -> {num2:F0} " + $"(restore {num * 100f:F0}% of max {maxHealth:F0}; bug-23 first-apply)."));
			}
		}
		else if (stats.CurrentHealth > maxHealth)
		{
			stats.SetHealth(maxHealth);
		}
	}

	public bool RestoreLiveHealth(float frac)
	{
		if (!HealthPersistenceOn || !HasLiveAnchor)
		{
			return false;
		}
		if ((Object)(object)_vitalsHost != (Object)(object)Current)
		{
			return false;
		}
		CharacterStats stats = Current.Stats;
		if ((Object)(object)stats == (Object)null)
		{
			return false;
		}
		float maxHealth = stats.MaxHealth;
		if (maxHealth <= 0f)
		{
			return false;
		}
		float num = AnchorVitals.RestoreHealth((double)frac, (double)maxHealth);
		float currentHealth = stats.CurrentHealth;
		_healthFraction = Mathf.Clamp01(frac);
		if (currentHealth <= num + 0.5f)
		{
			return false;
		}
		stats.SetHealth(num);
		CompanionRuntime.Log.LogMessage((object)($"{TagAnchor} restored live HP {currentHealth:F0} -> {num:F0} " + $"(carry {frac * 100f:F0}% of max {maxHealth:F0}; Bug 48 in-session persisting-anchor over-heal)."));
		return true;
	}

	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;
		if ((Object)(object)stats == (Object)null)
		{
			return "no live anchor 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 || (Object)(object)Current.Stats == (Object)null)
		{
			return false;
		}
		Current.Stats.SetHealth(Current.Stats.ActiveMaxHealth);
		return true;
	}

	public bool HealAmount(float amount, out bool reArmCrit, bool quiet = false)
	{
		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;
		}
		if (!quiet && stats.CurrentHealth - currentHealth > 0.005f)
		{
			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;
		if ((Object)(object)stats == (Object)null)
		{
			current = 0f;
			max = 0f;
			return false;
		}
		current = stats.CurrentHealth;
		max = stats.MaxHealth;
		return true;
	}
}
internal sealed class AnchorTargeting
{
	private readonly Func<Character> _current;

	private readonly Func<CharacterAI> _ai;

	private readonly Func<ICompanionSettings> _cfg;

	private Character _assertedLock;

	private float _unifyLogAt;

	private Character _unifySkipLogged;

	private Character Current => _current();

	private CharacterAI AI => _ai();

	private ICompanionSettings Cfg => _cfg();

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

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

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

	internal Character AssertedLock
	{
		get
		{
			return _assertedLock;
		}
		set
		{
			_assertedLock = value;
		}
	}

	internal AnchorTargeting(Func<Character> current, Func<CharacterAI> ai, Func<ICompanionSettings> cfg)
	{
		_current = current;
		_ai = ai;
		_cfg = cfg;
	}

	internal void ForgetSkipMarker()
	{
		_unifySkipLogged = null;
	}

	internal static bool IsProtectedFrom(Character attacker, Character target)
	{
		return TargetableOverrides.IsBlocked(attacker, target);
	}

	internal static Character LockedEnemy(CharacterAI ai)
	{
		Character val = (((Object)(object)ai != (Object)null && (Object)(object)ai.TargetingSystem != (Object)null) ? ai.TargetingSystem.LockedCharacter : null);
		if (!((Object)(object)val != (Object)null) || !val.Alive)
		{
			return null;
		}
		return val;
	}

	internal void Calm()
	{
		_assertedLock = null;
		_unifySkipLogged = null;
		if (HasLiveAnchor)
		{
			CalmAnchor(AI);
		}
	}

	internal void CalmAnchor(CharacterAI ai)
	{
		try
		{
			AggroTools.Calm(ai);
			DictionaryExt<string, Character> val = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.Characters : null);
			if (val == null)
			{
				return;
			}
			for (int i = 0; i < val.Count; i++)
			{
				Character val2 = val.Values[i];
				if (!((Object)(object)val2 == (Object)null) && val2.IsAI && val2.Alive && !((Object)(object)val2 == (Object)(object)Current))
				{
					CharacterAI component = ((Component)val2).GetComponent<CharacterAI>();
					if (!((Object)(object)component == (Object)null) && !((Object)(object)component.TargetingSystem == (Object)null) && !((Object)(object)component.TargetingSystem.LockedCharacter != (Object)(object)Current))
					{
						component.TargetingSystem.SetLockingPoint((LockingPoint)null);
						CompanionRuntime.Log.LogMessage((object)(TagAnchor + " released '" + val2.Name + "'s reciprocal lock on the anchor."));
					}
				}
			}
		}
		catch (Exception ex)
		{
			CompanionRuntime.Log.LogWarning((object)(TagAnchor + " calm failed: " + ex.Message));
		}
	}

	internal void PinTo(Vector3 puppetPos)
	{
		//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_0027: 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_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		if (HasLiveAnchor && !(Vector3.Distance(((Component)Current).transform.position, puppetPos) <= 4f))
		{
			Vector3 pos;
			Vector3 val = (NavProbe.SampleAtFeet(puppetPos, 1.5f, out pos) ? pos : puppetPos);
			Current.Teleport(val, Quaternion.identity);
			CompanionRuntime.Log.LogMessage((object)(TagAnchor + " combat pin: anchor -> puppet at " + ((Vector3)(ref val)).ToString("F1") + " (bug-3 fix)."));
		}
	}

	internal void UnifyLock(Character target)
	{
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		CharacterAI aI = AI;
		bool flag = (Object)(object)aI != (Object)null && (Object)(object)aI.TargetingSystem != (Object)null && (Object)(object)aI.TargetingSystem.LockedCharacter == (Object)(object)target;
		if (!AnchorGlue.ShouldAssertLock(Cfg.UnifyTargets, (Object)(object)target != (Object)null && target.Alive, HasLiveAnchor && (Object)(object)aI != (Object)null, flag))
		{
			if (flag && (Object)(object)target != (Object)null && (Object)(object)_unifySkipLogged != (Object)(object)target)
			{
				_unifySkipLogged = target;
				CompanionRuntime.Log.LogMessage((object)(TagGlue + " anchor already locked onto '" + target.Name + "' by itself — no unify write needed."));
			}
			return;
		}
		if (Vector3.Distance(((Component)Current).transform.position, ((Component)target).transform.position) > Cfg.CombatLeashDistance)
		{
			if ((Object)(object)_unifySkipLogged != (Object)(object)target)
			{
				_unifySkipLogged = target;
				CompanionRuntime.Log.LogMessage((object)(TagGlue + " unify refused: '" + target.Name + "' is beyond the combat leash — stale, not a live fight (session-27 fix)."));
			}
			return;
		}
		if (IsProtectedFrom(Current, target))
		{
			if ((Object)(object)_unifySkipLogged != (Object)(object)target)
			{
				_unifySkipLogged = target;
				CompanionRuntime.Log.LogMessage((object)(TagGlue + " unify refused: '" + target.Name + "' is PROTECTED (AggroKit override) — the anchor may not target it."));
			}
			return;
		}
		try
		{
			if ((Object)(object)target.LockingPoint == (Object)null)
			{
				if ((Object)(object)_unifySkipLogged != (Object)(object)target)
				{
					_unifySkipLogged = target;
					CompanionRuntime.Log.LogWarning((object)(TagGlue + " unify skipped: '" + target.Name + "' has no LockingPoint — the anchor cannot lock it."));
				}
			}
			else if (!((Object)(object)aI.TargetingSystem == (Object)null))
			{
				AggroTools.ForceTarget(aI, target);
				_assertedLock = target;
				if (Time.time - _unifyLogAt > 2f)
				{
					_unifyLogAt = Time.time;
					CompanionRuntime.Log.LogMessage((object)(TagGlue + " anchor lock unified onto '" + target.Name + "' (pet's combat target)."));
				}
			}
		}
		catch (Exception ex)
		{
			CompanionRuntime.Log.LogWarning((object)(TagGlue + " lock unify failed: " + ex.Message));
		}
	}
}
internal sealed class AnchorWeld
{
	private readonly Func<Character> _current;

	private readonly Func<CharacterAI> _ai;

	private readonly Func<ICompanionSettings> _cfg;

	private readonly AnchorPhysics _physics;

	private bool _glueWasEngaged;

	private bool _agentWarpNoted;

	private float _glueJumpLog;

	private Character Current => _current();

	private ICompanionSettings Cfg => _cfg();

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

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

	internal AnchorWeld(Func<Character> current, Func<CharacterAI> ai, Func<ICompanionSettings> cfg, AnchorPhysics physics)
	{
		_current = current;
		_ai = ai;
		_cfg = cfg;
		_physics = physics;
	}

	internal void ResetWarpNote()
	{
		_agentWarpNoted = false;
	}

	internal void GlueTick(CompanionBody body)
	{
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		//IL_015a: Unknown result type (might be due to invalid IL or missing references)
		//IL_015f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0161: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Unknown result type (might be due to invalid IL or missing references)
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_018c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0193: Unknown result type (might be due to invalid IL or missing references)
		//IL_019a: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)body == (Object)null))
		{
			_physics.Sync();
			bool hasLiveAnchor = HasLiveAnchor;
			CharacterAI val = (hasLiveAnchor ? _ai() : null);
			bool flag = (Object)(object)body.CombatTarget != (Object)null || (hasLiveAnchor && (Object)(object)AnchorTargeting.LockedEnemy(val) != (Object)null);
			bool flag2 = AnchorGlue.Engaged(Cfg.GlueMode, true, flag);
			if (flag2 != _glueWasEngaged)
			{
				_glueWasEngaged = flag2;
				CompanionRuntime.Log.LogMessage((object)string.Format("{0} {1} (mode={2}, combat={3}).", TagGlue, flag2 ? "engaged" : "released", Cfg.GlueMode, flag));
			}
			Vector3 position = ((Component)body).transform.position;
			float num = (hasLiveAnchor ? Vector3.Distance(((Component)Current).transform.position, position) : 0f);
			AnchorGlueAction val2 = AnchorGlue.Decide(Cfg.GlueMode, true, flag, hasLiveAnchor, !PhotonNetwork.isNonMasterClientInRoom, CompanionRuntime.IsSanePosition(position) && (!hasLiveAnchor || CompanionRuntime.IsSanePosition(((Component)Current).transform.position)), (Object)(object)val != (Object)null && (Object)(object)val.NavMeshAgent != (Object)null && val.NavMeshAgent.updatePosition, num);
			if ((int)val2 != 0 && hasLiveAnchor && !((Object)(object)Current == (Object)null))
			{
				Vector3 facingDir = body.FacingDir;
				facingDir.y = 0f;
				float num2 = default(float);
				float num3 = default(float);
				float num4 = default(float);
				AnchorGlue.WeldPosition(position.x, position.y, position.z, facingDir.x, facingDir.z, Cfg.GlueOffsetBehind, ref num2, ref num3, ref num4);
				Apply(new Vector3(num2, num3, num4), facingDir, val2, num);
			}
		}
	}

	internal void Apply(Vector3 pos, Vector3 facingFlat, AnchorGlueAction act, float sep, bool logJump = true)
	{
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: 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_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Expected I4, but got Unknown
		//IL_006d: 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)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)Current == (Object)null)
		{
			return;
		}
		CharacterAI val = _ai();
		Quaternion val2 = ((((Vector3)(ref facingFlat)).sqrMagnitude > 1E-06f) ? Quaternion.LookRotation(((Vector3)(ref facingFlat)).normalized, Vector3.up) : ((Component)Current).transform.rotation);
		switch (act - 1)
		{
		case 0:
			((Component)Current).transform.SetPositionAndRotation(pos, val2);
			_agentWarpNoted = false;
			break;
		case 1:
			try
			{
				Current.Internal_SendTeleport(pos, val2);
			}
			catch (Exception ex)
			{
				CompanionRuntime.Log.LogWarning((object)(TagGlue + " Internal_SendTeleport failed (" + ex.Message + ") — plain write instead."));
				((Component)Current).transform.SetPositionAndRotation(pos, val2);
			}
			_agentWarpNoted = false;
			_physics.Sync();
			if (logJump && Time.time - _glueJumpLog > 2f)
			{
				_glueJumpLog = Time.time;
				CompanionRuntime.Log.LogMessage((object)$"{TagGlue} closed a {sep:F1}m gap (collider-safe local move, no RPC).");
			}
			break;
		case 2:
			if ((Object)(object)val != (Object)null && (Object)(object)val.NavMeshAgent != (Object)null)
			{
				val.NavMeshAgent.Warp(pos);
				if (!_agentWarpNoted)
				{
					_agentWarpNoted = true;
					CompanionRuntime.Log.LogMessage((object)(TagGlue + " anchor AI is in far/inactive mode (agent drives the transform) — warping the agent instead of the transform."));
				}
			}
			else if (!_agentWarpNoted)
			{
				_agentWarpNoted = true;
				CompanionRuntime.Log.LogWarning((object)(TagGlue + " AgentWarp wanted but the anchor has no CharacterAI/NavMeshAgent — nothing warped (weld skipped this frame)."));
			}
			break;
		}
	}
}
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)
	{
		return From(src, null);
	}

	public static CreatureAttributes From(Character src, CompanionHost host)
	{
		if ((Object)(object)src == (Object)null)
		{
			return null;
		}
		ModLog val = host?.Log ?? CompanionRuntime.Log;
		string text = CompanionRuntime.Tag("STATS", host?.Settings);
		try
		{
			CreatureAttributes val2 = Read(src);
			if (val2 != null)
			{
				float num = CoopHealthDivisor(src);
				if (num != 1f)
				{
					val2.MaxHealth = CoopCaptureNormalizer.Normalize(val2.MaxHealth, num);
				}
				val.LogMessage((object)(text + " captured '" + src.Name + "': " + Describe(val2) + ((num != 1f) ? $" coopDiv={num:F2} (guest capture normalized to the solo baseline — MP-CAPNORM)" : "")));
			}
			return val2;
		}
		catch (Exception ex)
		{
			val.LogWarning((object)(text + " 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_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: 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),
			StatusResistance = BaseOf(stats.m_allStatusEffectBuildUpResistance)
		};
		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 CoopHealthDivisor(Character src)
	{
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!PhotonNetwork.isNonMasterClientInRoom)
			{
				return 1f;
			}
			CharacterStats val = (((Object)(object)src != (Object)null) ? src.Stats : null);
			CoopStats val2 = (((Object)(object)val != (Object)null) ? val.CoopStats : null);
			if ((Object)(object)val2 == (Object)null || val2.StatData == null)
			{
				return 1f;
			}
			int num = (((Object)(object)Global.Lobby != (Object)null && Global.Lobby.PlayersInLobby != null) ? Global.Lobby.PlayersInLobby.Count : 0);
			CoopStatData[] statData = val2.StatData;
			foreach (CoopStatData val3 in statData)
			{
				object obj;
				if (val3 == null)
				{
					obj = null;
				}
				else
				{
					TagSourceSelector stat = val3.Stat;
					obj = ((stat != null) ? stat.Tag.TagName : null);
				}
				string text = (string)obj;
				if (text != null && text.Contains("MaxHealth"))
				{
					return CoopCaptureNormalizer.Divisor(true, num, val3.Value);
				}
			}
			return 1f;
		}
		catch
		{
			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}");
		}
		if (a.StatusResistance != 0f)
		{
			stringBuilder.Append($" statusRes={a.StatusResistance: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 sealed class BodyAcquisitionSpec
{
	public CompanionHost Host;

	public MonoBehaviour Runner;

	public string Noun = "companion";

	public Func<Companion> Live;

	public Func<Character> Player;

	public Func<string> SpeciesId;

	public Func<bool> IsGhost;

	public Action<CompanionBody, bool> OnBodyBuilt;

	public Func<Character, CompanionBody> TryBuildNearby;

	public bool UseTemplateCache;

	public bool UseDonorHarvest;

	public bool UseGhostStandIn;

	public Func<string, string> RangedCaptureFilter;

	public Func<string, int> RangedCaptureSkillId;

	public float HarvestRetryCooldownSeconds = 60f;
}
public sealed class BodyAcquisition
{
	private readonly BodyAcquisitionSpec _spec;

	public bool Active { get; private set; }

	private ModLog Log => _spec.Host.Log;

	public BodyAcquisition(BodyAcquisitionSpec spec)
	{
		if (spec == null)
		{
			throw new ArgumentNullException("spec");
		}
		if (spec.Host == null || (Object)(object)spec.Runner == (Object)null || spec.Live == null || spec.Player == null || spec.SpeciesId == null || spec.OnBodyBuilt == null)
		{
			throw new ArgumentException("BodyAcquisitionSpec: Host/Runner/Live/Player/SpeciesId/OnBodyBuilt are required.");
		}
		_spec = spec;
	}

	public void Kick()
	{
		if (!Active && _spec.Live() != null)
		{
			_spec.Runner.StartCoroutine(Run());
		}
	}

	private bool GhostNow()
	{
		if (_spec.IsGhost != null)
		{
			return _spec.IsGhost();
		}
		return false;
	}

	private string Filter(string species)
	{
		return _spec.RangedCaptureFilter?.Invoke(species);
	}

	private int SkillId(string species)
	{
		return _spec.RangedCaptureSkillId?.Invoke(species) ?? 0;
	}

	public IEnumerator Run()
	{
		Companion c = _spec.Live();
		if (c == null)
		{
			yield break;
		}
		Active = true;
		bool triedStandIn = false;
		float voidHoldLogAt = -999f;
		float expeditionHoldLogAt = -999f;
		float expeditionHoldSince = -1f;
		HarvestPacing pacing = new HarvestPacing(_spec.HarvestRetryCooldownSeconds, 9, 3);
		try
		{
			while (_spec.Live() == c && ReformFlow.WantsBodyUpgrade((Object)(object)c.Body != (Object)null, GhostNow()))
			{
				if (ExpeditionHarvest.InProgress)
				{
					if (expeditionHoldSince < 0f)
					{
						expeditionHoldSince = Time.unscaledTime;
					}
					if (Time.unscaledTime - expeditionHoldLogAt > 15f)
					{
						expeditionHoldLogAt = Time.unscaledTime;
						float num = Time.unscaledTime - expeditionHoldSince;
						Log.LogMessage((object)("[PERSIST] re-form ladder holding: an expedition owns the scene " + $"pipeline (elapsed={num:F0}s) — no body built this pass." + ((num > 120f) ? " This is far longer than a harvest takes; if it does not clear, the expedition flag is WEDGED — run the 'expeditionreset' verb." : "")));
					}
					yield return (object)new WaitForSeconds(1.5f);
					continue;
				}
				expeditionHoldSince = -1f;
				Character player = _spec.Player();
				if ((Object)(object)player != (Object)null && !CompanionRuntime.IsSanePosition(((Component)player).transform.position))
				{
					if (Time.unscaledTime - voidHoldLogAt > 10f)
					{
						voidHoldLogAt = Time.unscaledTime;
						Log.LogMessage((object)("[PERSIST] re-form ladder holding: owner reads as void/staging " + $"(floor y={-3000f:F0}) — no body built this pass."));
					}
					yield return (object)new WaitForSeconds(1.5f);
					continue;
				}
				if ((Object)(object)player != (Object)null)
				{
					if (_spec.TryBuildNearby != null)
					{
						CompanionBody companionBody = _spec.TryBuildNearby(player);
						if ((Object)(object)companionBody != (Object)null)
						{
							_spec.OnBodyBuilt.Invoke(companionBody, false);
							break;
						}
					}
					string species = _spec.SpeciesId();
					if (_spec.UseTemplateCache && BodyTemplateCache.TryResolve(species, out var template))
					{
						CompanionBody companionBody2 = BodyTemplateCache.PuppetFrom(template, player, Filter(species), SkillId(species));
						if ((Object)(object)companionBody2 != (Object)null)
						{
							Log.LogMessage((object)("[PERSIST] re-forming '" + species + "' from the session body-template cache."));
							_spec.OnBodyBuilt.Invoke(companionBody2, false);
							break;
						}
					}
					if (_spec.UseDonorHarvest)
					{
						Scene activeScene = SceneManager.GetActiveScene();
						string name = ((Scene)(ref activeScene)).name;
						string lastScene = pacing.LastScene;
						if (pacing.TryEarlyRearm(name, Time.time))
						{
							Log.LogMessage((object)("[PERSIST] harvest rung re-armed early: scene changed '" + lastScene + "' → '" + name + "' (fresh region-aware donor order)."));
						}
						bool flag = pacing.Allowed(DonorHarvest.CyclesThisSession, name);
						bool flag2 = (Object)(object)c.Body == (Object)null || GhostNow();
						if (pacing.TryPark(flag, flag2, Time.time))
						{
							Log.LogMessage((object)("[PERSIST] harvest rung PARKED for '" + species + "' — " + ((DonorHarvest.CyclesThisSession >= pacing.MaxCyclesPerSession) ? $"session additive-cycle budget spent ({DonorHarvest.CyclesThisSession} cycles; the LightProbes crash ceiling is ~11-17). A relaunch resets it." : "3 failed retries this scene; a scene change re-arms it.")));
						}
						if (pacing.ReadyToAttempt(flag, flag2, Time.time) && DonorHarvest.TryGetDonorScenes(species, out var sceneNames, out var searchTerm))
						{
							if (pacing.HasAttempted)
							{
								Log.LogMessage((object)("[PERSIST] harvest rung re-armed (" + ((name != pacing.LastScene) ? "scene change" : $"{_spec.HarvestRetryCooldownSeconds:F0}s cooldown") + ") — retrying the donor chain for '" + species + "'" + (GhostNow() ? " (upgrading the ghost stand-in)" : "") + "."));
							}
							pacing.NoteAttempt(name, Time.time);
							CompanionBody harvested = null;
							Log.LogMessage((object)("[PERSIST] no wild '" + species + "' nearby — harvesting a body (" + (PhotonNetwork.isNonMasterClientInRoom ? "guest-local" : "master") + ", " + $"{sceneNames.Count} donor candidate(s), region-aware order)."));
							yield return DonorHarvest.HarvestChain(sceneNames, searchTerm, player, delegate(CompanionBody b)
							{
								harvested = b;
							}, Filter(species), SkillId(species));
							if (_spec.Live() != c)
							{
								if ((Object)(object)harvested != (Object)null)
								{
									Object.Destroy((Object)(object)((Component)harvested).gameObject);
								}
								Log.LogMessage((object)("[LIFECYCLE] " + _spec.Noun + " despawned during re-form: destroying in-flight body." + (((Object)(object)harvested != (Object)null) ? $" (body#{harvested.BodyId})" : "")));
								break;
							}
							if ((Object)(object)harvested != (Object)null)
							{
								_spec.OnBodyBuilt.Invoke(harvested, false);
								break;
							}
							Log.LogMessage((object)("[PERSIST] harvest failed — " + (GhostNow() ? "keeping the ghost stand-in" : "falling back to the ghost stand-in") + "; " + $"the rung retries in {_spec.HarvestRetryCooldownSeconds:F0}s or on a scene change (F2)."));
						}
					}
					if (_spec.UseGhostStandIn && ReformFlow.StandInGate((Object)(object)c.Body != (Object)null, triedStandIn))
					{
						triedStandIn = true;
						Character ghost = BodyFactory.SpawnGhostActive(player);
						if ((Object)(object)ghost != (Object)null)
						{
							float t0 = Time.time;
							while ((Object)(object)ghost != (Object)null && !BodyFactory.GhostVisualReady(ghost) && Time.time - t0 < 4f)
							{
								BodyFactory.NudgeGhostActive(ghost);
								yield return null;
							}
							if ((Object)(object)ghost != (Object)null && !BodyFactory.GhostVisualReady(ghost))
							{
								BodyFactory.ForceGhostVisuals(ghost);
							}
							bool flag3 = BodyFactory.GhostVisualReady(ghost);
							Log.LogMessage((object)$"[PERSIST] ghost visuals ready={flag3} after {Time.time - t0:F1}s — forming spectral stand-in.");
							CompanionBody companionBody3 = BodyFactory.FinishGhostPuppet(ghost, player);
							if (_spec.Live() != c)
							{
								if ((Object)(object)companionBody3 != (Object)null)
								{
									Object.Destroy((Object)(object)((Component)companionBody3).gameObject);
								}
								Log.LogMessage((object)("[LIFECYCLE] " + _spec.Noun + " despawned during re-form: destroying in-flight body." + (((Object)(object)companionBody3 != (Object)null) ? $" (body#{companionBody3.BodyId})" : "")));
								break;
							}
							if ((Object)(object)companionBody3 != (Object)null)
							{
								_spec.OnBodyBuilt.Invoke(companionBody3, true);
							}
						}
						else
						{
							Log.LogMessage((object)("[PERSIST] no spawnable stand-in body — " + _spec.Noun + " stays bodiless (systems tick) until you pass a wild '" + species + "'."));
						}
					}
				}
				yield return (object)new WaitForSeconds(1.5f);
			}
		}
		finally
		{
			BodyAcquisition bodyAcquisition = this;
			bodyAcquisition.Active = false;
			try
			{
				Companion companion = bodyAcquisition._spec.Live();
				if (companion != null && companion == c && (Object)(object)c.Body == (Object)null)
				{
					bodyAcquisition.Log.LogMessage((object)"[PERSIST] re-form ladder ended with no body (exception or abort) — the next Kick()/recall restarts it.");
				}
			}
			catch
			{
			}
		}
	}
}
public static class BodyCensus
{
	internal sealed class Entry
	{
		public CompanionBody Body;

		public float BornAt;

		public bool EverClaimed;

		public float UnclaimedSince;

		public bool WarnedOrphan;
	}

	private static readonly List<Entry> s_entries = new List<Entry>();

	private static readonly List<Func<IEnumerable<CompanionBody>>> s_claims = new List<Func<IEnumerable<CompanionBody>>>();

	private static int s_consumerSources;

	internal static int ConsumerSourceCount => s_consumerSources;

	internal static int SourceCount => s_claims.Count;

	public static int LiveCount => s_entries.Count;

	internal static void Register(CompanionBody b)
	{
		if ((Object)(object)b == (Object)null)
		{
			return;
		}
		for (int i = 0; i < s_entries.Count; i++)
		{
			if (s_entries[i].Body == b)
			{
				return;
			}
		}
		s_entries.Add(new Entry
		{
			Body = b,
			BornAt = Time.unscaledTime,
			UnclaimedSince = Time.unscaledTime
		});
	}

	internal static void Unregister(CompanionBody b)
	{
		for (int num = s_entries.Count - 1; num >= 0; num--)
		{
			if (s_entries[num].Body == b)
			{
				s_entries.RemoveAt(num);
			}
		}
	}

	internal static void PurgeDead()
	{
		for (int num = s_entries.Count - 1; num >= 0; num--)
		{
			if ((Object)(object)s_entries[num].Body == (Object)null)
			{
				s_entries.RemoveAt(num);
			}
		}
	}

	public static void RegisterClaimSource(Func<IEnumerable<CompanionBody>> source)
	{
		RegisterClaimSource(source, isConsumer: true);
	}

	public static void RegisterClaimSource(Func<IEnumerable<CompanionBody>> source, bool isConsumer)
	{
		if (source != null && !s_claims.Contains(source))
		{
			s_claims.Add(source);
			if (isConsumer)
			{
				s_consumerSources++;
			}
		}
	}

	internal static void RegisterInternalClaimSource(Func<IEnumerable<CompanionBody>> source)
	{
		RegisterClaimSource(source, isConsumer: false);
	}

	internal static List<Entry> Snapshot()
	{
		return new List<Entry>(s_entries);
	}

	internal static HashSet<CompanionBody> ClaimedNow()
	{
		HashSet<CompanionBody> hashSet = new HashSet<CompanionBody>();
		for (int i = 0; i < s_claims.Count; i++)
		{
			try
			{
				foreach (CompanionBody item in s_claims[i]() ?? Enumerable.Empty<CompanionBody>())
				{
					if ((Object)(object)item != (Object)null)
					{
						hashSet.Add(item);
					}
				}
			}
			catch (Exception ex)
			{
				ModLog log = CompanionRuntime.Log;
				if (log != null)
				{
					log.LogWarning((object)("[CENSUS] claim source threw (skipped this sweep): " + ex.Message));
				}
			}
		}
		return hashSet;
	}

	public static string Dump()
	{
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append($"[CENSUS] {s_entries.Count} live CompanionBody instance(s), {s_claims.Count} claim source(s) " + $"({s_consumerSources} consumer, {s_claims.Count - s_consumerSources} internal).");
		if (s_consumerSources == 0)
		{
			stringBuilder.Append("\n[CENSUS]   NO CONSUMER claim source — the reaper is disabled. A consumer plugin (Beastwhispering/Hireling) older than this CompanionKit is the usual cause.");
		}
		HashSet<CompanionBody> hashSet = ClaimedNow();
		foreach (Entry item in Snapshot())
		{
			CompanionBody body = item.Body;
			if (!((Object)(object)body == (Object)null))
			{
				NavMeshAgent agent = body._agent;
				string text;
				Vector3 val;
				if ((Object)(object)agent == (Object)null)
				{
					text = "agent=none";
				}
				else if (!((Behaviour)agent).enabled)
				{
					text = "agent=disabled";
				}
				else if (!agent.isOnNavMesh)
				{
					text = "agent=off-mesh";
				}
				else
				{
					val = agent.nextPosition;
					text = string.Format("agent=on next={0} updPos={1}", ((Vector3)(ref val)).ToString("F1"), agent.updatePosition);
				}
				string text2 = (body.StuckDetection ? string.Format(" stuck={0:F0}s{1}", body.StuckSeconds, body.StuckTripped ? " TRIPPED" : "") : "");
				string text3 = ((body.Contamination > 0) ? $" contaminated={body.Contamination}" : "");
				string text4 = $"\n[CENSUS]   body#{body.BodyId} '{body.SpeciesId}' origin={body.Origin} age={Time.unscaledTime - item.BornAt:F0}s ";
				object[] obj = new object[4]
				{
					hashSet.Contains(body),
					item.EverClaimed,
					null,
					null
				};
				val = ((Component)body).transform.position;
				obj[2] = ((Vector3)(ref val)).ToString("F1");
				obj[3] = text;
				stringBuilder.Append(text4 + string.Format("claimed={0} everClaimed={1} pos={2} {3}", obj) + text2 + text3);
			}
		}
		return stringBuilder.ToString();
	}
}
internal static class BodyDiagnostics
{
	internal static IEnumerator DriftScan(CompanionBody body)
	{
		Transform root = ((Component)body).transform;
		Transform[] all = ((Component)body).GetComponentsInChildren<Transform>(true);
		Vector3[] start = all.Select((Transform t) => root.InverseTransformPoint(t.position)).ToArray();
		float[] maxd = new float[all.Length];
		for (int f = 0; f < 60; f++)
		{
			for (int num = 0; num < all.Length; num++)
			{
				float num2 = Vector3.Distance(root.InverseTransformPoint(all[num].position), start[num]);
				if (num2 > maxd[num])
				{
					maxd[num] = num2;
				}
			}
			yield return null;
		}
		Dictionary<Transform, int> dictionary = new Dictionary<Transform, int>();
		for (int num3 = 0; num3 < all.Length; num3++)
		{
			dictionary[all[num3]] = num3;
		}
		int num4 = (from i in Enumerable.Range(0, all.Length)
			orderby maxd[i] descending
			select i).First();
		CompanionRuntime.Log.LogMessage((object)"[DRIFT] ancestry of top drifter (the root-motion bone = where drift jumps up):");
		Transform val = all[num4];
		while ((Object)(object)val != (Object)null)
		{
			int value;
			float num5 = (dictionary.TryGetValue(val, out value) ? maxd[value] : (-1f));
			CompanionRuntime.Log.LogMessage((object)$"[DRIFT]   '{((Object)val).name}' drift={num5:F2}m");
			if (!((Object)(object)val == (Object)(object)root))
			{
				val = val.parent;
				continue;
			}
			break;
		}
	}

	internal static IEnumerator PosDumpAll()
	{
		List<BodyCensus.Entry> list = BodyCensus.Snapshot();
		CompanionRuntime.Log.LogMessage((object)$"[POS] census-wide posdump: {list.Count} live body(ies).");
		List<IEnumerator> runs = new List<IEnumerator>();
		foreach (BodyCensus.Entry item in list)
		{
			if ((Object)(object)item.Body != (Object)null)
			{
				runs.Add(PosDump(item.Body));
			}
		}
		for (int f = 0; f < 90; f++)
		{
			for (int num = runs.Count - 1; num >= 0; num--)
			{
				if (!runs[num].MoveNext())
				{
					runs.RemoveAt(num);
				}
			}
			if (runs.Count == 0)
			{
				break;
			}
			yield return null;
		}
	}

	internal static IEnumerator PosDump(CompanionBody body)
	{
		for (int i = 0; i < 90; i++)
		{
			if ((Object)(object)body == (Object)null)
			{
				break;
			}
			Vector3 position = ((Component)body).transform.position;
			bool flag = (Object)(object)body._agent != (Object)null && ((Behaviour)body._agent).enabled && body._agent.isOnNavMesh;
			float value;
			string on;
			string text = ((body._loco != null && body._loco.ReadForward(out value, out on)) ? $"{value:F1}@{on}" : "n/a");
			string text2 = ((body._slope != null) ? body._slope.Describe() : "none");
			if (flag)
			{
				Vector3 nextPosition = body._agent.nextPosition;
				ModLog log = CompanionRuntime.Log;
				object[] obj = new object[10]
				{
					body.BodyId,
					i,
					position.x,
					position.z,
					Vector3.Distance(position, nextPosition),
					((Component)body).transform.eulerAngles.y,
					null,
					null,
					null,
					null
				};
				Vector3 velocity = body._agent.velocity;
				obj[6] = ((Vector3)(ref velocity)).magnitude;
				obj[7] = body._agent.isStopped;
				obj[8] = text;
				obj[9] = text2;
				log.LogMessage((object)string.Format("[POS]#{0} f{1} tf=({2:F2},{3:F2}) gap={4:F2} rotY={5:F0} vel={6:F2} stop={7} mF={8} slope={9}", obj));
			}
			else
			{
				string text3 = (((Object)(object)body._agent == (Object)null) ? "none" : ((!((Behaviour)body._agent).enabled) ? "disabled (latched/direct-drive)" : "off-mesh"));
				CompanionRuntime.Log.LogMessage((object)$"[POS]#{body.BodyId} f{i} tf=({position.x:F2},{position.z:F2}) agent={text3} rotY={((Component)body).transform.eulerAngles.y:F0} mF={text} slope={text2}");
			}
			yield return null;
		}
	}
}
public static class BodyFactory
{
	public enum EquipmentStripMode
	{
		Components,
		GameObjects
	}

	public static bool IsWildTamable(Character c)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Invalid comparison between Unknown and I4
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Invalid comparison between Unknown and I4
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)c != (Object)null && c.IsAI && c.Alive && (int)c.Faction != 1 && (int)c.Faction != 0 && (int)c.Faction != 7 && (Object)(object)c.OwnerPlayerSys == (Object)null && !CompanionAnchor.IsAnchor(c))
		{
			return !AnchorSentinel.IsAnchorUid(UID.op_Implicit(c.UID));
		}
		return false;
	}

	public static string WhyNotWildTamable(Character c)
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Invalid comparison between Unknown and I4
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Invalid comparison between Unknown and I4
		//IL_009d: Unknown result type (might be due to invalid