Decompiled source of FactionTactics Client v1.0.13

FactionTactics.Client.dll

Decompiled 3 hours ago
using System;
using System.Collections.Concurrent;
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.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Logging;
using FactionTactics.Ambience;
using FactionTactics.Combat;
using FactionTactics.ConsoleCmds;
using FactionTactics.Doctrine;
using FactionTactics.Orders;
using FactionTactics.Util;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("FactionTactics.Client")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.13.0")]
[assembly: AssemblyInformationalVersion("1.0.13+b72dd217344d7e9cda3ad9c8d0c8ee1bd7ac5931")]
[assembly: AssemblyProduct("FactionTactics.Client")]
[assembly: AssemblyTitle("FactionTactics.Client")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.13.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 FactionTactics.ConsoleCmds
{
	public static class FtConfigRpc
	{
		public const string RpcCmd = "FT_ConfigCmd";

		public const string RpcReply = "FT_ConfigReply";

		public const string RpcSync = "FT_ConfigSync";

		private static bool _registered;

		private static object? _registeredOn;

		public static Action<string>? LocalPrint { get; set; }

		public static bool IsRegistered => _registered;

		public static void EnsureRegistered()
		{
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null && (!_registered || _registeredOn != instance))
				{
					instance.Register<ZPackage>("FT_ConfigCmd", (Action<long, ZPackage>)OnCmd);
					instance.Register<ZPackage>("FT_ConfigReply", (Action<long, ZPackage>)OnReply);
					instance.Register<ZPackage>("FT_ConfigSync", (Action<long, ZPackage>)OnSync);
					_registered = true;
					_registeredOn = instance;
					FactionTacticsLog.Debug("[FT] FtConfigRpc registered FT_ConfigCmd/FT_ConfigReply/FT_ConfigSync");
				}
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("FtConfigRpc.EnsureRegistered: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		public static void SendCmd(string op, string? key = null, string? value = null)
		{
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Expected O, but got Unknown
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Expected O, but got Unknown
			EnsureRegistered();
			if (ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null)
			{
				PushLocal("[ft] not connected");
				return;
			}
			try
			{
				ZPackage val = new ZPackage();
				val.Write(op ?? "");
				val.Write(key ?? "");
				val.Write(value ?? "");
				byte[] array = val.GetArray();
				try
				{
					ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
					if (serverPeer != null && serverPeer.m_uid != 0L)
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "FT_ConfigCmd", new object[1] { (object)new ZPackage(array) });
						return;
					}
				}
				catch
				{
				}
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FT_ConfigCmd", new object[1] { (object)new ZPackage(array) });
			}
			catch (Exception ex)
			{
				PushLocal("[ft] RPC send failed: " + ex.Message);
			}
		}

		private static void OnCmd(long senderPeerId, ZPackage pkg)
		{
			if (pkg != null && !((Object)(object)ZNet.instance == (Object)null))
			{
				ZNet.instance.IsServer();
			}
		}

		private static void Reply(long peerId, string text)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			try
			{
				EnsureRegistered();
				if (ZRoutedRpc.instance != null)
				{
					ZPackage val = new ZPackage();
					val.Write(text ?? "");
					if (peerId != 0L)
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "FT_ConfigReply", new object[1] { val });
					}
				}
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("FtConfigRpc.Reply: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static void BroadcastSync()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			try
			{
				EnsureRegistered();
				if (ZRoutedRpc.instance != null)
				{
					ZPackage val = new ZPackage();
					val.Write(CombatTuning.HoldAttackCooldown);
					val.Write(CombatTuning.HoldAttackRangeFactor);
					val.Write(CombatTuning.SwingStaggerMs);
					ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FT_ConfigSync", new object[1] { val });
				}
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("FtConfigRpc.BroadcastSync: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static void OnReply(long senderPeerId, ZPackage pkg)
		{
			if (pkg == null)
			{
				return;
			}
			try
			{
				string[] array = (pkg.ReadString() ?? "").Split(new char[1] { '\n' }, StringSplitOptions.None);
				for (int i = 0; i < array.Length; i++)
				{
					PushLocal(array[i]);
				}
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("FtConfigRpc.OnReply: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static void OnSync(long senderPeerId, ZPackage pkg)
		{
			if (pkg == null)
			{
				return;
			}
			try
			{
				CombatTuning.HoldAttackCooldown = pkg.ReadSingle();
				CombatTuning.HoldAttackRangeFactor = pkg.ReadSingle();
				CombatTuning.SwingStaggerMs = pkg.ReadSingle();
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("FtConfigRpc.OnSync: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static void PushLocal(string line)
		{
			try
			{
				LocalPrint?.Invoke(line);
			}
			catch
			{
			}
			try
			{
				FactionTacticsLog.Debug(line);
			}
			catch
			{
			}
		}

		public static bool PeerIsAdmin(long peerId)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			try
			{
				if (peerId == 0L || peerId == ZNet.GetUID())
				{
					try
					{
						if (ZNet.instance.LocalPlayerIsAdminOrHost())
						{
							return true;
						}
					}
					catch
					{
					}
				}
			}
			catch
			{
			}
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "PlayerIsAdmin", new Type[1] { typeof(long) }, (Type[])null);
				if (methodInfo != null)
				{
					return (bool)methodInfo.Invoke(ZNet.instance, new object[1] { peerId });
				}
			}
			catch
			{
			}
			try
			{
				MethodInfo methodInfo2 = AccessTools.Method(typeof(ZNet), "IsAdmin", (Type[])null, (Type[])null);
				if (methodInfo2 != null)
				{
					ParameterInfo[] parameters = methodInfo2.GetParameters();
					if (parameters.Length == 1 && parameters[0].ParameterType == typeof(long))
					{
						return (bool)methodInfo2.Invoke(ZNet.instance, new object[1] { peerId });
					}
				}
			}
			catch
			{
			}
			try
			{
				ZNetPeer peer = ZNet.instance.GetPeer(peerId);
				if (peer == null)
				{
					return false;
				}
				string text = null;
				try
				{
					ISocket socket = peer.m_socket;
					text = ((socket != null) ? socket.GetHostName() : null);
				}
				catch
				{
				}
				FieldInfo fieldInfo = AccessTools.Field(typeof(ZNet), "m_adminList");
				if (fieldInfo != null && !string.IsNullOrEmpty(text))
				{
					object value = fieldInfo.GetValue(ZNet.instance);
					if (value != null)
					{
						MethodInfo methodInfo3 = AccessTools.Method(value.GetType(), "Contains", (Type[])null, (Type[])null) ?? AccessTools.Method(value.GetType(), "IsInList", (Type[])null, (Type[])null);
						if (methodInfo3 != null)
						{
							return (bool)methodInfo3.Invoke(value, new object[1] { text });
						}
					}
				}
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("PeerIsAdmin fallback: " + ex.GetType().Name + ": " + ex.Message);
			}
			return false;
		}

		public static bool LocalPlayerIsAdmin()
		{
			try
			{
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.LocalPlayerIsAdminOrHost())
				{
					return true;
				}
			}
			catch
			{
			}
			try
			{
				return PeerIsAdmin(ZNet.GetUID());
			}
			catch
			{
				return false;
			}
		}
	}
}
namespace FactionTactics.Ambience
{
	public static class DeathRushAudio
	{
		private static readonly Dictionary<int, float> LastPulseAt = new Dictionary<int, float>();

		private const float DefaultCooldown = 4.5f;

		public static void PulseIfNeeded(MonsterAI ai, MemberIntent intent)
		{
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ai == (Object)null || intent == null || !intent.DeathRush || intent.OrderKind != DoctrineOrderKind.Charge)
			{
				return;
			}
			int instanceID;
			try
			{
				instanceID = ((Object)ai).GetInstanceID();
			}
			catch
			{
				return;
			}
			float time = Time.time;
			float num = 4.5f;
			if (num < 0.5f)
			{
				num = 0.5f;
			}
			if (LastPulseAt.TryGetValue(instanceID, out var value) && time - value < num)
			{
				return;
			}
			LastPulseAt[instanceID] = time;
			try
			{
				((BaseAI)ai).Alert();
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("DeathRushAudio.Alert failed: " + ex.GetType().Name + ": " + ex.Message);
			}
			try
			{
				EffectList value2 = Traverse.Create((object)ai).Field("m_alertedEffects").GetValue<EffectList>();
				if (value2 != null && value2.HasEffects())
				{
					value2.Create(((Component)ai).transform.position, ((Component)ai).transform.rotation, (Transform)null, 1f, -1, default(ZDOID));
				}
			}
			catch (Exception ex2)
			{
				FactionTacticsLog.Debug("DeathRushAudio.m_alertedEffects failed: " + ex2.GetType().Name + ": " + ex2.Message);
			}
		}
	}
}
namespace FactionTactics.Combat
{
	public static class CombatAuthority
	{
		public static bool ShouldReleaseToVanillaUpdateAI(MemberIntent? intent)
		{
			return ShouldReleaseToVanilla(intent);
		}

		public static bool ShouldReleaseToVanilla(MemberIntent? intent)
		{
			return intent?.AllowVanillaChase ?? true;
		}

		public static bool ShouldSoleBrain(MemberIntent? intent)
		{
			if (intent != null)
			{
				return !ShouldReleaseToVanilla(intent);
			}
			return false;
		}

		public static bool ShouldAllowVanillaMoveTo(MemberIntent? intent)
		{
			return ShouldReleaseToVanilla(intent);
		}

		public static bool IsAttackReleaseOrder(DoctrineOrderKind order)
		{
			return order == DoctrineOrderKind.Charge;
		}
	}
	public static class CombatDriver
	{
		private const float MoveArriveDist = 1.5f;

		private const float AdvanceStopDist = 2f;

		private const float DefaultMeleeRange = 2.5f;

		private const float HoldGroundPinDist = 2.5f;

		private static readonly ConcurrentDictionary<long, float> NextSwingTime = new ConcurrentDictionary<long, float>();

		public static long DriveCount { get; private set; }

		public static long AttackAttempts { get; private set; }

		public static long Swings { get; private set; }

		public static long HoldBlocks { get; private set; }

		public static void ResetCounters()
		{
			DriveCount = (AttackAttempts = (Swings = (HoldBlocks = 0L)));
			NextSwingTime.Clear();
		}

		public static bool ShouldReleaseToVanilla(MemberIntent intent)
		{
			return CombatAuthority.ShouldReleaseToVanilla(intent);
		}

		public static void Drive(MonsterAI ai, MemberIntent intent, float dt)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: 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_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			DriveCount++;
			PulseDeathRushAudio(ai, intent);
			CallUpdateTarget(ai, dt);
			bool flag = IsLineFront(intent);
			if (intent.HoldGround)
			{
				if (!HoldGroundDestinationFar(ai, intent))
				{
					((BaseAI)ai).StopMoving();
					FaceThreatOrSlot(ai, intent);
					SoftSuppressHunt(ai);
					TryDriveAttack(ai, intent);
					return;
				}
				intent.HoldGround = false;
			}
			Vector3 val = intent.DesiredPosition;
			if (val == Vector3.zero && intent.AllowVanillaChase)
			{
				Character targetCreature = ((BaseAI)ai).GetTargetCreature();
				if ((Object)(object)targetCreature != (Object)null)
				{
					val = ((Component)targetCreature).transform.position;
				}
			}
			if (flag && !intent.AllowVanillaChase)
			{
				CallMoveTo((BaseAI)(object)ai, dt, val, 1.5f, intent.PreferRun);
				Vector3 position = ((Component)ai).transform.position;
				float num = position.x - val.x;
				float num2 = position.z - val.z;
				if ((float)Math.Sqrt(num * num + num2 * num2) < 2f)
				{
					((BaseAI)ai).StopMoving();
				}
				FaceThreatOrSlot(ai, intent);
				SoftSuppressHunt(ai);
				TryDriveAttack(ai, intent);
				return;
			}
			if (val != Vector3.zero)
			{
				CallMoveTo((BaseAI)(object)ai, dt, val, 1.5f, intent.PreferRun);
			}
			if (intent.PreferKeepRange && !intent.AllowVanillaChase)
			{
				Character targetCreature2 = ((BaseAI)ai).GetTargetCreature();
				if ((Object)(object)targetCreature2 != (Object)null)
				{
					CallLookAt((BaseAI)(object)ai, ((Component)targetCreature2).transform.position);
				}
			}
			else if (intent.AllowVanillaChase)
			{
				FaceThreatOrSlot(ai, intent);
			}
			TryDriveAttack(ai, intent);
		}

		public static bool IsLineFront(MemberIntent intent)
		{
			if (intent.Role != SquadRole.Front && intent.Role != SquadRole.Leader)
			{
				return false;
			}
			if (intent.Formation != FormationType.ShieldWall && intent.Formation != FormationType.Line)
			{
				return false;
			}
			if (intent.OrderKind != DoctrineOrderKind.Hold && intent.OrderKind != DoctrineOrderKind.Advance)
			{
				return intent.OrderKind == DoctrineOrderKind.ProtectMissiles;
			}
			return true;
		}

		public static void SuppressVanillaChase(MonsterAI ai, MemberIntent intent)
		{
			//IL_0035: 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_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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)
			if (intent.AllowVanillaChase)
			{
				return;
			}
			try
			{
				Traverse obj = Traverse.Create((object)ai);
				obj.Field("m_targetCreature").SetValue((object)null);
				obj.Field("m_targetStatic").SetValue((object)null);
				Vector3 val = intent.DesiredPosition;
				if (val == Vector3.zero)
				{
					val = ((Component)ai).transform.position;
				}
				obj.Field("m_lastKnownTargetPos").SetValue((object)val);
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("SuppressVanillaChase Traverse failed: " + ex.GetType().Name + ": " + ex.Message);
			}
			SoftSuppressHunt(ai);
		}

		public static void SoftSuppressHunt(MonsterAI ai)
		{
			try
			{
				if (((BaseAI)ai).HuntPlayer())
				{
					((BaseAI)ai).SetHuntPlayer(false);
				}
			}
			catch
			{
			}
		}

		private static void FaceThreatOrSlot(MonsterAI ai, MemberIntent intent)
		{
			//IL_0017: 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_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Character targetCreature = ((BaseAI)ai).GetTargetCreature();
				if ((Object)(object)targetCreature != (Object)null)
				{
					CallLookAt((BaseAI)(object)ai, ((Component)targetCreature).transform.position);
					return;
				}
			}
			catch
			{
			}
			if (intent.DesiredPosition != Vector3.zero)
			{
				CallLookAt((BaseAI)(object)ai, intent.DesiredPosition);
			}
		}

		private static void CallUpdateTarget(MonsterAI ai, float dt)
		{
			try
			{
				Character? character = ValheimIds.GetCharacter((BaseAI?)(object)ai);
				Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null);
				if (val != null)
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(MonsterAI), "UpdateTarget", (Type[])null, (Type[])null);
					if (!(methodInfo == null))
					{
						object[] parameters = new object[4] { val, dt, false, false };
						methodInfo.Invoke(ai, parameters);
					}
				}
			}
			catch (Exception)
			{
			}
		}

		private static void TryDriveAttack(MonsterAI ai, MemberIntent intent)
		{
			if (intent.OrderKind == DoctrineOrderKind.RetreatAndReform || (intent.OrderKind == DoctrineOrderKind.Kite && intent.Role != SquadRole.Missile))
			{
				return;
			}
			bool flag = intent.HoldGround || (IsLineFront(intent) && (intent.OrderKind == DoctrineOrderKind.Hold || intent.OrderKind == DoctrineOrderKind.ProtectMissiles));
			try
			{
				Character targetCreature = ((BaseAI)ai).GetTargetCreature();
				if ((Object)(object)targetCreature == (Object)null)
				{
					if (flag)
					{
						HoldBlocks++;
					}
					return;
				}
				if (IsMidAttack(ai))
				{
					if (flag)
					{
						HoldBlocks++;
					}
					return;
				}
				if (!IsInWeaponBand(ai, targetCreature, intent))
				{
					if (flag)
					{
						HoldBlocks++;
					}
					return;
				}
				long key = InstanceId(ai);
				float time = Time.time;
				float num = EffectiveCooldown(ai, intent);
				if (NextSwingTime.TryGetValue(key, out var value) && time < value)
				{
					if (flag)
					{
						HoldBlocks++;
					}
					return;
				}
				AttackAttempts++;
				MethodInfo methodInfo = AccessTools.Method(typeof(MonsterAI), "DoAttack", (Type[])null, (Type[])null);
				if (!(methodInfo == null))
				{
					methodInfo.Invoke(ai, new object[2] { targetCreature, false });
					Swings++;
					NextSwingTime[key] = time + num;
				}
			}
			catch (Exception)
			{
			}
		}

		private static float EffectiveCooldown(MonsterAI ai, MemberIntent intent)
		{
			float num = CombatTuning.HoldAttackCooldown;
			try
			{
				float value = Traverse.Create((object)ai).Field("m_minAttackInterval").GetValue<float>();
				if (value > num)
				{
					num = value;
				}
			}
			catch
			{
			}
			float num2 = CombatTuning.SwingStaggerMs * (float)Math.Max(0, intent.SlotIndex) / 1000f;
			if (intent.Role == SquadRole.Missile)
			{
				num2 += 0.05f;
			}
			return num + num2;
		}

		private static bool IsMidAttack(MonsterAI ai)
		{
			try
			{
				Character character = ValheimIds.GetCharacter((BaseAI?)(object)ai);
				if ((Object)(object)character != (Object)null)
				{
					MethodInfo methodInfo = AccessTools.Method(((object)character).GetType(), "InAttack", (Type[])null, (Type[])null);
					if (methodInfo != null && methodInfo.GetParameters().Length == 0 && (bool)methodInfo.Invoke(character, null))
					{
						return true;
					}
				}
			}
			catch
			{
			}
			try
			{
				if (Traverse.Create((object)ai).Field("m_wasInAttack").GetValue<bool>())
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static bool IsInWeaponBand(MonsterAI ai, Character target, MemberIntent intent)
		{
			//IL_0006: 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_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Vector3 position = ((Component)ai).transform.position;
				Vector3 position2 = ((Component)target).transform.position;
				float num = position.x - position2.x;
				float num2 = position.z - position2.z;
				float num3 = (float)Math.Sqrt(num * num + num2 * num2);
				float num4 = 2.5f;
				float num5 = 0f;
				try
				{
					Traverse val = Traverse.Create((object)ai);
					num4 = val.Field("m_aiAttackRange").GetValue<float>();
					if (num4 <= 0.01f)
					{
						num4 = 2.5f;
					}
					try
					{
						num5 = val.Field("m_aiAttackRangeMin").GetValue<float>();
					}
					catch
					{
						num5 = 0f;
					}
				}
				catch
				{
					num4 = 2.5f;
				}
				try
				{
					AccessTools.Method(typeof(MonsterAI), "SelectBestAttack", (Type[])null, (Type[])null);
				}
				catch
				{
				}
				float num6 = CombatTuning.HoldAttackRangeFactor;
				if (num6 < 0.25f)
				{
					num6 = 0.25f;
				}
				num4 *= num6;
				if ((intent.PreferKeepRange || intent.Role == SquadRole.Missile) && num4 < 8f)
				{
					num4 = 12f;
				}
				if (num3 > num4)
				{
					return false;
				}
				if (num5 > 0.1f && num3 < num5 * 0.5f && intent.PreferKeepRange)
				{
					return false;
				}
				return true;
			}
			catch
			{
				return true;
			}
		}

		private static bool HoldGroundDestinationFar(MonsterAI ai, MemberIntent intent)
		{
			//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_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			Vector3 desiredPosition = intent.DesiredPosition;
			if (desiredPosition == Vector3.zero || (Object)(object)ai == (Object)null)
			{
				return false;
			}
			Vector3 position = ((Component)ai).transform.position;
			float num = position.x - desiredPosition.x;
			float num2 = position.z - desiredPosition.z;
			return num * num + num2 * num2 > 6.25f;
		}

		private static long InstanceId(MonsterAI ai)
		{
			try
			{
				long num = ValheimIds.FromCharacter(ValheimIds.GetCharacter((BaseAI?)(object)ai));
				if (num != 0L)
				{
					return num;
				}
			}
			catch
			{
			}
			return ((Object)(object)ai != (Object)null) ? ((Object)ai).GetInstanceID() : 0;
		}

		private static bool CallMoveTo(BaseAI ai, float dt, Vector3 point, float dist, bool run)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			return Traverse.Create((object)ai).Method("MoveTo", new object[4] { dt, point, dist, run }).GetValue<bool>();
		}

		private static void CallLookAt(BaseAI ai, Vector3 point)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			Traverse.Create((object)ai).Method("LookAt", new object[1] { point }).GetValue();
		}

		private static void PulseDeathRushAudio(MonsterAI ai, MemberIntent intent)
		{
			if (intent != null && intent.DeathRush)
			{
				DeathRushAudio.PulseIfNeeded(ai, intent);
			}
		}
	}
	public static class CombatTuning
	{
		public static float HoldAttackCooldown = 0.85f;

		public static float HoldAttackRangeFactor = 1f;

		public static float SwingStaggerMs = 75f;

		public static void ApplyKnob(string key, string value)
		{
			if (string.IsNullOrWhiteSpace(key) || value == null)
			{
				return;
			}
			key = key.Trim();
			if (float.TryParse(value.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				if (Matches(key, "HoldAttackCooldown"))
				{
					HoldAttackCooldown = Clamp(result, 0.05f, 10f);
				}
				else if (Matches(key, "HoldAttackRangeFactor"))
				{
					HoldAttackRangeFactor = Clamp(result, 0.25f, 3f);
				}
				else if (Matches(key, "SwingStaggerMs"))
				{
					SwingStaggerMs = Clamp(result, 0f, 2000f);
				}
			}
		}

		public static void CopyFromPluginConfig()
		{
		}

		private static bool Matches(string key, string name)
		{
			return string.Equals(key, name, StringComparison.OrdinalIgnoreCase);
		}

		private static float Clamp(float v, float lo, float hi)
		{
			if (!(v < lo))
			{
				if (!(v > hi))
				{
					return v;
				}
				return hi;
			}
			return lo;
		}
	}
}
namespace FactionTactics.Util
{
	public static class FactionTacticsLog
	{
		public static Action<string>? DebugSink { get; set; }

