Decompiled source of FactionTactics Server v1.0.13

FactionTactics.dll

Decompiled 3 hours ago
using System;
using System.Collections;
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 System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FactionTactics.Ambience;
using FactionTactics.Combat;
using FactionTactics.Commander;
using FactionTactics.Config;
using FactionTactics.ConsoleCmds;
using FactionTactics.Dedicated;
using FactionTactics.Doctrine;
using FactionTactics.HarmonyPatches;
using FactionTactics.Orders;
using FactionTactics.Siege;
using FactionTactics.Squad;
using FactionTactics.Util;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: InternalsVisibleTo("FactionTactics.Tests")]
[assembly: AssemblyCompany("FactionTactics")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.13.0")]
[assembly: AssemblyInformationalVersion("1.0.13+b72dd217344d7e9cda3ad9c8d0c8ee1bd7ac5931")]
[assembly: AssemblyProduct("FactionTactics")]
[assembly: AssemblyTitle("FactionTactics")]
[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
{
	[BepInPlugin("com.nate.factiontactics", "FactionTactics", "1.0.13")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.nate.factiontactics";

		public const string PluginName = "FactionTactics";

		public const string PluginVersion = "1.0.13";

		private Harmony? _harmony;

		private SquadDirector? _director;

		private EnemyOwnershipDirector? _enemyOwnership;

		private float _tickAccumulator;

		private float _burstRemaining;

		private int _lastCandidateCount = -1;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal SquadDirector? Director => _director;

		private void Awake()
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			FactionTacticsLog.DebugSink = delegate(string msg)
			{
				Log.LogDebug((object)msg);
			};
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			DoctrinePackRegistry registry = DoctrinePackRegistry.CreateDefault();
			SiegeDirector siege = new SiegeDirector();
			AmbushAmbienceDirector ambience = new AmbushAmbienceDirector();
			ICommander commander = new ScriptedCommander(registry, siege);
			IRoleScorer roleScorer = new NullRoleScorer();
			IActionScorer actionScorer = new NullActionScorer();
			OrderApplicator applicator = new OrderApplicator();
			SquadDiscovery discovery = new SquadDiscovery(registry);
			_director = new SquadDirector(discovery, commander, registry, roleScorer, actionScorer, applicator, siege, ambience);
			_enemyOwnership = new EnemyOwnershipDirector();
			FtConsoleCommands.Register();
			try
			{
				FtConfigRpc.EnsureRegistered();
			}
			catch
			{
			}
			if (PluginConfig.EnablePlugin.Value)
			{
				_harmony = new Harmony("com.nate.factiontactics");
				MonsterAIPatches.Apply(_harmony);
				Log.LogInfo((object)string.Format("{0} {1} loaded (1.0.13 always-threat: ceil(10%) Attack or ranged harass while engaged/Theater). Tick={2}s", "FactionTactics", "1.0.13", PluginConfig.TickIntervalSeconds.Value));
			}
			else
			{
				Log.LogInfo((object)"FactionTactics disabled via config.");
			}
		}

		private void Update()
		{
			if (!PluginConfig.EnablePlugin.Value || _director == null || ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()))
			{
				return;
			}
			_tickAccumulator += Time.deltaTime;
			float num = Math.Max(0.1f, PluginConfig.TickIntervalSeconds.Value);
			if (_burstRemaining > 0f)
			{
				ConfigEntry<bool> discoveryBurstOnSpawn = PluginConfig.DiscoveryBurstOnSpawn;
				if (discoveryBurstOnSpawn == null || discoveryBurstOnSpawn.Value)
				{
					num = Math.Max(0.05f, PluginConfig.DiscoveryBurstSeconds?.Value ?? 0.2f);
					_burstRemaining -= Time.deltaTime;
				}
			}
			if (_tickAccumulator < num)
			{
				return;
			}
			_tickAccumulator = 0f;
			try
			{
				_enemyOwnership?.Tick(num);
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"EnemyOwnershipDirector tick failed: {arg}");
			}
			try
			{
				_director.Tick(num);
				MaybeStartDiscoveryBurst();
			}
			catch (Exception arg2)
			{
				Log.LogError((object)$"SquadDirector tick failed: {arg2}");
			}
			try
			{
				FtConfigRpc.EnsureRegistered();
			}
			catch
			{
			}
		}

		private void MaybeStartDiscoveryBurst()
		{
			ConfigEntry<bool> discoveryBurstOnSpawn = PluginConfig.DiscoveryBurstOnSpawn;
			if (discoveryBurstOnSpawn != null && !discoveryBurstOnSpawn.Value)
			{
				return;
			}
			SquadDirector director = _director;
			if (director != null)
			{
				int num = Math.Max(director.LastDiscoveredCount, director.LastCandidateGauge);
				if (_lastCandidateCount >= 0 && num > _lastCandidateCount + 1)
				{
					_burstRemaining = 2f;
				}
				if (_lastCandidateCount == 0 && num > 0)
				{
					_burstRemaining = 2f;
				}
				_lastCandidateCount = num;
			}
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
}
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>();
		}
	}
	public static class ValheimWorldScan
	{
		private static bool _loggedDedicatedDiscovery;

		private static bool _sceneDumpDone;

		private static float _lastSceneDumpRealtime;

		private static FieldInfo? _sCharactersField;

		private static bool _sCharactersResolved;

		private static FieldInfo? _baseAiInstancesField;

		private static bool _baseAiInstancesResolved;

		private static FieldInfo? _zNetSceneInstancesField;

		private static bool _zNetSceneInstancesFieldResolved;

		private static readonly HashSet<string> _loggedPathFailures = new HashSet<string>(StringComparer.Ordinal);

		private static readonly string[] KnownEnemyPrefabs = new string[29]
		{
			"Skeleton", "Skeleton_Poison", "Skeleton_NoArcher", "Skeleton_Hildir", "Greyling", "Greydwarf", "Greydwarf_Elite", "Greydwarf_Shaman", "Draugr", "Draugr_Elite",
			"Draugr_Ranged", "Goblin", "GoblinShaman", "GoblinBrute", "Fuling", "Wolf", "Drake", "Hatchling", "Blob", "BlobElite",
			"Seeker", "SeekerBrood", "Tick", "Gjall", "Charred_Melee", "Charred_Archer", "Charred_Mage", "Asksvin", "Troll"
		};

		public static int LastScanCharacterCount { get; private set; }

		public static int LastScanPlayerCount { get; private set; }

		public static int LastScanInRangeCount { get; private set; }

		public static int LastScanPlayerZdoCount { get; private set; }

		public static int LastScanZdoCount => LastScanPlayerZdoCount;

		public static int LastScanInstancesFound { get; private set; }

		public static int LastScanBaseAiInstances { get; private set; }

		public static int LastScanSceneInstances { get; private set; }

		public static int LastScanSceneNrOfInstances { get; private set; }

		public static int LastScanPrefabZdos { get; private set; }

		public static int LastScanPrefabLive { get; private set; }

		public static int LastScanPrefabMai { get; private set; }

		public static int LastScanAnimalAi { get; private set; }

		public static int LastScanFindObjects { get; private set; }

		public static int LastScanFindAll { get; private set; }

		public static int LastScanRegistry { get; private set; }

		public static int LastScanMonsterAiCount { get; private set; }

		public static int LastScanEnemyZdoCount { get; private set; }

		public static List<MonsterAI> EnumerateMonsterAIs()
		{
			//IL_0308: Unknown result type (might be due to invalid IL or missing references)
			//IL_030d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0316: Unknown result type (might be due to invalid IL or missing references)
			List<MonsterAI> list = new List<MonsterAI>();
			HashSet<int> hashSet = new HashSet<int>();
			int chars = 0;
			int players = 0;
			int num = 0;
			int lastScanPlayerZdoCount = 0;
			int instancesFound = 0;
			int num2 = 0;
			int num3 = 0;
			int lastScanSceneNrOfInstances = 0;
			int num4 = 0;
			int prefabLive = 0;
			int prefabMai = 0;
			int num5 = 0;
			int lastScanFindObjects = 0;
			int lastScanFindAll = 0;
			int lastScanRegistry = 0;
			int num6 = 0;
			try
			{
				List<MonsterAI> list2 = EnemyOwnershipDirector.SnapshotLiveMonsterAIs();
				_ = list2.Count;
				foreach (MonsterAI item in list2)
				{
					if (!((Object)(object)item == (Object)null) && hashSet.Add(((Object)item).GetInstanceID()))
					{
						list.Add(item);
						MonsterAIRegistry.Register(item);
					}
				}
			}
			catch (Exception ex)
			{
				LogPathFailureOnce("EnemyOwnership.SnapshotLive", ex);
			}
			try
			{
				int count = list.Count;
				try
				{
					List<BaseAI> allInstances = BaseAI.GetAllInstances();
					if (allInstances != null)
					{
						TryAddMonsterAIs(allInstances, list, hashSet);
					}
				}
				catch (Exception ex2)
				{
					LogPathFailureOnce("BaseAI.GetAllInstances.early", ex2);
				}
				try
				{
					TryAddMonsterAIs(BaseAI.BaseAIInstances, list, hashSet);
				}
				catch (Exception ex3)
				{
					LogPathFailureOnce("BaseAI.BaseAIInstances.early", ex3);
				}
				try
				{
					TryAddMonsterAIs(BaseAI.Instances, list, hashSet);
				}
				catch (Exception ex4)
				{
					LogPathFailureOnce("BaseAI.Instances.early", ex4);
				}
				num6 = list.Count - count;
				num2 = Math.Max(num2, num6);
			}
			catch (Exception ex5)
			{
				LogPathFailureOnce("BaseAI.earlyHarvest", ex5);
			}
			try
			{
				List<MonsterAI> list3 = MonsterAIRegistry.Snapshot();
				lastScanRegistry = list3.Count;
				foreach (MonsterAI item2 in list3)
				{
					if (!((Object)(object)item2 == (Object)null) && hashSet.Add(((Object)item2).GetInstanceID()))
					{
						list.Add(item2);
					}
				}
				try
				{
					num5 = Math.Max(num5, MonsterAIRegistry.AnimalAiCount);
					foreach (BaseAI item3 in MonsterAIRegistry.SnapshotBase())
					{
						if (!((Object)(object)item3 == (Object)null))
						{
							MonsterAI val = (MonsterAI)(object)((item3 is MonsterAI) ? item3 : null);
							if (val != null && hashSet.Add(((Object)val).GetInstanceID()))
							{
								list.Add(val);
							}
						}
					}
				}
				catch
				{
				}
			}
			catch (Exception ex6)
			{
				LogPathFailureOnce("MonsterAIRegistry", ex6);
			}
			try
			{
				List<Character> allCharacters = Character.GetAllCharacters();
				if (allCharacters != null)
				{
					chars = allCharacters.Count;
					foreach (Character item4 in allCharacters)
					{
						if ((Object)(object)item4 == (Object)null)
						{
							continue;
						}
						try
						{
							if (item4.IsPlayer())
							{
								players++;
							}
						}
						catch
						{
						}
					}
				}
			}
			catch (Exception ex7)
			{
				LogPathFailureOnce("GetAllCharacters", ex7);
			}
			try
			{
				AddFromCharacterSCharacters(list, hashSet, ref chars, ref players);
			}
			catch (Exception ex8)
			{
				LogPathFailureOnce("s_characters", ex8);
			}
			try
			{
				float num7 = PluginConfig.DiscoveryRadius?.Value ?? 64f;
				float num8 = Mathf.Max(64f, num7);
				List<Vector3> list4 = CollectPlayerPositions();
				if (list4.Count > 0)
				{
					players = Math.Max(players, list4.Count);
				}
				List<Character> list5 = new List<Character>();
				foreach (Vector3 item5 in list4)
				{
					list5.Clear();
					try
					{
						Character.GetCharactersInRange(item5, num8, list5);
					}
					catch
					{
						continue;
					}
					foreach (Character item6 in list5)
					{
						if (!((Object)(object)item6 == (Object)null))
						{
							bool flag = false;
							try
							{
								flag = item6.IsPlayer();
							}
							catch
							{
							}
							if (!flag)
							{
								num++;
								TryAddMonsterAIFromCharacter(item6, list, hashSet);
							}
						}
					}
				}
			}
			catch (Exception ex9)
			{
				LogPathFailureOnce("GetCharactersInRange", ex9);
			}
			try
			{
				List<Character> allCharacters2 = Character.GetAllCharacters();
				if (allCharacters2 != null)
				{
					foreach (Character item7 in allCharacters2)
					{
						if ((Object)(object)item7 == (Object)null)
						{
							continue;
						}
						try
						{
							if (item7.IsPlayer())
							{
								continue;
							}
						}
						catch
						{
						}
						TryAddMonsterAIFromCharacter(item7, list, hashSet);
					}
				}
			}
			catch (Exception ex10)
			{
				LogPathFailureOnce("GetAllCharacters-mobs", ex10);
			}
			try
			{
				List<IMonoUpdater> instances = Character.Instances;
				if (instances != null)
				{
					foreach (IMonoUpdater item8 in instances)
					{
						Character val2 = (Character)(object)((item8 is Character) ? item8 : null);
						if (val2 == null)
						{
							continue;
						}
						try
						{
							if (val2.IsPlayer())
							{
								continue;
							}
						}
						catch
						{
						}
						TryAddMonsterAIFromCharacter(val2, list, hashSet);
					}
				}
			}
			catch (Exception ex11)
			{
				LogPathFailureOnce("Character.Instances", ex11);
			}
			try
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					List<ZDO> allCharacterZDOS = ZNet.instance.GetAllCharacterZDOS();
					if (allCharacterZDOS != null)
					{
						lastScanPlayerZdoCount = allCharacterZDOS.Count;
					}
				}
			}
			catch (Exception ex12)
			{
				LogPathFailureOnce("GetAllCharacterZDOS", ex12);
			}
			try
			{
				num2 = AddFromBaseAiMInstances(list, hashSet);
			}
			catch (Exception ex13)
			{
				LogPathFailureOnce("BaseAI.m_instances", ex13);
			}
			try
			{
				List<BaseAI> allInstances2 = BaseAI.GetAllInstances();
				if (allInstances2 != null)
				{
					num2 = Math.Max(num2, allInstances2.Count);
					TryAddMonsterAIs(allInstances2, list, hashSet);
				}
			}
			catch (Exception ex14)
			{
				LogPathFailureOnce("BaseAI.GetAllInstances", ex14);
			}
			try
			{
				TryAddMonsterAIs(BaseAI.BaseAIInstances, list, hashSet);
			}
			catch (Exception ex15)
			{
				LogPathFailureOnce("BaseAI.BaseAIInstances", ex15);
			}
			try
			{
				TryAddMonsterAIs(BaseAI.Instances, list, hashSet);
			}
			catch (Exception ex16)
			{
				LogPathFailureOnce("BaseAI.Instances", ex16);
			}
			try
			{
				if ((Object)(object)ZNetScene.instance != (Object)null)
				{
					try
					{
						lastScanSceneNrOfInstances = ZNetScene.instance.NrOfInstances();
					}
					catch (Exception ex17)
					{
						LogPathFailureOnce("ZNetScene.NrOfInstances", ex17);
					}
				}
				num3 = AddFromZNetSceneInstances(list, hashSet);
			}
			catch (Exception ex18)
			{
				LogPathFailureOnce("ZNetScene.m_instances", ex18);
			}
			try
			{
				num4 = AddFromZdoManEnemyPrefabs(list, hashSet, ref instancesFound, ref prefabLive, ref prefabMai);
			}
			catch (Exception ex19)
			{
				LogPathFailureOnce("ZDOMan.prefabZdos", ex19);
			}
			try
			{
				num4 += AddFromZdoManSectorObjects(list, hashSet, ref instancesFound, ref prefabLive, ref prefabMai);
			}
			catch (Exception ex20)
			{
				LogPathFailureOnce("ZDOMan.FindSectorObjects", ex20);
			}
			try
			{
				if (num3 > 0)
				{
					MaybeDumpSceneInstances(force: false);
				}
			}
			catch (Exception ex21)
			{
				LogPathFailureOnce("sceneDump", ex21);
			}
			try
			{
				lastScanFindObjects = AddFromFindObjectsOfType(list, hashSet);
			}
			catch (Exception ex22)
			{
				LogPathFailureOnce("FindObjectsOfType", ex22);
			}
			try
			{
				lastScanFindAll = AddFromResourcesFindObjectsOfTypeAll(list, hashSet);
			}
			catch (Exception ex23)
			{
				LogPathFailureOnce("Resources.FindObjectsOfTypeAll", ex23);
			}
			LastScanCharacterCount = chars;
			LastScanPlayerCount = players;
			LastScanInRangeCount = num;
			LastScanPlayerZdoCount = lastScanPlayerZdoCount;
			LastScanInstancesFound = instancesFound;
			LastScanBaseAiInstances = num2;
			LastScanSceneInstances = num3;
			LastScanSceneNrOfInstances = lastScanSceneNrOfInstances;
			LastScanPrefabZdos = num4;
			LastScanPrefabLive = prefabLive;
			LastScanPrefabMai = prefabMai;
			LastScanAnimalAi = num5;
			LastScanFindObjects = lastScanFindObjects;
			LastScanFindAll = lastScanFindAll;
			LastScanRegistry = lastScanRegistry;
			LastScanMonsterAiCount = list.Count;
			return list;
		}

		private static void AddFromCharacterSCharacters(List<MonsterAI> list, HashSet<int> seen, ref int chars, ref int players)
		{
			if (!_sCharactersResolved)
			{
				_sCharactersResolved = true;
				try
				{
					_sCharactersField = typeof(Character).GetField("s_characters", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				}
				catch (Exception ex)
				{
					LogPathFailureOnce("s_characters.resolve", ex);
					_sCharactersField = null;
				}
			}
			if (_sCharactersField == null)
			{
				return;
			}
			object value;
			try
			{
				value = _sCharactersField.GetValue(null);
			}
			catch (Exception ex2)
			{
				LogPathFailureOnce("s_characters.get", ex2);
				return;
			}
			if (!(value is IList list2))
			{
				return;
			}
			chars = Math.Max(chars, list2.Count);
			int num = 0;
			foreach (object item in list2)
			{
				Character val = (Character)((item is Character) ? item : null);
				if (val == null || (Object)(object)val == (Object)null)
				{
					continue;
				}
				try
				{
					if (val.IsPlayer())
					{
						num++;
						continue;
					}
				}
				catch
				{
				}
				TryAddMonsterAIFromCharacter(val, list, seen);
			}
			players = Math.Max(players, num);
		}

		private static int AddFromBaseAiMInstances(List<MonsterAI> list, HashSet<int> seen)
		{
			if (!_baseAiInstancesResolved)
			{
				_baseAiInstancesResolved = true;
				try
				{
					_baseAiInstancesField = typeof(BaseAI).GetField("m_instances", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				}
				catch (Exception ex)
				{
					LogPathFailureOnce("BaseAI.m_instances.resolve", ex);
					_baseAiInstancesField = null;
				}
			}
			if (_baseAiInstancesField == null)
			{
				return 0;
			}
			object value;
			try
			{
				value = _baseAiInstancesField.GetValue(null);
			}
			catch (Exception ex2)
			{
				LogPathFailureOnce("BaseAI.m_instances.get", ex2);
				return 0;
			}
			if (value == null)
			{
				return 0;
			}
			int result = ((value is ICollection collection) ? collection.Count : 0);
			TryAddMonsterAIs(value as IEnumerable, list, seen);
			return result;
		}

		private static int AddFromZNetSceneInstances(List<MonsterAI> list, HashSet<int> seen)
		{
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return 0;
			}
			if (!_zNetSceneInstancesFieldResolved)
			{
				_zNetSceneInstancesFieldResolved = true;
				try
				{
					_zNetSceneInstancesField = typeof(ZNetScene).GetField("m_instances", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				}
				catch (Exception ex)
				{
					LogPathFailureOnce("ZNetScene.m_instances.resolve", ex);
					_zNetSceneInstancesField = null;
				}
			}
			if (_zNetSceneInstancesField == null)
			{
				return 0;
			}
			object value;
			try
			{
				value = _zNetSceneInstancesField.GetValue(ZNetScene.instance);
			}
			catch (Exception ex2)
			{
				LogPathFailureOnce("ZNetScene.m_instances.get", ex2);
				return 0;
			}
			if (value == null)
			{
				return 0;
			}
			int result = ((value is ICollection collection) ? collection.Count : 0);
			if (value is IDictionary dictionary)
			{
				{
					foreach (DictionaryEntry item in dictionary)
					{
						object? value2 = item.Value;
						ZNetView val = (ZNetView)((value2 is ZNetView) ? value2 : null);
						if (val != null && (Object)(object)val != (Object)null)
						{
							TryAddFromZNetView(val, list, seen);
						}
					}
					return result;
				}
			}
			if (value is IEnumerable enumerable)
			{
				foreach (object item2 in enumerable)
				{
					ZNetView val2 = null;
					ZNetView val3 = (ZNetView)((item2 is ZNetView) ? item2 : null);
					if (val3 != null)
					{
						val2 = val3;
					}
					else if (item2 != null)
					{
						Type type = item2.GetType();
						if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<, >))
						{
							object? obj = type.GetProperty("Value")?.GetValue(item2);
							val2 = (ZNetView)((obj is ZNetView) ? obj : null);
						}
					}
					if ((Object)(object)val2 != (Object)null)
					{
						TryAddFromZNetView(val2, list, seen);
					}
				}
			}
			return result;
		}

		private static void TryAddFromZNetView(ZNetView nv, List<MonsterAI> list, HashSet<int> seen, ref int maiHits)
		{
			try
			{
				MonsterAIRegistry.RegisterFromComponent((Component?)(object)nv);
				MonsterAI val = null;
				try
				{
					val = ((Component)nv).GetComponent<MonsterAI>();
				}
				catch
				{
				}
				if ((Object)(object)val == (Object)null)
				{
					try
					{
						val = ((Component)nv).GetComponentInChildren<MonsterAI>(true);
					}
					catch
					{
					}
				}
				if ((Object)(object)val != (Object)null)
				{
					maiHits++;
					if (seen.Add(((Object)val).GetInstanceID()))
					{
						list.Add(val);
					}
					return;
				}
				BaseAI val2 = null;
				try
				{
					val2 = ((Component)nv).GetComponent<BaseAI>();
				}
				catch
				{
				}
				if ((Object)(object)val2 == (Object)null)
				{
					try
					{
						val2 = ((Component)nv).GetComponentInChildren<BaseAI>(true);
					}
					catch
					{
					}
				}
				if ((Object)(object)val2 != (Object)null)
				{
					MonsterAIRegistry.RegisterBase(val2);
					MonsterAI val3 = (MonsterAI)(object)((val2 is MonsterAI) ? val2 : null);
					if (val3 != null)
					{
						maiHits++;
						if (seen.Add(((Object)val3).GetInstanceID()))
						{
							list.Add(val3);
						}
						return;
					}
				}
				Character val4 = null;
				try
				{
					val4 = ((Component)nv).GetComponent<Character>();
				}
				catch
				{
				}
				if ((Object)(object)val4 == (Object)null)
				{
					try
					{
						val4 = ((Component)nv).GetComponentInChildren<Character>(true);
					}
					catch
					{
					}
				}
				if ((Object)(object)val4 == (Object)null)
				{
					return;
				}
				try
				{
					if (val4.IsPlayer())
					{
						return;
					}
				}
				catch
				{
				}
				int count = list.Count;
				TryAddMonsterAIFromCharacter(val4, list, seen);
				if (list.Count > count)
				{
					maiHits++;
				}
			}
			catch
			{
			}
		}

		private static void TryAddFromZNetView(ZNetView nv, List<MonsterAI> list, HashSet<int> seen)
		{
			int maiHits = 0;
			TryAddFromZNetView(nv, list, seen, ref maiHits);
		}

		private static int AddFromZdoManEnemyPrefabs(List<MonsterAI> list, HashSet<int> seen, ref int instancesFound, ref int prefabLive, ref int prefabMai)
		{
			if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return 0;
			}
			List<ZDO> list2 = new List<ZDO>();
			int num = 0;
			string[] knownEnemyPrefabs = KnownEnemyPrefabs;
			foreach (string text in knownEnemyPrefabs)
			{
				list2.Clear();
				int num2 = 0;
				try
				{
					while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(text, list2, ref num2))
					{
					}
				}
				catch (Exception ex)
				{
					LogPathFailureOnce("GetAllZDOsWithPrefabIterative:" + text, ex);
					continue;
				}
				num += list2.Count;
				foreach (ZDO item in list2)
				{
					TryResolveZdoToMonsterAI(item, list, seen, ref instancesFound, ref prefabLive, ref prefabMai);
				}
			}
			return num;
		}

		private static int AddFromZdoManSectorObjects(List<MonsterAI> list, HashSet<int> seen, ref int instancesFound, ref int prefabLive, ref int prefabMai)
		{
			//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_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_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: 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_0087: 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_00da: Unknown result type (might be due to invalid IL or missing references)
			if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return 0;
			}
			List<Vector3> list2 = CollectPlayerPositions();
			if (list2.Count == 0)
			{
				return 0;
			}
			List<ZDO> list3 = new List<ZDO>();
			List<ZDO> list4 = new List<ZDO>();
			HashSet<long> hashSet = new HashSet<long>();
			int num = 0;
			SimulationDistance originalDistance = SimulationDistance.OriginalDistance;
			foreach (Vector3 item2 in list2)
			{
				Vector2s zone;
				try
				{
					zone = ZoneSystem.GetZone(item2);
				}
				catch (Exception ex)
				{
					LogPathFailureOnce("ZoneSystem.GetZone", ex);
					continue;
				}
				list3.Clear();
				list4.Clear();
				try
				{
					ZDOMan.instance.FindSectorObjects(zone, originalDistance, list3, list4);
				}
				catch (Exception ex2)
				{
					LogPathFailureOnce("FindSectorObjects", ex2);
					continue;
				}
				List<ZDO>[] array = new List<ZDO>[2] { list3, list4 };
				for (int i = 0; i < array.Length; i++)
				{
					foreach (ZDO item3 in array[i])
					{
						if (item3 != null)
						{
							long item;
							try
							{
								item = ValheimIds.ToLong(item3.m_uid);
							}
							catch
							{
								item = ((object)item3).GetHashCode();
							}
							if (hashSet.Add(item) && IsLikelyEnemyPrefabZdo(item3))
							{
								num++;
								TryResolveZdoToMonsterAI(item3, list, seen, ref instancesFound, ref prefabLive, ref prefabMai);
							}
						}
					}
				}
			}
			return num;
		}

		public static bool IsLikelyEnemyPrefabZdo(ZDO zdo)
		{
			try
			{
				int prefab = zdo.GetPrefab();
				if (prefab == 0)
				{
					return false;
				}
				GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab);
				if ((Object)(object)prefab2 == (Object)null)
				{
					return false;
				}
				string text = ((Object)prefab2).name ?? "";
				string[] knownEnemyPrefabs = KnownEnemyPrefabs;
				foreach (string value in knownEnemyPrefabs)
				{
					if (text.StartsWith(value, StringComparison.OrdinalIgnoreCase))
					{
						return true;
					}
				}
				return text.StartsWith("Skeleton", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Greyling", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Greydwarf", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Draugr", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Goblin", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Fuling", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Charred", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Wolf", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Blob", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Seeker", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Tick", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Gjall", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Asksvin", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Drake", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Hatchling", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Troll", StringComparison.OrdinalIgnoreCase);
			}
			catch
			{
				return false;
			}
		}

		private static void TryResolveZdoToMonsterAI(ZDO zdo, List<MonsterAI> list, HashSet<int> seen, ref int instancesFound, ref int prefabLive, ref int prefabMai)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (zdo == null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return;
			}
			try
			{
				ZNetView val = ZNetScene.instance.FindInstance(zdo);
				if ((Object)(object)val != (Object)null)
				{
					instancesFound++;
					prefabLive++;
					int maiHits = 0;
					TryAddFromZNetView(val, list, seen, ref maiHits);
					prefabMai += maiHits;
					return;
				}
			}
			catch
			{
			}
			try
			{
				GameObject val2 = ZNetScene.instance.FindInstance(zdo.m_uid);
				if ((Object)(object)val2 == (Object)null)
				{
					return;
				}
				instancesFound++;
				prefabLive++;
				MonsterAIRegistry.RegisterFromComponent((Component?)(object)val2.GetComponent<ZNetView>());
				MonsterAI val3 = null;
				try
				{
					val3 = val2.GetComponent<MonsterAI>();
				}
				catch
				{
				}
				if ((Object)(object)val3 == (Object)null)
				{
					try
					{
						val3 = val2.GetComponentInChildren<MonsterAI>(true);
					}
					catch
					{
					}
				}
				if ((Object)(object)val3 != (Object)null)
				{
					prefabMai++;
					if (seen.Add(((Object)val3).GetInstanceID()))
					{
						list.Add(val3);
					}
					return;
				}
				BaseAI val4 = null;
				try
				{
					val4 = val2.GetComponent<BaseAI>();
				}
				catch
				{
				}
				if ((Object)(object)val4 == (Object)null)
				{
					try
					{
						val4 = val2.GetComponentInChildren<BaseAI>(true);
					}
					catch
					{
					}
				}
				if ((Object)(object)val4 != (Object)null)
				{
					MonsterAIRegistry.RegisterBase(val4);
				}
				Character val5 = null;
				try
				{
					val5 = val2.GetComponent<Character>();
				}
				catch
				{
				}
				if ((Object)(object)val5 == (Object)null)
				{
					try
					{
						val5 = val2.GetComponentInChildren<Character>(true);
					}
					catch
					{
					}
				}
				if ((Object)(object)val5 == (Object)null)
				{
					return;
				}
				try
				{
					if (val5.IsPlayer())
					{
						return;
					}
				}
				catch
				{
				}
				int count = list.Count;
				TryAddMonsterAIFromCharacter(val5, list, seen);
				if (list.Count > count)
				{
					prefabMai++;
				}
			}
			catch
			{
			}
		}

		private static int AddFromFindObjectsOfType(List<MonsterAI> list, HashSet<int> seen)
		{
			MonsterAI[] array = null;
			try
			{
				array = Object.FindObjectsByType<MonsterAI>((FindObjectsSortMode)0);
			}
			catch
			{
				try
				{
					array = Object.FindObjectsOfType<MonsterAI>();
				}
				catch (Exception ex)
				{
					LogPathFailureOnce("FindObjectsOfType.inner", ex);
					return 0;
				}
			}
			if (array == null)
			{
				return 0;
			}
			MonsterAI[] array2 = array;
			foreach (MonsterAI val in array2)
			{
				if (!((Object)(object)val == (Object)null) && seen.Add(((Object)val).GetInstanceID()))
				{
					list.Add(val);
				}
			}
			return array.Length;
		}

		private static int AddFromResourcesFindObjectsOfTypeAll(List<MonsterAI> list, HashSet<int> seen)
		{
			//IL_0040: 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)
			MonsterAI[] array;
			try
			{
				array = Resources.FindObjectsOfTypeAll<MonsterAI>();
			}
			catch (Exception ex)
			{
				LogPathFailureOnce("Resources.FindObjectsOfTypeAll.inner", ex);
				return 0;
			}
			if (array == null)
			{
				return 0;
			}
			int num = 0;
			MonsterAI[] array2 = array;
			foreach (MonsterAI val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				try
				{
					Scene scene = ((Component)val).gameObject.scene;
					if (!((Scene)(ref scene)).IsValid())
					{
						continue;
					}
				}
				catch
				{
				}
				num++;
				if (seen.Add(((Object)val).GetInstanceID()))
				{
					list.Add(val);
				}
			}
			return num;
		}

		private static void TryAddMonsterAIFromCharacter(Character ch, List<MonsterAI> list, HashSet<int> seen)
		{
			if ((Object)(object)ch == (Object)null)
			{
				return;
			}
			MonsterAI val = null;
			try
			{
				val = ((Component)ch).GetComponent<MonsterAI>();
			}
			catch
			{
			}
			if ((Object)(object)val == (Object)null)
			{
				try
				{
					val = ((Component)ch).GetComponentInChildren<MonsterAI>(true);
				}
				catch
				{
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				try
				{
					BaseAI component = ((Component)ch).GetComponent<BaseAI>();
					MonsterAI val2 = (MonsterAI)(((object)((component is MonsterAI) ? component : null)) ?? ((object)/*isinst with value type is only supported in some contexts*/));
					if ((Object)(object)val2 != (Object)null)
					{
						val = val2;
					}
				}
				catch
				{
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				try
				{
					BaseAI baseAI = ch.GetBaseAI();
					MonsterAI val3 = (MonsterAI)(object)((baseAI is MonsterAI) ? baseAI : null);
					if (val3 != null)
					{
						val = val3;
					}
					else if ((Object)(object)baseAI != (Object)null)
					{
						MonsterAIRegistry.RegisterBase(baseAI);
						val = ((Component)baseAI).GetComponent<MonsterAI>() ?? ((Component)baseAI).GetComponentInChildren<MonsterAI>(true);
					}
				}
				catch
				{
				}
			}
			if (!((Object)(object)val == (Object)null))
			{
				MonsterAIRegistry.Register(val);
				if (seen.Add(((Object)val).GetInstanceID()))
				{
					list.Add(val);
				}
			}
		}

		private static bool TryAddMonsterAIs(IEnumerable? source, List<MonsterAI> list, HashSet<int> seen)
		{
			if (source == null)
			{
				return false;
			}
			foreach (object item in source)
			{
				if (item == null)
				{
					continue;
				}
				MonsterAI val = (MonsterAI)((item is MonsterAI) ? item : null);
				if (val != null)
				{
					if (seen.Add(((Object)val).GetInstanceID()))
					{
						list.Add(val);
						MonsterAIRegistry.Register(val);
					}
					continue;
				}
				BaseAI val2 = (BaseAI)((item is BaseAI) ? item : null);
				if (val2 != null)
				{
					try
					{
						MonsterAI val3 = (MonsterAI)(((object)((val2 is MonsterAI) ? val2 : null)) ?? ((object)((Component)val2).GetComponent<MonsterAI>()));
						if ((Object)(object)val3 != (Object)null && seen.Add(((Object)val3).GetInstanceID()))
						{
							list.Add(val3);
							MonsterAIRegistry.Register(val3);
						}
					}
					catch
					{
					}
					continue;
				}
				Component val4 = (Component)((item is Component) ? item : null);
				if (val4 == null)
				{
					continue;
				}
				try
				{
					MonsterAI val5 = (MonsterAI)(((object)((val4 is MonsterAI) ? val4 : null)) ?? ((object)val4.GetComponent<MonsterAI>()));
					if ((Object)(object)val5 != (Object)null && seen.Add(((Object)val5).GetInstanceID()))
					{
						list.Add(val5);
						MonsterAIRegistry.Register(val5);
					}
				}
				catch
				{
				}
			}
			return true;
		}

		private static void LogPathFailureOnce(string path, Exception ex)
		{
			if (_loggedPathFailures.Add(path))
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("discovery path " + path + " failed: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}

		public static List<Vector3> CollectPlayerPositions()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: 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_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			List<Vector3> list = new List<Vector3>();
			try
			{
				List<Player> allPlayers = Player.GetAllPlayers();
				if (allPlayers != null)
				{
					foreach (Player item in allPlayers)
					{
						if ((Object)(object)item == (Object)null)
						{
							continue;
						}
						try
						{
							if (((Character)item).IsDead())
							{
								continue;
							}
						}
						catch
						{
						}
						list.Add(((Component)item).transform.position);
					}
				}
			}
			catch (Exception ex)
			{
				LogPathFailureOnce("Player.GetAllPlayers.positions", ex);
			}
			if (list.Count > 0)
			{
				return list;
			}
			try
			{
				foreach (Character item2 in EnumeratePlayerCharacters())
				{
					if ((Object)(object)item2 == (Object)null)
					{
						continue;
					}
					try
					{
						if (item2.IsDead())
						{
							continue;
						}
					}
					catch
					{
					}
					list.Add(((Component)item2).transform.position);
				}
			}
			catch
			{
			}
			if (list.Count > 0)
			{
				return list;
			}
			try
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					List<PlayerInfo> playerList = ZNet.instance.GetPlayerList();
					if (playerList != null)
					{
						foreach (PlayerInfo item3 in playerList)
						{
							Vector3 position = item3.m_position;
							if (!(position == Vector3.zero) || item3.m_publicPosition)
							{
								list.Add(position);
							}
						}
					}
				}
			}
			catch (Exception ex2)
			{
				LogPathFailureOnce("ZNet.GetPlayerList", ex2);
			}
			return list;
		}

		public static List<Player> CollectPlayers()
		{
			List<Player> list = new List<Player>();
			try
			{
				List<Player> allPlayers = Player.GetAllPlayers();
				if (allPlayers != null)
				{
					foreach (Player item in allPlayers)
					{
						if ((Object)(object)item != (Object)null)
						{
							list.Add(item);
						}
					}
				}
				if (list.Count > 0)
				{
					return list;
				}
			}
			catch (Exception ex)
			{
				LogPathFailureOnce("Player.GetAllPlayers", ex);
			}
			try
			{
				foreach (Character item2 in EnumeratePlayerCharacters())
				{
					Player val = (Player)(object)((item2 is Player) ? item2 : null);
					if (val != null)
					{
						list.Add(val);
					}
				}
			}
			catch
			{
			}
			return list;
		}

		public static List<Character> EnumeratePlayerCharacters()
		{
			List<Character> list = new List<Character>();
			try
			{
				List<Character> allCharacters = Character.GetAllCharacters();
				if (allCharacters != null)
				{
					foreach (Character item in allCharacters)
					{
						if ((Object)(object)item == (Object)null)
						{
							continue;
						}
						try
						{
							if (item.IsPlayer())
							{
								list.Add(item);
							}
						}
						catch
						{
						}
					}
					if (list.Count > 0)
					{
						return list;
					}
				}
			}
			catch
			{
			}
			try
			{
				List<IMonoUpdater> instances = Character.Instances;
				if (instances != null)
				{
					foreach (IMonoUpdater item2 in instances)
					{
						Character val = (Character)(object)((item2 is Character) ? item2 : null);
						if (val == null)
						{
							continue;
						}
						try
						{
							if (val.IsPlayer())
							{
								list.Add(val);
							}
						}
						catch
						{
						}
					}
					if (list.Count > 0)
					{
						return list;
					}
				}
			}
			catch
			{
			}
			try
			{
				List<Player> allPlayers = Player.GetAllPlayers();
				if (allPlayers != null)
				{
					foreach (Player item3 in allPlayers)
					{
						if ((Object)(object)item3 != (Object)null)
						{
							list.Add((Character)(object)item3);
						}
					}
				}
			}
			catch
			{
			}
			return list;
		}

		public static void MaybeDumpSceneInstances(bool force)
		{
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!force && _sceneDumpDone && realtimeSinceStartup - _lastSceneDumpRealtime < 14f)
			{
				return;
			}
			if (!_zNetSceneInstancesFieldResolved)
			{
				_zNetSceneInstancesFieldResolved = true;
				try
				{
					_zNetSceneInstancesField = typeof(ZNetScene).GetField("m_instances", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				}
				catch
				{
					_zNetSceneInstancesField = null;
				}
			}
			if (_zNetSceneInstancesField == null)
			{
				return;
			}
			object value;
			try
			{
				value = _zNetSceneInstancesField.GetValue(ZNetScene.instance);
			}
			catch
			{
				return;
			}
			if (value == null)
			{
				return;
			}
			List<ZNetView> list = new List<ZNetView>();
			try
			{
				if (value is IDictionary dictionary)
				{
					foreach (DictionaryEntry item in dictionary)
					{
						object? value2 = item.Value;
						ZNetView val = (ZNetView)((value2 is ZNetView) ? value2 : null);
						if (val != null && (Object)(object)val != (Object)null)
						{
							list.Add(val);
						}
					}
				}
				else if (value is IEnumerable enumerable)
				{
					foreach (object item2 in enumerable)
					{
						ZNetView val2 = null;
						ZNetView val3 = (ZNetView)((item2 is ZNetView) ? item2 : null);
						if (val3 != null)
						{
							val2 = val3;
						}
						else if (item2 != null)
						{
							Type type = item2.GetType();
							if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<, >))
							{
								object? obj3 = type.GetProperty("Value")?.GetValue(item2);
								val2 = (ZNetView)((obj3 is ZNetView) ? obj3 : null);
							}
						}
						if ((Object)(object)val2 != (Object)null)
						{
							list.Add(val2);
						}
					}
				}
			}
			catch (Exception ex)
			{
				LogPathFailureOnce("sceneDump.enumerate", ex);
				return;
			}
			if (list.Count == 0 && !force)
			{
				return;
			}
			int num = Math.Min(25, list.Count);
			for (int i = 0; i < num; i++)
			{
				ZNetView val4 = list[i];
				try
				{
					string text = "?";
					bool flag = false;
					try
					{
						ZDO zDO = val4.GetZDO();
						if (zDO != null)
						{
							flag = (Object)(object)ZNetScene.instance.FindInstance(zDO) != (Object)null;
							int prefab = zDO.GetPrefab();
							GameObject val5 = ((prefab != 0) ? ZNetScene.instance.GetPrefab(prefab) : null);
							if ((Object)(object)val5 != (Object)null)
							{
								text = ((Object)val5).name;
							}
							else if ((Object)(object)((Component)val4).gameObject != (Object)null)
							{
								text = ((Object)((Component)val4).gameObject).name;
							}
						}
						else if ((Object)(object)((Component)val4).gameObject != (Object)null)
						{
							text = ((Object)((Component)val4).gameObject).name;
						}
					}
					catch
					{
						try
						{
							text = (((Object)(object)((Component)val4).gameObject != (Object)null) ? ((Object)((Component)val4).gameObject).name : "?");
						}
						catch
						{
							text = "?";
						}
					}
					string text2 = DescribeAiComponents(((Component)val4).gameObject);
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)$"FactionTactics sceneDump: [{i}] prefab={text} live={flag} comps=[{text2}]");
					}
				}
				catch (Exception ex2)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogInfo((object)$"FactionTactics sceneDump: [{i}] error={ex2.GetType().Name}:{ex2.Message}");
					}
				}
			}
			_sceneDumpDone = true;
			_lastSceneDumpRealtime = realtimeSinceStartup;
			ManualLogSource log3 = Plugin.Log;
			if (log3 != null)
			{
				log3.LogInfo((object)$"FactionTactics sceneDump: done showing={num}/{list.Count}");
			}
		}

		private static string DescribeAiComponents(GameObject? go)
		{
			if ((Object)(object)go == (Object)null)
			{
				return "null";
			}
			List<string> names = new List<string>();
			try
			{
				AddIf("Character", (Object)(object)go.GetComponent<Character>() != (Object)null || (Object)(object)go.GetComponentInChildren<Character>(true) != (Object)null);
				AddIf("Humanoid", (Object)(object)go.GetComponent<Humanoid>() != (Object)null || (Object)(object)go.GetComponentInChildren<Humanoid>(true) != (Object)null);
				AddIf("BaseAI", (Object)(object)go.GetComponent<BaseAI>() != (Object)null || (Object)(object)go.GetComponentInChildren<BaseAI>(true) != (Object)null);
				AddIf("MonsterAI", (Object)(object)go.GetComponent<MonsterAI>() != (Object)null || (Object)(object)go.GetComponentInChildren<MonsterAI>(true) != (Object)null);
				AddIf("AnimalAI", (Object)(object)go.GetComponent<AnimalAI>() != (Object)null || (Object)(object)go.GetComponentInChildren<AnimalAI>(true) != (Object)null);
				AddIf("ZNetView", (Object)(object)go.GetComponent<ZNetView>() != (Object)null);
				try
				{
					BaseAI val = go.GetComponent<BaseAI>() ?? go.GetComponentInChildren<BaseAI>(true);
					if ((Object)(object)val != (Object)null)
					{
						string name = ((object)val).GetType().Name;
						if (!names.Contains(name))
						{
							names.Add(name);
						}
					}
				}
				catch
				{
				}
			}
			catch (Exception ex)
			{
				return "err:" + ex.GetType().Name;
			}
			if (names.Count <= 0)
			{
				return "none";
			}
			return string.Join(",", names);
			void AddIf(string label, bool present)
			{
				if (present)
				{
					names.Add(label);
				}
			}
		}

		public static bool TryGetEnemyPrefabName(ZDO zdo, out string prefabName)
		{
			prefabName = "";
			if (zdo == null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return false;
			}
			try
			{
				int prefab = zdo.GetPrefab();
				if (prefab == 0)
				{
					return false;
				}
				GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab);
				if ((Object)(object)prefab2 == (Object)null)
				{
					return false;
				}
				prefabName = ((Object)prefab2).name ?? "";
				return !string.IsNullOrEmpty(prefabName);
			}
			catch
			{
				return false;
			}
		}

		public static List<ZDO> EnumerateEnemyZdosNearPlayers(float discoveryRadius)
		{
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: 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)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			List<ZDO> list = new List<ZDO>();
			HashSet<long> seen = new HashSet<long>();
			LastScanEnemyZdoCount = 0;
			if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return list;
			}
			List<Vector3> playerPositions = CollectPlayerPositions();
			float radius = Mathf.Max(1f, discoveryRadius);
			bool radiusFilter = playerPositions.Count > 0;
			try
			{
				List<ZDO> list2 = new List<ZDO>();
				string[] knownEnemyPrefabs = KnownEnemyPrefabs;
				foreach (string text in knownEnemyPrefabs)
				{
					list2.Clear();
					int num = 0;
					try
					{
						while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(text, list2, ref num))
						{
						}
					}
					catch (Exception ex)
					{
						LogPathFailureOnce("EnumerateEnemyZdos.prefab:" + text, ex);
						continue;
					}
					foreach (ZDO item in list2)
					{
						Consider(item);
					}
				}
			}
			catch (Exception ex2)
			{
				LogPathFailureOnce("EnumerateEnemyZdos.prefabWalk", ex2);
			}
			try
			{
				if (playerPositions.Count > 0)
				{
					List<ZDO> list3 = new List<ZDO>();
					List<ZDO> list4 = new List<ZDO>();
					SimulationDistance originalDistance = SimulationDistance.OriginalDistance;
					foreach (Vector3 item2 in playerPositions)
					{
						Vector2s zone;
						try
						{
							zone = ZoneSystem.GetZone(item2);
						}
						catch (Exception ex3)
						{
							LogPathFailureOnce("EnumerateEnemyZdos.GetZone", ex3);
							continue;
						}
						list3.Clear();
						list4.Clear();
						try
						{
							ZDOMan.instance.FindSectorObjects(zone, originalDistance, list3, list4);
						}
						catch (Exception ex4)
						{
							LogPathFailureOnce("EnumerateEnemyZdos.FindSectorObjects", ex4);
							continue;
						}
						List<ZDO>[] array = new List<ZDO>[2] { list3, list4 };
						for (int i = 0; i < array.Length; i++)
						{
							foreach (ZDO item3 in array[i])
							{
								Consider(item3);
							}
						}
					}
				}
			}
			catch (Exception ex5)
			{
				LogPathFailureOnce("EnumerateEnemyZdos.sector", ex5);
			}
			LastScanEnemyZdoCount = list.Count;
			return list;
			void Consider(ZDO zdo)
			{
				//IL_0006: 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_0040: 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_005c: Unknown result type (might be due to invalid IL or missing references)
				if (zdo != null)
				{
					long num2;
					try
					{
						num2 = ValheimIds.ToLong(zdo.m_uid);
					}
					catch
					{
						num2 = ((object)zdo).GetHashCode();
					}
					if (num2 != 0L && seen.Add(num2) && IsLikelyEnemyPrefabZdo(zdo))
					{
						Vector3 position;
						try
						{
							position = zdo.GetPosition();
						}
						catch
						{
							return;
						}
						if (radiusFilter)
						{
							bool flag = false;
							for (int j = 0; j < playerPositions.Count; j++)
							{
								if (Vector3.Distance(position, playerPositions[j]) <= radius)
								{
									flag = true;
									break;
								}
							}
							if (!flag)
							{
								return;
							}
						}
						list.Add(zdo);
					}
				}
			}
		}

		public static void LogDedicatedDiscoveryOnce(int players, int monsterAi)
		{
			if (!_loggedDedicatedDiscovery)
			{
				_loggedDedicatedDiscovery = true;
				int num = 0;
				string arg = "?";
				try
				{
					num = EnemyOwnershipDirector.SnapshotLiveMonsterAIs().Count;
				}
				catch
				{
				}
				try
				{
					arg = MonsterAIRegistry.Diagnostics();
				}
				catch
				{
				}
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)($"dedicated discovery 0.1.10: chars={LastScanCharacterCount} " + $"baseAiInstances={LastScanBaseAiInstances} " + $"sceneInstances={LastScanSceneInstances} sceneNr={LastScanSceneNrOfInstances} " + $"prefabZdos={LastScanPrefabZdos} prefabLive={LastScanPrefabLive} prefabMai={LastScanPrefabMai} " + $"findObjects={LastScanFindObjects} findAll={LastScanFindAll} registry={LastScanRegistry} animalAI={LastScanAnimalAi} " + $"ownershipLive={num} regDiag=[{arg}] " + $"playerZdos={LastScanPlayerZdoCount} players={players} monsterAI={monsterAi}"));
				}
			}
		}
	}
}
namespace FactionTactics.Squad
{
	public interface IActionScorer
	{
		float ScoreAction(DoctrineOrderKind order, SquadSnapshot snapshot);
	}
	public interface IRoleScorer
	{
		float ScoreRole(SquadMemberView member, SquadRole role, SquadSnapshot snapshot);
	}
	public interface ISquadDiscovery
	{
		IReadOnlyList<SquadUnit> Discover();
	}
	public sealed class NullRoleScorer : IRoleScorer
	{
		public float ScoreRole(SquadMemberView member, SquadRole role, SquadSnapshot snapshot)
		{
			return 0f;
		}
	}
	public sealed class NullActionScorer : IActionScorer
	{
		public float ScoreAction(DoctrineOrderKind order, SquadSnapshot snapshot)
		{
			return 0f;
		}
	}
	public sealed class SquadDirector
	{
		private readonly struct CommandingSquad
		{
			public SquadUnit Squad { get; }

			public SquadRuntimeState State { get; }

			public int Alive { get; }

			public CommandingSquad(SquadUnit squad, SquadRuntimeState state, int alive)
			{
				Squad = squad;
				State = state;
				Alive = alive;
			}
		}

