Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FactionTactics Server v1.0.13
FactionTactics.dll
Decompiled 3 hours ago
The result has been truncated due to the large size, download it to view full contents!
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