		public static void Debug(string message)
		{
			try
			{
				DebugSink?.Invoke(message);
			}
			catch
			{
			}
		}
	}
	public static class ValheimIds
	{
		public static long ToLong(ZDOID zdoid)
		{
			return ((((ZDOID)(ref zdoid)).UserID & 0xFFFFFFFFu) << 32) | ((ZDOID)(ref zdoid)).ID;
		}

		public static long FromCharacter(Character? ch)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ch == (Object)null)
			{
				return 0L;
			}
			return ToLong(ch.GetZDOID());
		}

		public static Character? GetCharacter(BaseAI? ai)
		{
			if ((Object)(object)ai == (Object)null)
			{
				return null;
			}
			Character value = Traverse.Create((object)ai).Field("m_character").GetValue<Character>();
			if ((Object)(object)value != (Object)null)
			{
				return value;
			}
			return ((Component)ai).GetComponent<Character>();
		}
	}
}
namespace FactionTactics.Doctrine
{
	public enum DoctrineOrderKind
	{
		Hold,
		Advance,
		Charge,
		Flank,
		FocusFire,
		ProtectMissiles,
		RetreatAndReform,
		Kite
	}
	public enum SquadRole
	{
		Unassigned,
		Front,
		Missile,
		Flanker,
		Leader
	}
}
namespace FactionTactics.Orders
{
	public static class IntentZdoCodec
	{
		[Flags]
		public enum IntentFlags
		{
			None = 0,
			Active = 1,
			HoldGround = 2,
			PreferRun = 4,
			AllowVanillaChase = 8,
			PreferKeepRange = 0x10,
			AssaultWallBreaker = 0x20,
			AssaultMissileCover = 0x40,
			AllowVanillaStructure = 0x80,
			DeathRush = 0x100
		}