		public const float BrokenCasualtyThreshold = 0.5f;

		public const int RuntimePruneTicks = 8;

		private readonly ISquadDiscovery _discovery;

		private readonly ICommander _commander;

		private readonly DoctrinePackRegistry _registry;

		private readonly IRoleScorer _roleScorer;

		private readonly IActionScorer _actionScorer;

		private readonly OrderApplicator _applicator;

		private readonly SiegeDirector _siege;

		private readonly AmbushAmbienceDirector _ambience;

		private readonly List<SquadUnit> _active = new List<SquadUnit>();

		private readonly List<SquadRuntimeState> _runtime = new List<SquadRuntimeState>();

		private int _nextStableSerial;

		private float _heartbeatAge;

		private const float HeartbeatIntervalSeconds = 15f;

		private int _lastDiscoveredCount;

		public IReadOnlyList<SquadUnit> ActiveSquads => _active;

		public IReadOnlyList<SquadRuntimeState> RuntimeStates => _runtime;

		public int LastDiscoveredCount => _lastDiscoveredCount;

		public int LastCandidateGauge
		{
			get
			{
				if (_discovery is SquadDiscovery squadDiscovery)
				{
					return Math.Max(squadDiscovery.LastCandidateCount, squadDiscovery.LastZdoCandidateCount);
				}
				return _lastDiscoveredCount;
			}
		}