		public const int SchemaVersion = 3;

		public const float StaleAfterSeconds = 6f;

		public static IntentFlags PackFlags(MemberIntent intent)
		{
			IntentFlags intentFlags = IntentFlags.Active;
			if (intent.HoldGround)
			{
				intentFlags |= IntentFlags.HoldGround;
			}
			if (intent.PreferRun)
			{
				intentFlags |= IntentFlags.PreferRun;
			}
			if (intent.AllowVanillaChase)
			{
				intentFlags |= IntentFlags.AllowVanillaChase;
			}
			if (intent.PreferKeepRange)
			{
				intentFlags |= IntentFlags.PreferKeepRange;
			}
			if (intent.AssaultWallBreaker)
			{
				intentFlags |= IntentFlags.AssaultWallBreaker;
			}
			if (intent.AssaultMissileCover)
			{
				intentFlags |= IntentFlags.AssaultMissileCover;
			}
			if (intent.AllowVanillaStructure)
			{
				intentFlags |= IntentFlags.AllowVanillaStructure;
			}
			if (intent.DeathRush)
			{
				intentFlags |= IntentFlags.DeathRush;
			}
			return intentFlags;
		}

		public static void ApplyFlags(MemberIntent intent, IntentFlags flags)
		{
			intent.HoldGround = (flags & IntentFlags.HoldGround) != 0;
			intent.PreferRun = (flags & IntentFlags.PreferRun) != 0;
			intent.AllowVanillaChase = (flags & IntentFlags.AllowVanillaChase) != 0;
			intent.PreferKeepRange = (flags & IntentFlags.PreferKeepRange) != 0;
			intent.AssaultWallBreaker = (flags & IntentFlags.AssaultWallBreaker) != 0;
			intent.AssaultMissileCover = (flags & IntentFlags.AssaultMissileCover) != 0;
			intent.AllowVanillaStructure = (flags & IntentFlags.AllowVanillaStructure) != 0;
			intent.DeathRush = (flags & IntentFlags.DeathRush) != 0;
		}