		public SquadDirector(ISquadDiscovery discovery, ICommander commander, DoctrinePackRegistry registry, IRoleScorer roleScorer, IActionScorer actionScorer, OrderApplicator applicator, SiegeDirector? siege = null, AmbushAmbienceDirector? ambience = null)
		{
			_discovery = discovery;
			_commander = commander;
			_registry = registry;
			_roleScorer = roleScorer;
			_actionScorer = actionScorer;
			_applicator = applicator;
			_siege = siege ?? new SiegeDirector();
			_ambience = ambience ?? new AmbushAmbienceDirector();
		}

		public IEnumerable<string> FormatStatusLines()
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (SquadUnit item in _active)
			{
				string key = string.Concat(str2: item.CurrentOrder?.OrderKind.ToString() ?? "none", str0: item.Doctrine?.Id ?? "?", str1: ":");
				dictionary[key] = ((!dictionary.TryGetValue(key, out var value)) ? 1 : (value + 1));
			}
			string summary = "none";
			if (dictionary.Count > 0)
			{
				List<string> list = new List<string>();
				foreach (KeyValuePair<string, int> item2 in dictionary.OrderBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> k) => k.Key, StringComparer.Ordinal))
				{
					list.Add($"{item2.Key}={item2.Value}");
				}
				summary = string.Join(", ", list);
			}
			yield return $"squads: discovered={_lastDiscoveredCount} active={_active.Count} runtime={_runtime.Count}";
			yield return "orders: [" + summary + "]";
			yield return "theater: " + FormatTheaterCounts();
			long writes = IntentZdoSync.Writes;
			long schemaWrites = IntentZdoSync.SchemaWrites;
			long readsOk = IntentZdoSync.ReadsOk;
			long readsStale = IntentZdoSync.ReadsStale;
			long schemaMismatches = IntentZdoSync.SchemaMismatches;
			long ownerDriveCount = MonsterAI_UpdateAI_Patch.OwnerDriveCount;
			long rpcIntentsSent = IntentRpcSync.RpcIntentsSent;
			long rpcPeerInvokes = IntentRpcSync.RpcPeerInvokes;
			long rpcBatchesSent = IntentRpcSync.RpcBatchesSent;
			yield return $"zdo: writes={writes} schemaWrites={schemaWrites} readsOk={readsOk} stale={readsStale} mismatch={schemaMismatches} ownerDrives={ownerDriveCount} rpcIntentsSent={rpcIntentsSent} rpcPeerInvokes={rpcPeerInvokes} rpcBatchesSent={rpcBatchesSent}";
			long swings = CombatDriver.Swings;
			long holdBlocks = CombatDriver.HoldBlocks;
			long slotLocks = FormationSlotLock.SlotLocks;
			long reshuffles = FormationSlotLock.Reshuffles;
			yield return $"combat: swings={swings} holdBlocks={holdBlocks} slotLocks={slotLocks} reshuffles={reshuffles}";
			if (_discovery is SquadDiscovery squadDiscovery)
			{
				yield return $"discovery: zdoCandidates={squadDiscovery.LastZdoCandidateCount} prefabZdos={squadDiscovery.LastPrefabZdos} prefabLive={squadDiscovery.LastPrefabLive} mai={squadDiscovery.LastPrefabMai}";
			}
		}

		public void Tick(float dt)
		{
			OrderScoreContext.ActionScorer = _actionScorer;
			try
			{
				TickCore(dt);
			}
			finally
			{
				OrderScoreContext.ActionScorer = null;
			}
		}

		private void TickCore(float dt)
		{
			_active.Clear();
			List<SquadUnit> list = MergeStragglers(_discovery.Discover());
			_lastDiscoveredCount = list.Count;
			HashSet<SquadRuntimeState> hashSet = new HashSet<SquadRuntimeState>();
			List<CommandingSquad> list2 = new List<CommandingSquad>();
			foreach (SquadUnit item3 in list)
			{
				SquadRuntimeState squadRuntimeState = MatchOrCreateRuntime(item3);
				hashSet.Add(squadRuntimeState);
				squadRuntimeState.TicksUnseen = 0;
				ApplyRuntimeToSquad(item3, squadRuntimeState, dt);
				int num = CountAlive(item3);
				int count = item3.Members.Count;
				string text = item3.Doctrine?.Id ?? "?";
				int num2 = EffectiveMinSize(text);
				squadRuntimeState.PeakAlive = Math.Max(squadRuntimeState.PeakAlive, Math.Max(num, count));
				if (squadRuntimeState.PeakAlive >= num2)
				{
					squadRuntimeState.EverMetMinSize = true;
				}
				squadRuntimeState.MemberIds.Clear();
				foreach (SquadMemberView member in item3.Members)
				{
					squadRuntimeState.MemberIds.Add(member.InstanceId);
				}
				item3.PeakAlive = squadRuntimeState.PeakAlive;
				if (num < num2)
				{
					(float ratio, bool isBroken) tuple = ComputeCasualties(num, squadRuntimeState, num2);
					float item = tuple.ratio;
					bool item2 = tuple.isBroken;
					item3.LastCasualtyRatio = item;
					item3.LastIsBroken = item2;
					if (!squadRuntimeState.EverMetMinSize || num <= 0)
					{
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogInfo((object)$"Squad {item3.SquadId} below minSize alive={num} roster={count} min={num2} doctrine={text}");
						}
						ClearTheaterRole(squadRuntimeState);
						continue;
					}
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogInfo((object)$"Squad {item3.SquadId} remnant keep-alive alive={num} peak={squadRuntimeState.PeakAlive} min={num2} doctrine={text}");
					}
				}
				list2.Add(new CommandingSquad(item3, squadRuntimeState, num));
			}
			AssignTheaterRoles(list2, dt);
			foreach (CommandingSquad item4 in list2)
			{
				SquadUnit squad = item4.Squad;
				SquadRuntimeState state = item4.State;
				int alive = item4.Alive;
				string text2 = squad.Doctrine?.Id ?? "?";
				RefineRolesWithScorer(squad);
				ThreatAssessment threatAssessment = AssessThreats(squad, state, EffectiveMinSize(squad.Doctrine?.Id));
				squad.LastCasualtyRatio = threatAssessment.CasualtyRatio;
				squad.LastIsBroken = threatAssessment.IsBroken;
				SquadSnapshot squadSnapshot = SquadSnapshot.FromSquad(squad, threatAssessment);
				squadSnapshot.TheaterRole = state.TheaterRole;
				BandedCadence.ReadInto(squadSnapshot, state);
				SquadOrder squadOrder = _commander.Propose(squadSnapshot);
				BandedCadence.WriteBack(state, squadSnapshot);
				if (squadOrder == null)
				{
					ManualLogSource log3 = Plugin.Log;
					if (log3 != null)
					{
						log3.LogInfo((object)("Squad " + squad.SquadId + " Propose returned null doctrine=" + text2));
					}
					continue;
				}
				squadOrder = MaybeRescoreOrder(squadOrder, squadSnapshot);
				squadOrder = (squad.CurrentOrder = ApplyOrderStability(squad, state, squadOrder, squadSnapshot));
				if (state.PreviousOrderKind != squadOrder.OrderKind)
				{
					state.OrderAgeSeconds = 0f;
					squad.OrderAgeSeconds = 0f;
				}
				squad.PreviousOrderKind = squadOrder.OrderKind;
				state.PreviousOrderKind = squadOrder.OrderKind;
				try
				{
					_applicator.Apply(squad, squadOrder, state);
					_active.Add(squad);
				}
				catch (Exception ex)
				{
					ManualLogSource log4 = Plugin.Log;
					if (log4 != null)
					{
						log4.LogInfo((object)("Squad " + squad.SquadId + " Apply failed doctrine=" + text2 + ": " + ex.GetType().Name + ": " + ex.Message));
					}
					continue;
				}
				ConfigEntry<bool> debugLogging = PluginConfig.DebugLogging;
				if (debugLogging != null && debugLogging.Value)
				{
					string text3 = ((!squadSnapshot.AssaultActive) ? "" : (squadSnapshot.PlayersNearAssault ? " Assault/hot" : " Assault/quiet"));
					string text4 = ((state.TheaterRole == TheaterRole.None) ? "" : $" theater={state.TheaterRole}");
					ManualLogSource log5 = Plugin.Log;
					if (log5 != null)
					{
						log5.LogInfo((object)$"[{squad.Doctrine?.DisplayName ?? text2}]{text3} {squad.SquadId} n={alive} peak={state.PeakAlive} cas={threatAssessment.CasualtyRatio:0.00} → {squadOrder.OrderKind} ({squadOrder.Formation}/{squadOrder.Stance}){text4}");
					}
				}
			}
			PruneUnseenRuntime(hashSet);
			_ = _registry;
			try
			{
				_ambience.Tick(_active);
			}
			catch (Exception ex2)
			{
				ManualLogSource log6 = Plugin.Log;
				if (log6 != null)
				{
					log6.LogDebug((object)("AmbushAmbience.Tick: " + ex2.GetType().Name + ": " + ex2.Message));
				}
			}
			try
			{
				IntentRpcSync.EnsureRegistered();
				IntentRpcSync.FlushBroadcast();
			}
			catch (Exception ex3)
			{
				ManualLogSource log7 = Plugin.Log;
				if (log7 != null)
				{
					log7.LogDebug((object)("IntentRpcSync.FlushBroadcast: " + ex3.GetType().Name + ": " + ex3.Message));
				}
			}
			MaybeLogHeartbeat(dt);
		}

		private static void ClearTheaterRole(SquadRuntimeState state)
		{
			state.TheaterRole = TheaterRole.None;
			state.TheaterRoleAgeSeconds = 0f;
			state.HasTheaterFocus = false;
			state.TheaterFocusId = 0L;
		}

		private string FormatTheaterCounts()
		{
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			for (int i = 0; i < _runtime.Count; i++)
			{
				switch (_runtime[i].TheaterRole)
				{
				case TheaterRole.Pin:
					num++;
					break;
				case TheaterRole.Flank:
					num2++;
					break;
				case TheaterRole.Harass:
					num3++;
					break;
				}
			}
			return $"pin={num} flank={num2} harass={num3}";
		}

		private static void AssignTheaterRoles(List<CommandingSquad> commanding, float dt)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			List<(long, Vector3)> world = null;
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				world = SampleWorldPlayers();
			}
			float radius = TheaterCommander.CoEngageRadius();
			List<TheaterSquadView> list = new List<TheaterSquadView>(commanding.Count);
			for (int i = 0; i < commanding.Count; i++)
			{
				CommandingSquad commandingSquad = commanding[i];
				Vector3 centroid = LivingCentroid(commandingSquad.Squad);
				TheaterSquadView theaterSquadView = new TheaterSquadView
				{
					State = commandingSquad.State,
					DoctrineId = (commandingSquad.Squad.Doctrine?.Id ?? ""),
					StableId = (commandingSquad.State.StableId ?? ""),
					Centroid = centroid
				};
				if (TryResolveTheaterFocus(commandingSquad.Squad, commandingSquad.State, centroid, radius, world, out var id, out var pos))
				{
					theaterSquadView.HasFocus = true;
					theaterSquadView.FocusPlayerId = id;
					theaterSquadView.FocusPosition = pos;
				}
				list.Add(theaterSquadView);
			}
			TheaterCommander.Assign(list, dt);
		}

		private static bool TryResolveTheaterFocus(SquadUnit squad, SquadRuntimeState state, Vector3 centroid, float radius, List<(long id, Vector3 pos)>? world, out long id, out Vector3 pos)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: 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_005b: 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_0101: 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_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			if (squad.DebugFocusPlayerId.HasValue)
			{
				id = squad.DebugFocusPlayerId.Value;
				pos = squad.DebugFocusPlayerPosition ?? centroid;
				return true;
			}
			if (world != null && world.Count > 0)
			{
				float num = float.MaxValue;
				long num2 = 0L;
				Vector3 val = Vector3.zero;
				bool flag = false;
				for (int i = 0; i < world.Count; i++)
				{
					float num3 = Vector3.Distance(centroid, world[i].pos);
					if (num3 < num)
					{
						num = num3;
						num2 = world[i].id;
						val = world[i].pos;
						flag = true;
					}
				}
				if (flag && num <= radius)
				{
					id = num2;
					pos = val;
					return true;
				}
				id = 0L;
				pos = Vector3.zero;
				return false;
			}
			if (state.HasStickyPlayer && state.StickyPlayerId != 0L)
			{
				id = state.StickyPlayerId;
				pos = state.StickyPlayerPosition;
				return true;
			}
			id = 0L;
			pos = Vector3.zero;
			return false;
		}

		private static List<(long id, Vector3 pos)> SampleWorldPlayers()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			List<(long, Vector3)> list = new List<(long, Vector3)>();
			try
			{
				List<Player> list2 = ValheimWorldScan.CollectPlayers();
				if (list2 == null)
				{
					return list;
				}
				for (int i = 0; i < list2.Count; i++)
				{
					Player val = list2[i];
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					long num = 0L;
					try
					{
						num = val.GetPlayerID();
					}
					catch
					{
						num = 0L;
					}
					if (num == 0L)
					{
						try
						{
							num = ((Object)val).GetInstanceID();
						}
						catch
						{
							continue;
						}
					}
					Vector3 position;
					try
					{
						position = ((Component)val).transform.position;
					}
					catch
					{
						continue;
					}
					list.Add((num, position));
				}
			}
			catch
			{
			}
			return list;
		}

		private static Vector3 LivingCentroid(SquadUnit squad)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: 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_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_0068: 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)
			Vector3 val = Vector3.zero;
			int num = 0;
			for (int i = 0; i < squad.Members.Count; i++)
			{
				SquadMemberView squadMemberView = squad.Members[i];
				if (squadMemberView.IsAlive)
				{
					val += squadMemberView.Position;
					num++;
				}
			}
			if (num == 0)
			{
				return Vector3.zero;
			}
			return new Vector3(val.x / (float)num, val.y / (float)num, val.z / (float)num);
		}

		private void MaybeLogHeartbeat(float dt)
		{
			bool num = PluginConfig.DebugLogging?.Value ?? false;
			bool flag = PluginConfig.HeartbeatLogging?.Value ?? false;
			if (!num && !flag)
			{
				return;
			}
			_heartbeatAge += dt;
			if (_heartbeatAge < 15f)
			{
				return;
			}
			_heartbeatAge = 0f;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			int num7 = 0;
			int num8 = 0;
			long num9 = 0L;
			long num10 = 0L;
			int num11 = 0;
			int num12 = 0;
			int num13 = 0;
			if (_discovery is SquadDiscovery squadDiscovery)
			{
				num2 = squadDiscovery.LastRegistry;
				num3 = squadDiscovery.LastFindAll;
				num4 = squadDiscovery.LastFindObjects;
				num5 = squadDiscovery.LastSceneInstances;
				num6 = squadDiscovery.LastPrefabZdos;
				num7 = squadDiscovery.LastPrefabLive;
				num8 = squadDiscovery.LastPrefabMai;
				num9 = squadDiscovery.LastUpdateAIHits;
				num10 = squadDiscovery.LastBaseAIUpdateHits;
				num11 = squadDiscovery.LastMonsterAiCount;
				num12 = squadDiscovery.LastCandidateCount;
				num13 = squadDiscovery.LastZdoCandidateCount;
			}
			try
			{
				if (num5 > 0)
				{
					ValheimWorldScan.MaybeDumpSceneInstances(force: true);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("sceneDump heartbeat failed: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			Dictionary<string, int> dictionary2 = new Dictionary<string, int>();
			foreach (SquadUnit item in _active)
			{
				string key = item.CurrentOrder?.OrderKind.ToString() ?? "none";
				dictionary[key] = ((!dictionary.TryGetValue(key, out var value)) ? 1 : (value + 1));
				string key2 = item.CurrentOrder?.Formation.ToString() ?? "none";
				dictionary2[key2] = ((!dictionary2.TryGetValue(key2, out var value2)) ? 1 : (value2 + 1));
			}
			string arg = "none";
			if (dictionary.Count > 0)
			{
				List<string> list = new List<string>();
				foreach (KeyValuePair<string, int> item2 in dictionary)
				{
					list.Add($"{item2.Key}={item2.Value}");
				}
				list.Sort(StringComparer.Ordinal);
				arg = string.Join(",", list);
			}
			string arg2 = "none";
			if (dictionary2.Count > 0)
			{
				List<string> list2 = new List<string>();
				foreach (KeyValuePair<string, int> item3 in dictionary2)
				{
					list2.Add($"{item3.Key}={item3.Value}");
				}
				list2.Sort(StringComparer.Ordinal);
				arg2 = string.Join(",", list2);
			}
			int num14 = 0;
			int num15 = 0;
			int num16 = 0;
			int num17 = 0;
			int num18 = 0;
			int num19 = 0;
			int num20 = 0;
			int num21 = 0;
			num14 = EnemyOwnershipDirector.LastEnemyOwned;
			num15 = EnemyOwnershipDirector.LastEnemyLive;
			num16 = EnemyOwnershipDirector.LastEnemyMai;
			num17 = EnemyOwnershipDirector.LastEnemyServerOwned;
			num18 = EnemyOwnershipDirector.LastEnemyClientOwned;
			num19 = EnemyOwnershipDirector.LastEnemyReclaims;
			num20 = ZDOMan_ReleaseNearbyZDOS_StickyEnemy_Patch.LastStickyKeeps;
			num21 = ZDOMan_ReleaseNearbyZDOS_StickyEnemy_Patch.LastStickyReclaims;
			long num22 = 0L;
			long num23 = 0L;
			long num24 = 0L;
			long num25 = 0L;
			long num26 = 0L;
			long num27 = 0L;
			long num28 = 0L;
			long num29 = 0L;
			long num30 = 0L;
			num22 = IntentZdoSync.Writes;
			num23 = IntentZdoSync.SchemaWrites;
			num24 = IntentZdoSync.ReadsOk;
			num25 = IntentZdoSync.ReadsStale;
			num26 = IntentZdoSync.SchemaMismatches;
			num27 = MonsterAI_UpdateAI_Patch.OwnerDriveCount;
			num28 = IntentRpcSync.RpcIntentsSent;
			num29 = IntentRpcSync.RpcBatchesSent;
			num30 = IntentRpcSync.RpcPeerInvokes;
			long num31 = 0L;
			long num32 = 0L;
			long num33 = 0L;
			num31 = CombatDriver.Swings;
			num32 = CombatDriver.HoldBlocks;
			num33 = FormationSlotLock.SlotLocks;
			ManualLogSource log2 = Plugin.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)($"FactionTactics heartbeat: discovered={_lastDiscoveredCount} active={_active.Count} " + $"squads={_active.Count} orders=[{arg}] " + $"formations=[{arg2}] baseAIUpdateHits={num10} updateAIHits={num9} " + $"registry={num2} findAll={num3} findObjects={num4} " + $"sceneInstances={num5} prefabZdos={num6} prefabLive={num7} prefabMai={num8} " + $"monsterAI={num11} candidates={num12} zdoCandidates={num13} " + $"enemyOwned={num14} enemyLive={num15} enemyMai={num16} " + $"enemyServerOwned={num17} enemyClientOwned={num18} " + $"enemyReclaims={num19} stickyKeeps={num20} stickyReclaims={num21}" + $" zdoIntentsWritten={num22} schemaWrites={num23} zdoReads={num24} zdoStale={num25} schemaMismatch={num26} ownerDrives={num27} rpcIntentsSent={num28} rpcPeerInvokes={num30} rpcBatchesSent={num29}" + $" swings={num31} holdBlocks={num32} slotLocks={num33}"));
			}
		}

		private SquadRuntimeState MatchOrCreateRuntime(SquadUnit squad)
		{
			string text = squad.Doctrine?.Id ?? "";
			SquadRuntimeState squadRuntimeState = null;
			int num = 0;
			foreach (SquadRuntimeState item in _runtime)
			{
				if (string.Equals(item.DoctrineId, text, StringComparison.OrdinalIgnoreCase))
				{
					int num2 = SquadIdentity.Overlap(item, squad.Members);
					if (num2 > 0 && num2 > num)
					{
						num = num2;
						squadRuntimeState = item;
					}
				}
			}
			if (squadRuntimeState != null)
			{
				return squadRuntimeState;
			}
			SquadRuntimeState squadRuntimeState2 = new SquadRuntimeState
			{
				StableId = $"{text}#{++_nextStableSerial}",
				DoctrineId = text
			};
			_runtime.Add(squadRuntimeState2);
			return squadRuntimeState2;
		}

		private static void ApplyRuntimeToSquad(SquadUnit squad, SquadRuntimeState state, float dt)
		{
			state.AgeSeconds += dt;
			state.OrderAgeSeconds += dt;
			FormationSlotLock.TickAge(state, dt);
			squad.AgeSeconds = state.AgeSeconds;
			squad.OrderAgeSeconds = state.OrderAgeSeconds;
			squad.PreviousOrderKind = state.PreviousOrderKind;
			squad.SquadId = state.StableId;
			squad.PeakAlive = state.PeakAlive;
		}

		private void PruneUnseenRuntime(HashSet<SquadRuntimeState> seen)
		{
			for (int num = _runtime.Count - 1; num >= 0; num--)
			{
				SquadRuntimeState squadRuntimeState = _runtime[num];
				if (!seen.Contains(squadRuntimeState))
				{
					squadRuntimeState.TicksUnseen++;
					if (squadRuntimeState.TicksUnseen >= 8)
					{
						_runtime.RemoveAt(num);
					}
				}
			}
		}

		private void RefineRolesWithScorer(SquadUnit squad)
		{
			int minSize = EffectiveMinSize(squad.Doctrine?.Id);
			SquadRuntimeState state = FindRuntime(squad.SquadId);
			ThreatAssessment threats = AssessThreats(squad, state, minSize);
			SquadSnapshot snapshot = SquadSnapshot.FromSquad(squad, threats);
			foreach (SquadMemberView member in squad.Members)
			{
				SquadRole squadRole = member.AssignedRole;
				float num = _roleScorer.ScoreRole(member, squadRole, snapshot);
				foreach (SquadRole value in Enum.GetValues(typeof(SquadRole)))
				{
					if (value != SquadRole.Unassigned)
					{
						float num2 = _roleScorer.ScoreRole(member, value, snapshot);
						if (num2 > num)
						{
							num = num2;
							squadRole = value;
						}
					}
				}
				if (num > 0f)
				{
					member.AssignedRole = squadRole;
				}
			}
		}

		private SquadRuntimeState? FindRuntime(string stableId)
		{
			foreach (SquadRuntimeState item in _runtime)
			{
				if (item.StableId == stableId)
				{
					return item;
				}
			}
			return null;
		}

		private SquadOrder MaybeRescoreOrder(SquadOrder order, SquadSnapshot snapshot)
		{
			if (IsPhaseBScored(snapshot.DoctrineId))
			{
				return order;
			}
			float num = _actionScorer.ScoreAction(order.OrderKind, snapshot);
			if (num == 0f)
			{
				return order;
			}
			DoctrineOrderKind doctrineOrderKind = order.OrderKind;
			float num2 = num;
			foreach (DoctrineOrderKind value in Enum.GetValues(typeof(DoctrineOrderKind)))
			{
				float num3 = _actionScorer.ScoreAction(value, snapshot);
				if (num3 > num2)
				{
					num2 = num3;
					doctrineOrderKind = value;
				}
			}
			if (doctrineOrderKind != order.OrderKind)
			{
				order.OrderKind = doctrineOrderKind;
				order.Source += "+ActionScorer";
			}
			return order;
		}

		private static bool IsPhaseBScored(string? doctrineId)
		{
			if (!string.Equals(doctrineId, "roman", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(doctrineId, "ambush", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static SquadOrder ApplyOrderStability(SquadUnit squad, SquadRuntimeState state, SquadOrder order, SquadSnapshot snapshot)
		{
			DoctrineOrderKind orderKind = order.OrderKind;
			order = ApplyChargeMaxHygiene(squad, state, order, snapshot);
			string text = squad.Doctrine?.Id ?? snapshot.DoctrineId;
			DoctrineOrderKind doctrineOrderKind = OrderTransition.ApplyMinDwell(text, snapshot, state.PreviousOrderKind, order.OrderKind, state.OrderAgeSeconds);
			if (doctrineOrderKind != order.OrderKind)
			{
				order.OrderKind = doctrineOrderKind;
				order.Source += "+Dwell";
			}
			DoctrineOrderKind doctrineOrderKind2 = OrderTransition.EnforceDeathRush(text, snapshot, order.OrderKind);
			if (doctrineOrderKind2 != order.OrderKind)
			{
				order.OrderKind = doctrineOrderKind2;
				order.Source += "+DeathRush";
			}
			if (order.OrderKind != orderKind)
			{
				(FormationType formation, StanceType stance) tuple = ScriptedCommander.PresentationFor(order.OrderKind, text ?? "", order.AssaultActive);
				FormationType item = tuple.formation;
				StanceType item2 = tuple.stance;
				order.Formation = item;
				order.Stance = item2;
			}
			return order;
		}

		private static SquadOrder ApplyChargeMaxHygiene(SquadUnit squad, SquadRuntimeState state, SquadOrder order, SquadSnapshot snapshot)
		{
			if (order == null)
			{
				return order;
			}
			if (string.Equals(squad.Doctrine?.Id, "death-rush", StringComparison.OrdinalIgnoreCase))
			{
				return order;
			}
			if (order.OrderKind != DoctrineOrderKind.Charge)
			{
				return order;
			}
			if (state.PreviousOrderKind != DoctrineOrderKind.Charge)
			{
				return order;
			}
			float num = PluginConfig.ChargeMaxSeconds?.Value ?? 4f;
			if (state.OrderAgeSeconds < num)
			{
				return order;
			}
			string a = squad.Doctrine?.Id ?? "";
			if (string.Equals(a, "ambush", StringComparison.OrdinalIgnoreCase))
			{
				order.OrderKind = DoctrineOrderKind.Kite;
			}
			else if (string.Equals(a, "artillery-jelly", StringComparison.OrdinalIgnoreCase))
			{
				order.OrderKind = DoctrineOrderKind.FocusFire;
			}
			else if (string.Equals(a, "roman", StringComparison.OrdinalIgnoreCase))
			{
				bool flag = snapshot.CountByRole(SquadRole.Missile) > 0;
				order.OrderKind = ((flag && !snapshot.IsBroken && snapshot.CasualtyRatio < 0.45f) ? DoctrineOrderKind.ProtectMissiles : DoctrineOrderKind.RetreatAndReform);
			}
			else
			{
				order.OrderKind = DoctrineOrderKind.RetreatAndReform;
			}
			order.Source += "+ChargeMax";
			return order;
		}

		private static void ApplyFlankAndIsolate(Vector3 centroid, float nearest, ThreatAssessment assessment)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			float num = PluginConfig.FlankSplitMeters?.Value ?? 12f;
			float num2 = PluginConfig.IsolateBuddyMeters?.Value ?? 8f;
			float num3 = 12f;
			try
			{
				List<Vector3> list = ValheimWorldScan.CollectPlayerPositions();
				if (list == null || list.Count == 0)
				{
					return;
				}
				int num4 = -1;
				float num5 = float.MaxValue;
				for (int i = 0; i < list.Count; i++)
				{
					float num6 = Vector3.Distance(centroid, list[i]);
					if (num6 < num5)
					{
						num5 = num6;
						num4 = i;
					}
				}
				bool flag = num5 <= num3 || (nearest < float.MaxValue && nearest <= num3);
				bool flag2 = false;
				if (list.Count >= 2)
				{
					for (int j = 0; j < list.Count; j++)
					{
						if (flag2)
						{
							break;
						}
						for (int k = j + 1; k < list.Count; k++)
						{
							if (Vector3.Distance(list[j], list[k]) > num)
							{
								flag2 = true;
								break;
							}
						}
					}
				}
				assessment.FlankOpportunity = flag || flag2;
				bool targetIsolated = false;
				if (flag && num4 >= 0)
				{
					targetIsolated = true;
					Vector3 val = list[num4];
					for (int l = 0; l < list.Count; l++)
					{
						if (l != num4 && Vector3.Distance(val, list[l]) <= num2)
						{
							targetIsolated = false;
							break;
						}
					}
				}
				assessment.TargetIsolated = targetIsolated;
			}
			catch
			{
			}
		}

		internal static List<SquadUnit> MergeStragglers(IReadOnlyList<SquadUnit> discovered)
		{
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			List<SquadUnit> list = new List<SquadUnit>(discovered);
			if (list.Count <= 1)
			{
				return list;
			}
			float num = PluginConfig.SquadMergeRadius?.Value ?? 40f;
			if (num <= 0f)
			{
				return list;
			}
			List<SquadUnit> list2 = new List<SquadUnit>();
			List<SquadUnit> list3 = new List<SquadUnit>();
			foreach (SquadUnit item in list)
			{
				int num2 = EffectiveMinSize(item.Doctrine?.Id ?? "");
				if (CountAlive(item) >= num2)
				{
					list2.Add(item);
				}
				else
				{
					list3.Add(item);
				}
			}
			if (list2.Count == 0 || list3.Count == 0)
			{
				return list;
			}
			HashSet<SquadUnit> hashSet = new HashSet<SquadUnit>();
			foreach (SquadUnit item2 in list3)
			{
				string b = item2.Doctrine?.Id ?? "";
				Vector3 val = ComputeSquadCentroid(item2);
				SquadUnit squadUnit = null;
				float num3 = float.MaxValue;
				foreach (SquadUnit item3 in list2)
				{
					if (string.Equals(item3.Doctrine?.Id, b, StringComparison.OrdinalIgnoreCase))
					{
						float num4 = Vector3.Distance(val, ComputeSquadCentroid(item3));
						if (num4 <= num && num4 < num3)
						{
							num3 = num4;
							squadUnit = item3;
						}
					}
				}
				if (squadUnit == null)
				{
					continue;
				}
				foreach (SquadMemberView member in item2.Members)
				{
					squadUnit.Members.Add(member);
				}
				hashSet.Add(item2);
			}
			if (hashSet.Count == 0)
			{
				return list;
			}
			List<SquadUnit> list4 = new List<SquadUnit>(list.Count - hashSet.Count);
			foreach (SquadUnit item4 in list)
			{
				if (!hashSet.Contains(item4))
				{
					list4.Add(item4);
				}
			}
			return list4;
		}

		internal static Vector3 ComputeSquadCentroid(SquadUnit squad)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: 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)
			Vector3 val = Vector3.zero;
			int num = 0;
			foreach (SquadMemberView member in squad.Members)
			{
				if (member.IsAlive)
				{
					val += member.Position;
					num++;
				}
			}
			if (num == 0)
			{
				foreach (SquadMemberView member2 in squad.Members)
				{
					val += member2.Position;
					num++;
				}
			}
			if (num <= 0)
			{
				return Vector3.zero;
			}
			return val / (float)num;
		}

		public static int EffectiveMinSize(string? doctrineId)
		{
			int val = PluginConfig.MinSquadSize?.Value ?? 3;
			if (string.Equals(doctrineId, "death-rush", StringComparison.OrdinalIgnoreCase))
			{
				int val2 = PluginConfig.DeathRushMinSquadSize?.Value ?? 1;
				return Math.Max(1, Math.Min(val2, val));
			}
			return Math.Max(1, val);
		}

		private ThreatAssessment AssessThreats(SquadUnit squad, SquadRuntimeState? state, int minSize)
		{
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Unknown result type (might be due to invalid IL or missing references)
			ThreatAssessment threatAssessment = new ThreatAssessment
			{
				ThreatCount = 0,
				NearestDistance = float.MaxValue,
				CasualtyRatio = 0f,
				IsBroken = false,
				MissileThreatened = false,
				FlankOpportunity = squad.Members.Exists((SquadMemberView m) => m.AssignedRole == SquadRole.Flanker),
				TargetIsolated = false,
				ThreatStaggeredOrLow = false,
				NearbyTrollCount = 0,
				NearestTrollDistance = float.MaxValue,
				IndoorsOrCrypt = false,
				NearStructure = false,
				NearbyDvergrCount = 0,
				NearDvergr = false,
				NearWorkbench = false,
				NearestWorkbenchDistance = float.MaxValue,
				PlayersNearAssault = false,
				AssaultActive = false
			};
			int num = CountAlive(squad);
			(float ratio, bool isBroken) tuple = ComputeCasualties(num, state, minSize);
			float item = tuple.ratio;
			bool item2 = tuple.isBroken;
			threatAssessment.CasualtyRatio = item;
			threatAssessment.IsBroken = item2;
			int num2 = 0;
			float num3 = float.MaxValue;
			float num4 = float.MaxValue;
			Vector3 val = Vector3.zero;
			int num5 = 0;
			foreach (SquadMemberView member in squad.Members)
			{
				if (!member.IsAlive)
				{
					continue;
				}
				num5++;
				val += member.Position;
				object? nativeHandle = member.NativeHandle;
				MonsterAI val2 = (MonsterAI)((nativeHandle is MonsterAI) ? nativeHandle : null);
				if (val2 == null)
				{
					continue;
				}
				Character targetSafe = GetTargetSafe(val2);
				if ((Object)(object)targetSafe != (Object)null)
				{
					num2++;
					float num6 = Vector3.Distance(member.Position, ((Component)targetSafe).transform.position);
					if (num6 < num3)
					{
						num3 = num6;
					}
					if ((member.AssignedRole == SquadRole.Front || member.AssignedRole == SquadRole.Leader) && num6 < num4)
					{
						num4 = num6;
					}
					TryThreatConditionFlags(targetSafe, threatAssessment);
				}
			}
			if (num5 > 0)
			{
				((Vector3)(ref val))..ctor(val.x / (float)num5, val.y / (float)num5, val.z / (float)num5);
			}
			threatAssessment.ThreatCount = ((num2 > 0) ? Math.Max(1, num2 / Math.Max(1, num5)) : 0);
			if (num2 > 0)
			{
				threatAssessment.ThreatCount = 1;
			}
			threatAssessment.NearestDistance = num3;
			try
			{
				List<Vector3> list = ValheimWorldScan.CollectPlayerPositions();
				if (list.Count > 0 && num5 > 0)
				{
					foreach (Vector3 item3 in list)
					{
						float num7 = Vector3.Distance(val, item3);
						if (num7 < num3)
						{
							num3 = num7;
						}
					}
					foreach (SquadMemberView member2 in squad.Members)
					{
						if (!member2.IsAlive || (member2.AssignedRole != SquadRole.Front && member2.AssignedRole != SquadRole.Leader))
						{
							continue;
						}
						foreach (Vector3 item4 in list)
						{
							float num8 = Vector3.Distance(member2.Position, item4);
							if (num8 <= 48f && num8 < num4)
							{
								num4 = num8;
							}
						}
					}
					if (num3 < float.MaxValue && num3 <= 48f)
					{
						if (threatAssessment.ThreatCount <= 0)
						{
							threatAssessment.ThreatCount = 1;
						}
						threatAssessment.NearestDistance = num3;
					}
				}
			}
			catch
			{
			}
			threatAssessment.MissileThreatened = threatAssessment.ThreatCount > 0 && num3 < 12f && squad.Members.Exists((SquadMemberView m) => m.AssignedRole == SquadRole.Missile);
			ApplyFlankAndIsolate(val, num3, threatAssessment);
			if (num4 < 1.7014117E+38f)
			{
				threatAssessment.FrontlineThreatDistance = num4;
			}
			EnrichTrollProximity(val, threatAssessment);
			EnrichEnvironmentHeuristics(squad, val, threatAssessment);
			_siege.EnrichWorkbenchProximity(val, threatAssessment);
			if (squad.DebugThreatCount.HasValue)
			{
				threatAssessment.ThreatCount = squad.DebugThreatCount.Value;
			}
			if (squad.DebugNearestThreatDistance.HasValue)
			{
				threatAssessment.NearestDistance = squad.DebugNearestThreatDistance.Value;
				threatAssessment.FrontlineThreatDistance = squad.DebugNearestThreatDistance.Value;
			}
			int num9 = num;
			threatAssessment.AssaultActive = threatAssessment.NearWorkbench && SiegeDirector.IsMasterEnabled && SiegeDirector.IsSiegeEligibleDoctrine(squad.Doctrine?.Id) && num9 >= SiegeDirector.MinAssaultSquadSize;
			return threatAssessment;
		}

		public static (float ratio, bool isBroken) ComputeCasualties(int alive, SquadRuntimeState? state, int minSize)
		{
			int num = ((state != null) ? Math.Max(state.PeakAlive, alive) : alive);
			if (num <= 0)
			{
				return (ratio: 0f, isBroken: false);
			}
			float num2 = 1f - (float)alive / (float)num;
			if (num2 < 0f)
			{
				num2 = 0f;
			}
			if (num2 > 1f)
			{
				num2 = 1f;
			}
			bool item = (state != null && state.EverMetMinSize && alive < minSize) || num2 >= 0.5f;
			return (ratio: num2, isBroken: item);
		}

		internal static int CountAlive(SquadUnit squad)
		{
			int num = 0;
			foreach (SquadMemberView member in squad.Members)
			{
				object? nativeHandle = member.NativeHandle;
				MonsterAI val = (MonsterAI)((nativeHandle is MonsterAI) ? nativeHandle : null);
				if (val != null)
				{
					try
					{
						Character character = ValheimIds.GetCharacter((BaseAI?)(object)val);
						if ((Object)(object)character != (Object)null)
						{
							member.IsAlive = !character.IsDead();
						}
					}
					catch
					{
					}
				}
				if (member.IsAlive && (!(member.HealthRatio > 0f) || !(member.HealthRatio <= 0.02f)))
				{
					num++;
				}
			}
			return num;
		}

		private static void EnrichEnvironmentHeuristics(SquadUnit squad, Vector3 centroid, ThreatAssessment assessment)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			assessment.IndoorsOrCrypt = DetectIndoorsOrCrypt(centroid);
			float range = PluginConfig.StructureDefenseRange?.Value ?? 24f;
			assessment.NearStructure = DetectNearStructure(centroid, range);
			float range2 = PluginConfig.DvergrSoftenRange?.Value ?? 30f;
			EnrichDvergrProximity(centroid, range2, assessment);
		}

		private static bool DetectIndoorsOrCrypt(Vector3 centroid)
		{
			//IL_001a: 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)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				float num = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.GetGroundHeight(centroid) : centroid.y);
				if (centroid.y < num - 2.5f)
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static bool DetectNearStructure(Vector3 centroid, float range)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Piece[] array = Object.FindObjectsOfType<Piece>();
				foreach (Piece val in array)
				{
					if (!((Object)(object)val == (Object)null) && !(Vector3.Distance(centroid, ((Component)val).transform.position) > range))
					{
						string name = ((Object)val).name ?? "";
						if (Contains(name, "totem") || Contains(name, "fuling") || Contains(name, "goblin") || Contains(name, "village") || Contains(name, "fence") || Contains(name, "wall") || Contains(name, "banner") || Contains(name, "guard"))
						{
							return true;
						}
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private static void EnrichDvergrProximity(Vector3 centroid, float range, ThreatAssessment assessment)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				List<MonsterAI> list = ValheimWorldScan.EnumerateMonsterAIs();
				int num = 0;
				foreach (MonsterAI item in list)
				{
					Character character = ValheimIds.GetCharacter((BaseAI?)(object)item);
					if (!((Object)(object)character == (Object)null) && !character.IsDead())
					{
						string name = ((Object)character).name.Replace("(Clone)", "").Trim();
						if ((Starts(name, "Dvergr") || Starts(name, "Dverger")) && !(Vector3.Distance(centroid, ((Component)character).transform.position) > range))
						{
							num++;
						}
					}
				}
				assessment.NearbyDvergrCount = num;
				assessment.NearDvergr = num > 0;
			}
			catch
			{
				assessment.NearbyDvergrCount = 0;
				assessment.NearDvergr = false;
			}
		}

		private static void EnrichTrollProximity(Vector3 centroid, ThreatAssessment assessment)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if (!TrollFortressHelper.IsEnabled)
			{
				return;
			}
			try
			{
				float synergyRange = TrollFortressHelper.SynergyRange;
				List<MonsterAI> list = ValheimWorldScan.EnumerateMonsterAIs();
				int num = 0;
				float num2 = float.MaxValue;
				foreach (MonsterAI item in list)
				{
					Character character = ValheimIds.GetCharacter((BaseAI?)(object)item);
					if ((Object)(object)character == (Object)null || character.IsDead() || !TrollFortressHelper.IsTrollPrefab(((Object)character).name.Replace("(Clone)", "").Trim()))
					{
						continue;
					}
					float num3 = Vector3.Distance(centroid, ((Component)character).transform.position);
					if (!(num3 > synergyRange))
					{
						num++;
						if (num3 < num2)
						{
							num2 = num3;
						}
					}
				}
				assessment.NearbyTrollCount = num;
				assessment.NearestTrollDistance = num2;
			}
			catch
			{
				assessment.NearbyTrollCount = 0;
				assessment.NearestTrollDistance = float.MaxValue;
			}
		}

		private static void TryThreatConditionFlags(Character target, ThreatAssessment assessment)
		{
			try
			{
				MethodInfo method = typeof(Character).GetMethod("GetHealth");
				MethodInfo method2 = typeof(Character).GetMethod("GetMaxHealth");
				if (method != null && method2 != null)
				{
					float num = (float)method.Invoke(target, null);
					float num2 = (float)method2.Invoke(target, null);
					if (num2 > 0f && num / num2 <= 0.35f)
					{
						assessment.ThreatStaggeredOrLow = true;
					}
				}
				if ((object)typeof(Character).GetField("m_staggerDamageFactor") == null)
				{
					typeof(Character).GetField("m_staggerTimer");
				}
			}
			catch
			{
			}
		}

		private static Character? GetTargetSafe(MonsterAI ai)
		{
			try
			{
				MethodInfo methodInfo = typeof(MonsterAI).GetMethod("GetTargetCreature") ?? typeof(MonsterAI).GetMethod("GetAttackTarget");
				if (methodInfo != null)
				{
					object? obj = methodInfo.Invoke(ai, null);
					return (Character?)((obj is Character) ? obj : null);
				}
			}
			catch
			{
			}
			return null;
		}

		private static bool Starts(string name, string prefix)
		{
			return name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
		}

		private static bool Contains(string name, string token)
		{
			return name.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0;
		}
	}
	public sealed class SquadDiscovery : ISquadDiscovery
	{
		private sealed class Candidate
		{
			public IDoctrinePack Doctrine { get; set; }

			public SquadMemberView View { get; set; }
		}

		private readonly DoctrinePackRegistry _registry;

		private int _nextSquadSerial;

		private static bool _loggedZeroPlayersWithPeers;

		private bool _loggedEmptyEnumerateWarning;

		public int LastPlayerCount { get; private set; }

		public int LastCharacterCount { get