		public static bool IsActive(IntentFlags flags)
		{
			return (flags & IntentFlags.Active) != 0;
		}

		public static bool IsFresh(float writtenAtSeconds, float nowSeconds, float staleAfter = 6f)
		{
			if (writtenAtSeconds <= 0f)
			{
				return false;
			}
			float num = nowSeconds - writtenAtSeconds;
			if (num < -2f)
			{
				return true;
			}
			if (num >= 0f)
			{
				return num <= staleAfter;
			}
			return false;
		}
	}
	public static class IntentZdoSync
	{
		public const string KeyVersion = "ft_iv";

		public const string KeyFlags = "ft_flg";

		public const string KeyOrder = "ft_ord";

		public const string KeyFormation = "ft_frm";

		public const string KeyStance = "ft_stn";

		public const string KeyRole = "ft_rol";

		public const string KeySlot = "ft_slot";

		public const string KeyFocus = "ft_focus";

		public const string KeyTime = "ft_t";

		public const string KeySquadHash = "ft_sq";

		public const string KeySlotIndex = "ft_sidx";

		private static bool _loggedSchemaMismatch;

		public static long Writes { get; private set; }

		public static long SchemaWrites { get; private set; }

		public static long ReadsOk { get; private set; }

		public static long ReadsMiss { get; private set; }

		public static long ReadsStale { get; private set; }

		public static long SchemaMismatches { get; private set; }

		public static void ResetCounters()
		{
			Writes = (SchemaWrites = (ReadsOk = (ReadsMiss = (ReadsStale = (SchemaMismatches = 0L)))));
			_loggedSchemaMismatch = false;
		}

		public static int StableHash(string? s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return 0;
			}
			int num = 5381;
			foreach (char c in s)
			{
				num = ((num << 5) + num) ^ c;
			}
			return num;
		}

		public static bool TryGetZdo(MonsterAI? ai, out ZDO zdo)
		{
			zdo = null;
			if ((Object)(object)ai == (Object)null)
			{
				return false;
			}
			try
			{
				ZNetView component = ((Component)ai).GetComponent<ZNetView>();
				if ((Object)(object)component == (Object)null || !component.IsValid())
				{
					return false;
				}
				ZDO zDO = component.GetZDO();
				if (zDO == null)
				{
					return false;
				}
				zdo = zDO;
				return true;
			}
			catch
			{
				return false;
			}
		}

		public static bool IsNetOwner(MonsterAI? ai)
		{
			if ((Object)(object)ai == (Object)null)
			{
				return false;
			}
			try
			{
				ZNetView component = ((Component)ai).GetComponent<ZNetView>();
				return (Object)(object)component != (Object)null && component.IsValid() && component.IsOwner();
			}
			catch
			{
				return false;
			}
		}

		public static bool Write(ZDO zdo, MemberIntent intent, float nowSeconds)
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if (zdo == null || intent == null)
			{
				return false;
			}
			try
			{
				zdo.Set("ft_iv", 3);
				zdo.Set("ft_flg", (int)IntentZdoCodec.PackFlags(intent));
				zdo.Set("ft_ord", (int)intent.OrderKind);
				zdo.Set("ft_frm", (int)intent.Formation);
				zdo.Set("ft_stn", (int)intent.Stance);
				zdo.Set("ft_rol", (int)intent.Role);
				zdo.Set("ft_slot", intent.DesiredPosition);
				zdo.Set("ft_focus", intent.FocusTargetId.GetValueOrDefault());
				zdo.Set("ft_t", nowSeconds);
				zdo.Set("ft_sq", StableHash(intent.SquadId));
				zdo.Set("ft_sidx", intent.SlotIndex);
				Writes++;
				SchemaWrites++;
				return true;
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("IntentZdoSync.Write failed: " + ex.GetType().Name + ": " + ex.Message);
				return false;
			}
		}

		public static bool WriteFromMonsterAI(MonsterAI ai, MemberIntent intent, float nowSeconds)
		{
			if (TryGetZdo(ai, out ZDO zdo))
			{
				return Write(zdo, intent, nowSeconds);
			}
			return false;
		}

		public static bool TryRead(ZDO zdo, float nowSeconds, out MemberIntent intent)
		{
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			intent = null;
			if (zdo == null)
			{
				ReadsMiss++;
				return false;
			}
			try
			{
				int num = zdo.GetInt("ft_iv", 0);
				switch (num)
				{
				case 0:
					ReadsMiss++;
					return false;
				default:
					SchemaMismatches++;
					ReadsMiss++;
					if (!_loggedSchemaMismatch)
					{
						_loggedSchemaMismatch = true;
						FactionTacticsLog.Debug(string.Format("[FT] INTENT SCHEMA MISMATCH: ZDO ft_iv={0} local={1} product={2}. ", num, 3, "1.0.13") + "Update FactionTactics server+client packs to the same 1.0.x release.");
						try
						{
							Debug.LogError((object)($"[FactionTactics] Intent schema mismatch (zdo={num} local={3}). " + "Client/server packs must match — reinstall both from the same 1.0.x release. Note: missing ZDO fields (zdo=0) with working RPC is OK in 1.0.3+."));
						}
						catch
						{
						}
					}
					return false;
				case 3:
				{
					IntentZdoCodec.IntentFlags flags = (IntentZdoCodec.IntentFlags)zdo.GetInt("ft_flg", 0);
					if (!IntentZdoCodec.IsActive(flags))
					{
						ReadsMiss++;
						return false;
					}
					if (!IntentZdoCodec.IsFresh(zdo.GetFloat("ft_t", 0f), nowSeconds))
					{
						ReadsStale++;
						return false;
					}
					long num2 = zdo.GetLong("ft_focus", 0L);
					intent = new MemberIntent
					{
						OrderKind = (DoctrineOrderKind)zdo.GetInt("ft_ord", 0),
						Formation = (FormationType)zdo.GetInt("ft_frm", 0),
						Stance = (StanceType)zdo.GetInt("ft_stn", 0),
						Role = (SquadRole)zdo.GetInt("ft_rol", 0),
						DesiredPosition = zdo.GetVec3("ft_slot", Vector3.zero),
						FocusTargetId = ((num2 != 0L) ? new long?(num2) : ((long?)null)),
						SquadId = "",
						SlotIndex = zdo.GetInt("ft_sidx", 0)
					};
					IntentZdoCodec.ApplyFlags(intent, flags);
					ReadsOk++;
					return true;
				}
				}
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("IntentZdoSync.TryRead failed: " + ex.GetType().Name + ": " + ex.Message);
				ReadsMiss++;
				return false;
			}
		}

		public static bool TryReadFromMonsterAI(MonsterAI ai, float nowSeconds, out MemberIntent intent)
		{
			intent = null;
			if (TryGetZdo(ai, out ZDO zdo))
			{
				return TryRead(zdo, nowSeconds, out intent);
			}
			return false;
		}

		public static void Clear(ZDO zdo)
		{
			if (zdo == null)
			{
				return;
			}
			try
			{
				zdo.Set("ft_flg", 0);
				zdo.Set("ft_iv", 0);
			}
			catch
			{
			}
		}
	}
	public static class IntentRpcSync
	{
		private struct PendingEntry
		{
			public MemberIntent Intent;

			public float WrittenAt;
		}

		private struct CacheEntry
		{
			public MemberIntent Intent;

			public float WrittenAt;
		}

		public const string RpcName = "FT_MemberIntents";

		private static readonly ConcurrentDictionary<long, PendingEntry> Pending = new ConcurrentDictionary<long, PendingEntry>();

		private static readonly ConcurrentDictionary<long, CacheEntry> Cache = new ConcurrentDictionary<long, CacheEntry>();

		private static bool _registered;

		private static object? _registeredOn;

		private static bool _loggedFirstApply;

		private static bool _loggedFirstPacket;

		private static bool _loggedRegister;

		public static long RpcIntentsSent { get; private set; }

		public static long RpcBatchesSent { get; private set; }

		public static long RpcPeerInvokes { get; private set; }

		public static long RpcIntentsReceived { get; private set; }

		public static long RpcPacketsReceived { get; private set; }

		public static long RpcCacheHits { get; private set; }

		public static long RpcSchemaMismatches { get; private set; }

		public static bool IsRegistered => _registered;

		public static void ResetCounters()
		{
			RpcIntentsSent = (RpcBatchesSent = (RpcPeerInvokes = 0L));
			RpcIntentsReceived = (RpcPacketsReceived = (RpcCacheHits = (RpcSchemaMismatches = 0L)));
			_loggedFirstApply = false;
			_loggedFirstPacket = false;
			Pending.Clear();
			Cache.Clear();
		}

		public static void Queue(long zdoidPacked, MemberIntent intent, float nowSeconds)
		{
			if (zdoidPacked != 0L && intent != null)
			{
				Pending[zdoidPacked] = new PendingEntry
				{
					Intent = Clone(intent),
					WrittenAt = nowSeconds
				};
			}
		}

		public static void EnsureRegistered()
		{
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance == null || (_registered && _registeredOn == instance))
				{
					return;
				}
				instance.Register<ZPackage>("FT_MemberIntents", (Action<long, ZPackage>)OnRouted);
				_registered = true;
				_registeredOn = instance;
				if (!_loggedRegister)
				{
					_loggedRegister = true;
					FactionTacticsLog.Debug("[FT] IntentRpcSync registered RPC 'FT_MemberIntents'");
					try
					{
						Debug.Log((object)"[FactionTactics] IntentRpcSync registered 'FT_MemberIntents' (RPC hybrid transport).");
						return;
					}
					catch
					{
						return;
					}
				}
				FactionTacticsLog.Debug("[FT] IntentRpcSync re-registered RPC 'FT_MemberIntents' (ZRoutedRpc instance changed).");
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("IntentRpcSync.EnsureRegistered failed: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		public static void FlushBroadcast()
		{
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected O, but got Unknown
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Expected O, but got Unknown
			EnsureRegistered();
			if (Pending.Count == 0 || ZRoutedRpc.instance == null)
			{
				return;
			}
			try
			{
				List<KeyValuePair<long, PendingEntry>> list = new List<KeyValuePair<long, PendingEntry>>(Pending.Count);
				foreach (KeyValuePair<long, PendingEntry> item in Pending)
				{
					list.Add(item);
				}
				Pending.Clear();
				if (list.Count == 0)
				{
					return;
				}
				ZPackage val = new ZPackage();
				val.Write(3);
				val.Write(list.Count);
				foreach (KeyValuePair<long, PendingEntry> item2 in list)
				{
					WriteOne(val, item2.Key, item2.Value.Intent, item2.Value.WrittenAt);
					RpcIntentsSent++;
				}
				byte[] array = val.GetArray();
				int num = 0;
				long num2 = 0L;
				try
				{
					num2 = ZNet.GetUID();
				}
				catch
				{
				}
				try
				{
					if ((Object)(object)ZNet.instance != (Object)null)
					{
						List<ZNetPeer> peers = ZNet.instance.GetPeers();
						if (peers != null)
						{
							for (int i = 0; i < peers.Count; i++)
							{
								ZNetPeer val2 = peers[i];
								if (val2 != null && val2.m_uid != 0L && (num2 == 0L || val2.m_uid != num2))
								{
									ZRoutedRpc.instance.InvokeRoutedRPC(val2.m_uid, "FT_MemberIntents", new object[1] { (object)new ZPackage(array) });
									num++;
									RpcPeerInvokes++;
								}
							}
						}
					}
				}
				catch (Exception ex)
				{
					FactionTacticsLog.Debug("IntentRpcSync.FlushBroadcast peer iterate: " + ex.GetType().Name + ": " + ex.Message);
				}
				try
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FT_MemberIntents", new object[1] { (object)new ZPackage(array) });
				}
				catch (Exception ex2)
				{
					FactionTacticsLog.Debug("IntentRpcSync.FlushBroadcast Everybody: " + ex2.GetType().Name + ": " + ex2.Message);
				}
				RpcBatchesSent++;
				if (num == 0)
				{
					FactionTacticsLog.Debug($"[FT] IntentRpcSync FlushBroadcast: no peers targeted; Everybody backup only (intents={list.Count}).");
				}
			}
			catch (Exception ex3)
			{
				FactionTacticsLog.Debug("IntentRpcSync.FlushBroadcast failed: " + ex3.GetType().Name + ": " + ex3.Message);
			}
		}

		private static void OnRouted(long senderPeerId, ZPackage pkg)
		{
			RpcPacketsReceived++;
			if (pkg == null)
			{
				LogFirstPacket(senderPeerId, -1, "null pkg");
				return;
			}
			try
			{
				int num = pkg.ReadInt();
				if (num != 3)
				{
					RpcSchemaMismatches++;
					LogFirstPacket(senderPeerId, -1, $"schema mismatch rpc={num}");
					FactionTacticsLog.Debug($"[FT] IntentRpcSync schema mismatch rpc={num} local={3}");
					return;
				}
				int num2 = pkg.ReadInt();
				LogFirstPacket(senderPeerId, num2, null);
				if (num2 < 0 || num2 > 4096)
				{
					return;
				}
				float time = Time.time;
				for (int i = 0; i < num2; i++)
				{
					if (!TryReadOne(pkg, out long id, out MemberIntent intent, out float writtenAt))
					{
						break;
					}
					if (id != 0L && intent != null)
					{
						if (!IntentZdoCodec.IsFresh(writtenAt, time))
						{
							writtenAt = time;
						}
						Cache[id] = new CacheEntry
						{
							Intent = intent,
							WrittenAt = writtenAt
						};
						RpcIntentsReceived++;
					}
				}
			}
			catch (Exception ex)
			{
				FactionTacticsLog.Debug("IntentRpcSync.OnRouted failed: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static void LogFirstPacket(long senderPeerId, int count, string? note)
		{
			if (_loggedFirstPacket)
			{
				return;
			}
			_loggedFirstPacket = true;
			string text = (string.IsNullOrEmpty(note) ? "" : (" " + note));
			string text2 = $"[FactionTactics] RPC OnRouted first packet sender={senderPeerId} count={count} packets={RpcPacketsReceived}{text}";
			try
			{
				Debug.Log((object)text2);
			}
			catch
			{
			}
			try
			{
				FactionTacticsLog.Debug(text2);
			}
			catch
			{
			}
		}

		private static void WriteOne(ZPackage pkg, long id, MemberIntent intent, float writtenAt)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			pkg.Write(id);
			pkg.Write((int)IntentZdoCodec.PackFlags(intent));
			pkg.Write((int)intent.OrderKind);
			pkg.Write((int)intent.Formation);
			pkg.Write((int)intent.Stance);
			pkg.Write((int)intent.Role);
			pkg.Write(intent.DesiredPosition);
			pkg.Write(intent.FocusTargetId.GetValueOrDefault());
			pkg.Write(writtenAt);
			pkg.Write(IntentZdoSync.StableHash(intent.SquadId));
			pkg.Write(intent.SlotIndex);
		}

		private static bool TryReadOne(ZPackage pkg, out long id, out MemberIntent intent, out float writtenAt)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			id = 0L;
			intent = null;
			writtenAt = 0f;
			try
			{
				id = pkg.ReadLong();
				IntentZdoCodec.IntentFlags flags = (IntentZdoCodec.IntentFlags)pkg.ReadInt();
				DoctrineOrderKind orderKind = (DoctrineOrderKind)pkg.ReadInt();
				FormationType formation = (FormationType)pkg.ReadInt();
				StanceType stance = (StanceType)pkg.ReadInt();
				SquadRole role = (SquadRole)pkg.ReadInt();
				Vector3 desiredPosition = pkg.ReadVector3();
				long num = pkg.ReadLong();
				writtenAt = pkg.ReadSingle();
				pkg.ReadInt();
				int slotIndex = pkg.ReadInt();
				if (!IntentZdoCodec.IsActive(flags))
				{
					return true;
				}
				intent = new MemberIntent
				{
					OrderKind = orderKind,
					Formation = formation,
					Stance = stance,
					Role = role,
					DesiredPosition = desiredPosition,
					FocusTargetId = ((num != 0L) ? new long?(num) : ((long?)null)),
					SquadId = "",
					SlotIndex = slotIndex
				};
				IntentZdoCodec.ApplyFlags(intent, flags);
				return true;
			}
			catch
			{
				return false;
			}
		}

		public static bool TryGet(long zdoidPacked, float nowSeconds, out MemberIntent intent)
		{
			intent = null;
			if (zdoidPacked == 0L)
			{
				return false;
			}
			if (!Cache.TryGetValue(zdoidPacked, out var value))
			{
				return false;
			}
			if (!IntentZdoCodec.IsFresh(value.WrittenAt, nowSeconds))
			{
				return false;
			}
			intent = value.Intent;
			RpcCacheHits++;
			if (!_loggedFirstApply)
			{
				_loggedFirstApply = true;
				try
				{
					Debug.Log((object)$"[FactionTactics] RPC intent applied (first) zdoid={zdoidPacked} order={intent.OrderKind} formation={intent.Formation}");
				}
				catch
				{
				}
				FactionTacticsLog.Debug($"[FT] first RPC intent applied zdoid={zdoidPacked} order={intent.OrderKind}");
			}
			return true;
		}

		public static bool TryGetFromMonsterAI(MonsterAI ai, float nowSeconds, out MemberIntent intent)
		{
			intent = null;
			try
			{
				return TryGet(ValheimIds.FromCharacter(ValheimIds.GetCharacter((BaseAI?)(object)ai)), nowSeconds, out intent);
			}
			catch
			{
				return false;
			}
		}

		private static MemberIntent Clone(MemberIntent src)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			return new MemberIntent
			{
				SquadId = src.SquadId,
				OrderKind = src.OrderKind,
				Formation = src.Formation,
				Stance = src.Stance,
				Role = src.Role,
				DesiredPosition = src.DesiredPosition,
				FocusTargetId = src.FocusTargetId,
				HoldGround = src.HoldGround,
				PreferRun = src.PreferRun,
				AllowVanillaChase = src.AllowVanillaChase,
				PreferKeepRange = src.PreferKeepRange,
				AssaultWallBreaker = src.AssaultWallBreaker,
				AssaultMissileCover = src.AssaultMissileCover,
				AllowVanillaStructure = src.AllowVanillaStructure,
				DeathRush = src.DeathRush,
				SlotIndex = src.SlotIndex
			};
		}
	}
	public sealed class MemberIntent
	{
		public string SquadId { get; set; } = "";

		public DoctrineOrderKind OrderKind { get; set; }

		public FormationType Formation { get; set; }

		public StanceType Stance { get; set; }

		public SquadRole Role { get; set; }

		public Vector3 DesiredPosition { get; set; }

		public long? FocusTargetId { get; set; }

		public bool HoldGround { get; set; }

		public bool PreferRun { get; set; }

		public bool AllowVanillaChase { get; set; }

		public bool PreferKeepRange { get; set; }

		public bool AssaultWallBreaker { get; set; }

		public bool AssaultMissileCover { get; set; }

		public bool AllowVanillaStructure { get; set; }

		public bool DeathRush { get; set; }

		public int SlotIndex { get; set; }
	}
	public enum FormationType
	{
		Loose,
		Line,
		ShieldWall,
		Wedge,
		Skirmish,
		Orb
	}
	public enum StanceType
	{
		Passive,
		Defensive,
		Aggressive,
		Fleeing
	}
	public static class FtVersion
	{
		public const string ProductVersion = "1.0.13";

		public const int IntentSchemaVersion = 3;
	}
}
namespace FactionTactics.Client
{
	[HarmonyPatch(typeof(MonsterAI))]
	public static class ClientMonsterAI_UpdateAI_Patch
	{
		public static long UpdateHits { get; private set; }

		public static long DriveHits { get; private set; }

		public static long SchemaRejects { get; private set; }

		public static long RpcDriveHits { get; private set; }

		[HarmonyPrefix]
		[HarmonyPatch("UpdateAI")]
		public static bool Prefix(MonsterAI __instance, float dt)
		{
			UpdateHits++;
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated())
			{
				return true;
			}
			if (!IntentZdoSync.IsNetOwner(__instance))
			{
				return true;
			}
			float time = Time.time;
			MemberIntent intent;
			bool flag = IntentRpcSync.TryGetFromMonsterAI(__instance, time, out intent);
			if (!flag && !IntentZdoSync.TryReadFromMonsterAI(__instance, time, out intent))
			{
				if (IntentZdoSync.SchemaMismatches > SchemaRejects)
				{
					SchemaRejects = IntentZdoSync.SchemaMismatches;
				}
				return true;
			}
			if (CombatAuthority.ShouldReleaseToVanillaUpdateAI(intent))
			{
				return true;
			}
			CombatDriver.Drive(__instance, intent, dt);
			DriveHits++;
			if (flag)
			{
				RpcDriveHits++;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(BaseAI), "MoveTo")]
	public static class ClientBaseAI_MoveTo_Patch
	{
		[HarmonyPrefix]
		public static bool Prefix(BaseAI __instance, float dt, ref Vector3 point, float dist, bool run)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null);
			if (val == null)
			{
				return true;
			}
			if (!IntentZdoSync.IsNetOwner(val))
			{
				return true;
			}
			float time = Time.time;
			if (!IntentRpcSync.TryGetFromMonsterAI(val, time, out MemberIntent intent) && !IntentZdoSync.TryReadFromMonsterAI(val, time, out intent))
			{
				return true;
			}
			if (CombatAuthority.ShouldAllowVanillaMoveTo(intent))
			{
				return true;
			}
			if (intent.HoldGround)
			{
				__instance.StopMoving();
				return false;
			}
			if (intent.DesiredPosition != Vector3.zero && (intent.PreferKeepRange || CombatDriver.IsLineFront(intent)))
			{
				point = intent.DesiredPosition;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	public static class ClientZNet_Awake_RegisterIntentRpc_Patch
	{
		[HarmonyPostfix]
		public static void Postfix()
		{
			IntentRpcSync.EnsureRegistered();
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	public static class ClientZNet_OnNewConnection_RegisterIntentRpc_Patch
	{
		[HarmonyPostfix]
		public static void Postfix()
		{
			IntentRpcSync.EnsureRegistered();
		}
	}
	[HarmonyPatch(typeof(ZNet), "Update")]
	public static class ClientZNet_Update_RegisterIntentRpc_Patch
	{
		[HarmonyPostfix]
		public static void Postfix()
		{
			if (!IntentRpcSync.IsRegistered)
			{
				IntentRpcSync.EnsureRegistered();
			}
		}
	}
	[HarmonyPatch(typeof(Game), "Update")]
	public static class ClientGame_Update_RegisterIntentRpc_Patch
	{
		[HarmonyPostfix]
		public static void Postfix()
		{
			if (!IntentRpcSync.IsRegistered)
			{
				IntentRpcSync.EnsureRegistered();
			}
		}
	}
	internal static class ClientConsoleCommands
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleOptionsFetcher <0>__TabOptions;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static Action<string> <>9__1_0;

			public static ConsoleEvent <>9__1_1;

			internal void <Register>b__1_0(string line)
			{
				try
				{
					Console instance = Console.instance;
					if ((Object)(object)instance != (Object)null)
					{
						instance.Print(line);
					}
				}
				catch
				{
				}
				try
				{
					ManualLogSource log = ClientPlugin.Log;
					if (log != null)
					{
						log.LogInfo((object)line);
					}
				}
				catch
				{
				}
			}

			internal void <Register>b__1_1(ConsoleEventArgs args)
			{
				Run(args);
			}
		}

		private static bool _registered;

		public static void Register()
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Expected O, but got Unknown
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			if (Type.GetType("FactionTactics.Plugin, FactionTactics", throwOnError: false) != null)
			{
				ManualLogSource log = ClientPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"FactionTactics.Client: skipping ft console (server plugin present).");
				}
				return;
			}
			_registered = true;
			FtConfigRpc.LocalPrint = delegate(string line)
			{
				try
				{
					Console instance = Console.instance;
					if ((Object)(object)instance != (Object)null)
					{
						instance.Print(line);
					}
				}
				catch
				{
				}
				try
				{
					ManualLogSource log3 = ClientPlugin.Log;
					if (log3 != null)
					{
						log3.LogInfo((object)line);
					}
				}
				catch
				{
				}
			};
			object obj = <>c.<>9__1_1;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					Run(args);
				};
				<>c.<>9__1_1 = val;
				obj = (object)val;
			}
			object obj2 = <>O.<0>__TabOptions;
			if (obj2 == null)
			{
				ConsoleOptionsFetcher val2 = TabOptions;
				<>O.<0>__TabOptions = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("ft", "Faction Tactics admin: ft help | get <key> | set <key> <value> | reload | status (RPC to dedicated)", (ConsoleEvent)obj, false, false, false, false, true, false, (ConsoleOptionsFetcher)obj2, true, false, true);
			ManualLogSource log2 = ClientPlugin.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)"FactionTactics.Client: registered admin 'ft' (RPC to dedicated).");
			}
		}

		private static List<string> TabOptions()
		{
			return new List<string>
			{
				"help", "get", "set", "reload", "status", "HoldAttackCooldown", "SwingStaggerMs", "HoldAttackRangeFactor", "FormationReshuffleSeconds", "FormationCasualtyReshuffle",
				"ChargeMaxSeconds", "OrderMinDwellSeconds", "OrderScoreHysteresis", "RomanWallOuter", "RomanWallInner", "RomanStandoffDistance", "RomanStandoffHoldMin", "RomanStandoffHoldMax", "RomanContactSwingRange", "RomanRetreatPauseSeconds",
				"VikingStandoffDistance", "VikingIndoorsStandoff", "VikingStandoffHoldMin", "VikingStandoffHoldMax", "VikingContactSwingRange", "VikingRetreatPauseSeconds", "FlankSplitMeters", "IsolateBuddyMeters", "DiscoveryBurstOnSpawn", "DiscoveryBurstSeconds",
				"TickIntervalSeconds", "MinSquadSize", "DiscoveryRadius"
			};
		}

		private static void Run(ConsoleEventArgs args)
		{
			if (!FtConfigRpc.LocalPlayerIsAdmin())
			{
				args.Context.AddString("[ft] admin only (ZNet admin list / host)");
				return;
			}
			switch ((args.Length >= 2) ? args[1].ToLowerInvariant() : "help")
			{
			case "help":
			case "?":
				PrintLocalHelp(args);
				FtConfigRpc.SendCmd("help");
				break;
			case "get":
				if (args.Length < 3)
				{
					args.Context.AddString("[ft] usage: ft get <key>");
				}
				else
				{
					FtConfigRpc.SendCmd("get", args[2]);
				}
				break;
			case "set":
			{
				if (args.Length < 4)
				{
					args.Context.AddString("[ft] usage: ft set <key> <value>");
					break;
				}
				string text = args[2];
				string text2 = string.Join(" ", args.Args.Skip(3));
				args.Context.AddString("[ft] requesting server set " + text + "=" + text2 + " …");
				FtConfigRpc.SendCmd("set", text, text2);
				break;
			}
			case "reload":
				args.Context.AddString("[ft] requesting server reload …");
				FtConfigRpc.SendCmd("reload");
				break;
			case "status":
				PrintLocalStatus(args);
				FtConfigRpc.SendCmd("status");
				break;
			default:
				args.Context.AddString("[ft] unknown subcommand '" + args[1] + "' — try: ft help");
				break;
			}
		}

		private static void PrintLocalHelp(ConsoleEventArgs args)
		{
			args.Context.AddString("[ft] FactionTactics.Client 1.0.13 (admin RPC → dedicated)");
			args.Context.AddString("  ft help | get <key> | set <key> <value> | reload | status");
			args.Context.AddString("  Mutating commands apply on the dedicated server PluginConfig (persisted).");
			args.Context.AddString("  Phase A/B knobs: HoldAttackCooldown, SwingStaggerMs, FormationReshuffleSeconds,");
			args.Context.AddString("    ChargeMaxSeconds, OrderMinDwellSeconds, OrderScoreHysteresis,");
			args.Context.AddString("    RomanStandoffDistance, RomanStandoffHoldMin/Max, RomanContactSwingRange, RomanRetreatPauseSeconds,");
			args.Context.AddString("    VikingStandoffDistance, VikingIndoorsStandoff, VikingContactSwingRange, FlankSplitMeters, DiscoveryBurst*");
			args.Context.AddString($"  Local executor tuning: HoldAttackCooldown={CombatTuning.HoldAttackCooldown} SwingStaggerMs={CombatTuning.SwingStaggerMs}");
		}

		private static void PrintLocalStatus(ConsoleEventArgs args)
		{
			args.Context.AddString(string.Format("[ft] FactionTactics.Client {0} product={1} schema=v{2}", "1.0.13", "1.0.13", 3));
			args.Context.AddString($"[ft] local: swings={CombatDriver.Swings} holdBlocks={CombatDriver.HoldBlocks} drives={ClientMonsterAI_UpdateAI_Patch.DriveHits} rpcDrives={ClientMonsterAI_UpdateAI_Patch.RpcDriveHits}");
			args.Context.AddString($"[ft] zdo: readsOk={IntentZdoSync.ReadsOk} stale={IntentZdoSync.ReadsStale} mismatch={IntentZdoSync.SchemaMismatches} rpcRecv={IntentRpcSync.RpcIntentsReceived} rpcPkts={IntentRpcSync.RpcPacketsReceived} rpcHits={IntentRpcSync.RpcCacheHits}");
			args.Context.AddString($"[ft] tuning: HoldAttackCooldown={CombatTuning.HoldAttackCooldown} SwingStaggerMs={CombatTuning.SwingStaggerMs} RangeFactor={CombatTuning.HoldAttackRangeFactor}");
			args.Context.AddString("[ft] requesting server status …");
		}
	}
	[BepInPlugin("com.nate.factiontactics.client", "FactionTactics.Client", "1.0.13")]
	public class ClientPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.nate.factiontactics.client";

		public const string PluginName = "FactionTactics.Client";

		public const string PluginVersion = "1.0.13";

		private Harmony? _harmony;

		internal static ClientPlugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			FactionTacticsLog.DebugSink = delegate(string msg)
			{
				Log.LogDebug((object)msg);
			};
			if (IsDedicatedProcess())
			{
				Log.LogInfo((object)"FactionTactics.Client 1.0.13: dedicated process detected — client executor idle (install FactionTactics server DLL for commander).");
				return;
			}
			_harmony = new Harmony("com.nate.factiontactics.client");
			_harmony.PatchAll(typeof(ClientPlugin).Assembly);
			try
			{
				IntentRpcSync.EnsureRegistered();
			}
			catch
			{
			}
			try
			{
				FtConfigRpc.EnsureRegistered();
			}
			catch
			{
			}
			ClientConsoleCommands.Register();
			Log.LogInfo((object)"FactionTactics.Client 1.0.13 loaded — owning-client combat executor (RPC intents → CombatDriver swing gate; admin ft RPC). Install alongside/without server DLL on players.");
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private static bool IsDedicatedProcess()
		{
			try
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					return ZNet.instance.IsDedicated();
				}
			}
			catch
			{
			}
			string[] commandLineArgs = Environment.GetCommandLineArgs();
			foreach (string a in commandLineArgs)
			{
				if (string.Equals(a, "-batchmode", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				if (string.Equals(a, "-nographics", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}
	}
}