using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; 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 HarmonyLib; using Microsoft.CodeAnalysis; using SkeletonCrew.Ai; using SkeletonCrew.Diagnostics; using SkeletonCrew.Jobs; using SkeletonCrew.Patches; using SkeletonCrew.Ui; using Splatform; using UnityEngine; using UnityEngine.AI; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("Vash")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Makes vanilla Blood Magic summons regenerate, survive portals, fight smarter and take orders.")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2+119317f8ff839fddaaedb7f65472ad5cea0434bb")] [assembly: AssemblyProduct("SkeletonCrew")] [assembly: AssemblyTitle("SkeletonCrew")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 SkeletonCrew { internal static class Commands { internal struct LookResult { internal Character Target; internal Character Friendly; internal Vector3 Point; internal bool HasPoint; internal HarvestTarget Harvest; internal string HarvestRefusal; internal string HitName; } internal enum OrderAction { None, MoveHere, Attack, Harvest, HarvestArea, Guard, Recall } private static int _tapCount; private static float _lastTapTime; private static LookResult _tapLook; private const float SnapSearchRadius = 6f; private static string _pendingMessage; internal static void HandleInput() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (IsTextInputActive() || (Object)(object)Player.m_localPlayer == (Object)null) { return; } KeyboardShortcut value = SkeletonCrewPlugin.MenuKey.Value; if (Input.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey)) { CrewMenu.Toggle(); } else if (!CrewMenu.HandleInput()) { value = SkeletonCrewPlugin.CycleSummonTypeKey.Value; if (Input.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey)) { CycleSummonType(); } value = SkeletonCrewPlugin.StanceKey.Value; if (Input.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey)) { Stance.Cycle(); } value = SkeletonCrewPlugin.SelectionKey.Value; if (Input.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey)) { CrewSelection.Cycle(); } HandleMultiTap(); FlushPendingMessage(); } } private static void HandleMultiTap() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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) KeyboardShortcut value = SkeletonCrewPlugin.CommandButton.Value; if (Input.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey)) { float value2 = SkeletonCrewPlugin.MultiTapSeconds.Value; if (_tapCount == 0 || Time.time - _lastTapTime > value2) { _tapCount = 1; _tapLook = Look(); } else { _tapCount++; } _lastTapTime = Time.time; switch (_tapCount) { case 1: Execute((!((Object)(object)_tapLook.Target != (Object)null)) ? OrderAction.MoveHere : OrderAction.Attack, _tapLook); break; case 2: Follow(); break; default: HoldAndGuard(); break; } } } internal static void Execute(OrderAction action, LookResult look) { switch (action) { case OrderAction.Attack: Attack(look); break; case OrderAction.MoveHere: MoveOrGuard(look, guard: false); break; case OrderAction.Guard: MoveOrGuard(look, guard: true); break; case OrderAction.Harvest: Harvest(look, area: false); break; case OrderAction.HarvestArea: Harvest(look, area: true); break; case OrderAction.Recall: RecallAll(); break; } } private static void Follow() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_005e: 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) Player localPlayer = Player.m_localPlayer; int num = SummonRegistry.AdoptOrphans(localPlayer, SkeletonCrewPlugin.CommandRadius.Value); foreach (SummonRegistry.Summon item in InRange()) { SummonRegistry.ClearStation(item.Id); SummonRegistry.ClearMove(item.Id); SummonRegistry.ClearJob(item.Id); SummonRegistry.Obey(item.Id, item.AI); SummonRegistry.GrantRecallGrace(item.Id); SummonRegistry.SetRegrouping(item.Id, value: true); if (SetFollowState(item, follow: true, localPlayer)) { num++; } } Report("follow me", num); } private static void HoldAndGuard() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; int num = 0; List list = new List(); foreach (SummonRegistry.Summon item in InRange()) { SummonRegistry.ClearMove(item.Id); SummonRegistry.Obey(item.Id, item.AI); SummonRegistry.SetRegrouping(item.Id, value: false); if (SetFollowState(item, follow: false, localPlayer)) { SummonRegistry.SetStation(item.Id, ((Component)item.Character).transform.position); list.Add(item.Id); num++; } } AssignGuardSlots(list, ((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).transform.position : Vector3.zero); Report("hold position", num); } private static void Attack(LookResult look) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)look.Target == (Object)null) { SkeletonCrewPlugin.Log.LogInfo((object)"Order 'attack': nothing targetable under the crosshair."); return; } int num = 0; foreach (SummonRegistry.Summon item in InRange()) { if (!((Object)(object)item.AI == (Object)null)) { SummonRegistry.ClearStation(item.Id); SummonRegistry.ClearMove(item.Id); SummonRegistry.SetRegrouping(item.Id, value: false); ((BaseAI)item.AI).ResetPatrolPoint(); SummonRegistry.OrderAttack(item.Id, look.Target); SummonRegistry.Retarget(item.AI, look.Target); ((BaseAI)item.AI).SetAlerted(true); num++; } } Report("attack " + look.Target.GetHoverName(), num); } private static void MoveOrGuard(LookResult look, bool guard) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: 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_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) if (!look.HasPoint) { SkeletonCrewPlugin.Log.LogInfo((object)"Order 'move': no ground point under the crosshair."); } else { if (RefusedAsLava(look.Point, "move")) { return; } _ = Player.m_localPlayer; int num = 0; List list = new List(); List list2 = new List(InRange()); Vector3 point = look.Point; if (RefusedAsUnreachable(list2, ref point, guard ? "guard" : "move")) { return; } look.Point = point; bool spread = list2.Count > 1; foreach (SummonRegistry.Summon item in list2) { if (!((Object)(object)item.AI == (Object)null)) { SummonRegistry.Obey(item.Id, item.AI); SummonRegistry.SetRegrouping(item.Id, value: false); SummonRegistry.ClearJob(item.Id); item.AI.SetFollowTarget((GameObject)null); SummonRegistry.OrderMove(item.Id, look.Point, spread); ((BaseAI)item.AI).ResetPatrolPoint(); ((BaseAI)item.AI).SetPatrolPoint(look.Point); if (guard) { SummonRegistry.SetStation(item.Id, look.Point); list.Add(item.Id); } else { SummonRegistry.ClearStation(item.Id); } num++; } } AssignGuardSlots(list, look.Point); Report(guard ? "guard here" : "move here", num); } } private static void AssignGuardSlots(List guards, Vector3 anchor) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < guards.Count; i++) { SummonRegistry.SetGuardSlot(guards[i], i, guards.Count, anchor); } } internal static void FollowFromMenu() { Follow(); } internal static void HoldFromMenu() { HoldAndGuard(); } internal static void DismissAllFromMenu() { int num = 0; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead() && !((Object)(object)item.Tameable == (Object)null) && TameableCullWeakestPatch.Dismiss(item.Tameable)) { num++; } } num += DismissAway(); SkeletonCrewPlugin.Log.LogInfo((object)$"Dismissed {num} summon(s) from the menu."); } private static int DismissAway() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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) if (ZDOMan.instance == null) { return 0; } List list = new List(); CrewHud.CollectAway(list); int num = 0; foreach (ZDOID item in list) { ZDO zDO = ZDOMan.instance.GetZDO(item); CrewHud.Forget(item); if (zDO != null && SummonRegistry.IsOurs(zDO)) { zDO.SetOwner(ZDOMan.GetSessionID()); ZDOMan.instance.DestroyZDO(zDO); num++; } } if (num > 0) { SkeletonCrewPlugin.Log.LogInfo((object)$"Dismissed {num} summon(s) that were out of range."); } return num; } private static void CycleSummonType() { SummonPreference[] array = (SummonPreference[])Enum.GetValues(typeof(SummonPreference)); int num = Array.IndexOf(array, SkeletonCrewPlugin.SummonWeaponPreference.Value); SummonPreference summonPreference = SkeletonCrewPlugin.SummonWeaponPreference.Value; for (int i = 1; i <= array.Length; i++) { SummonPreference summonPreference2 = array[(num + i + array.Length) % array.Length]; if ((summonPreference2 != SummonPreference.Adaptive || SkeletonCrewPlugin.EnableAdaptive.Value) && Progression.IsUnlocked(summonPreference2)) { summonPreference = summonPreference2; break; } } bool isArmed = SummonGroup.IsArmed; SummonGroup.Disarm(); SkeletonCrewPlugin.SummonWeaponPreference.Value = summonPreference; SkeletonCrewPlugin.Log.LogInfo((object)$"Next summon: {summonPreference}."); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, isArmed ? $"Next raise: {summonPreference} - group off" : $"Next raise: {summonPreference}", 0, (Sprite)null, false); } } private static bool RefusedAsLava(Vector3 point, string order) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (!LavaGuard.IsLava(point)) { return false; } SkeletonCrewPlugin.Log.LogInfo((object)("Order '" + order + "': refused. Destination is lava.")); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, "Won't go there - that's lava", 0, (Sprite)null, false); } return true; } private static bool AnyPathTo(List recipients, Vector3 target, AgentType agent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) foreach (SummonRegistry.Summon recipient in recipients) { if ((Object)(object)recipient.Character != (Object)null && Pathfinding.instance.HavePath(((Component)recipient.Character).transform.position, target, agent)) { return true; } } return false; } private static bool RefusedAsUnreachable(List recipients, ref Vector3 point, string order) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Pathfinding.instance == (Object)null || recipients.Count == 0) { return false; } Vector3 val = point; AgentType val2 = (AgentType)(((Object)(object)recipients[0].AI == (Object)null) ? 13 : ((int)((BaseAI)recipients[0].AI).m_pathAgentType)); AgentSettings settings = Pathfinding.instance.GetSettings(val2); if (settings != null) { Pathfinding.instance.SnapToNavMesh(ref val, true, settings); } Vector3 val3 = default(Vector3); if (!AnyPathTo(recipients, val, val2) && Pathfinding.instance.FindValidPoint(ref val3, point, 6f, val2) && AnyPathTo(recipients, val3, val2)) { SkeletonCrewPlugin.Log.LogInfo((object)("Order '" + order + "': the exact spot is off the navmesh; using walkable ground " + $"{Vector3.Distance(point, val3):0.#}m away.")); val = val3; } point = val; int num = 0; for (int num2 = recipients.Count - 1; num2 >= 0; num2--) { SummonRegistry.Summon summon = recipients[num2]; if (!((Object)(object)summon.Character == (Object)null) && !((Object)(object)summon.AI == (Object)null) && !Pathfinding.instance.HavePath(((Component)summon.Character).transform.position, val, ((BaseAI)summon.AI).m_pathAgentType)) { recipients.RemoveAt(num2); num++; } } if (num == 0) { return false; } if (recipients.Count > 0) { SkeletonCrewPlugin.Log.LogInfo((object)$"Order '{order}': {num} summon(s) have no path there and were left out."); return false; } SkeletonCrewPlugin.Log.LogInfo((object)$"Order '{order}': refused. No summon has a path to {point}."); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, "They can't get there from here", 0, (Sprite)null, false); } return true; } private static void Harvest(LookResult look, bool area) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) if (look.Harvest == null || look.Harvest.Kind == HarvestKind.None) { SkeletonCrewPlugin.Log.LogInfo((object)"Order 'harvest': nothing harvestable under the crosshair."); } else { if (look.HasPoint && RefusedAsLava(look.Point, "harvest")) { return; } int num = ToolTier.BestFor(look.Harvest.Kind); if (num < look.Harvest.MinToolTier) { string text = $"Need tool tier {look.Harvest.MinToolTier} - your best is {num}"; SkeletonCrewPlugin.Log.LogInfo((object)("Order 'harvest " + look.Harvest.Name + "': refused. " + text + ".")); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false); } return; } Vector3 position = look.Harvest.Position; List list = (area ? HarvestScan.Gather(position, SkeletonCrewPlugin.HarvestAreaRadius.Value, look.Harvest.Kind) : null); if (list != null && look.Harvest.Kind == HarvestKind.Pick) { string wanted = look.Harvest.PrefabName; list.RemoveAll((HarvestTarget t) => t.PrefabName != wanted); } Dictionary assigned = new Dictionary(); int num2 = 0; List list2 = new List(InRange()); Vector3 point = look.Harvest.WorkPosition((list2.Count > 0 && (Object)(object)list2[0].Character != (Object)null) ? ((Component)list2[0].Character).transform.position : ((Component)Player.m_localPlayer).transform.position); if (RefusedAsUnreachable(list2, ref point, "harvest")) { return; } foreach (SummonRegistry.Summon item in list2) { SummonRegistry.ClearStation(item.Id); SummonRegistry.ClearMove(item.Id); SummonRegistry.SetRegrouping(item.Id, value: false); HarvestTarget target = NextForSpread(list, item, assigned, look.Harvest) ?? look.Harvest.Copy(); SummonRegistry.OrderHarvest(item.Id, target, area, position); num2++; } Report(area ? ("harvest area around " + look.Harvest.Name) : ("harvest " + look.Harvest.Name), num2); } } private static HarvestTarget NextForSpread(List spread, SummonRegistry.Summon s, Dictionary assigned, HarvestTarget primary) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if (spread == null || (Object)(object)s.Character == (Object)null) { return null; } if ((Object)(object)primary?.Root != (Object)null) { assigned.TryGetValue(primary.Root, out var value); if (value < primary.WorkerCapacity) { assigned[primary.Root] = value + 1; return primary.Copy(); } } Vector3 position = ((Component)s.Character).transform.position; HarvestTarget harvestTarget = null; float num = float.MaxValue; foreach (HarvestTarget item in spread) { assigned.TryGetValue(item.Root, out var value2); if (value2 < item.WorkerCapacity) { Vector3 val = item.Position - position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; harvestTarget = item; } } } if (harvestTarget == null) { return null; } assigned.TryGetValue(harvestTarget.Root, out var value3); assigned[harvestTarget.Root] = value3 + 1; return harvestTarget.Copy(); } private static void RecallAll() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; int num = 0; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead()) { SummonRegistry.ClearStation(item.Id); Recall.Bring(item, localPlayer, "ordered", Vector3.Distance(((Component)item.Character).transform.position, ((Component)localPlayer).transform.position)); num++; } } Report("recall to me", num); } private static bool SetFollowState(SummonRegistry.Summon s, bool follow, Player player) { if ((Object)(object)s.AI == (Object)null) { return false; } if ((Object)(object)s.AI.GetFollowTarget() != (Object)null == follow) { return true; } if (follow) { ((BaseAI)s.AI).ResetPatrolPoint(); s.AI.SetFollowTarget(((Component)player).gameObject); SummonRegistry.MarkOwned(s.Character, player); return true; } s.AI.SetFollowTarget((GameObject)null); ((BaseAI)s.AI).SetPatrolPoint(); return true; } internal static LookResult Look() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) LookResult result = default(LookResult); Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return result; } float value = SkeletonCrewPlugin.TargetRange.Value; float value2 = SkeletonCrewPlugin.TargetAssistRadius.Value; RaycastHit[] obj = ((value2 > 0f) ? Physics.SphereCastAll(((Component)main).transform.position, value2, ((Component)main).transform.forward, value) : Physics.RaycastAll(((Component)main).transform.position, ((Component)main).transform.forward, value)); Array.Sort(obj, (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance)); RaycastHit[] array = obj; for (int num = 0; num < array.Length; num++) { RaycastHit val = array[num]; bool flag = ((RaycastHit)(ref val)).distance <= 0f; Character componentInParent = ((Component)((RaycastHit)(ref val)).collider).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { if (result.Harvest == null) { result.Harvest = HarvestTarget.Resolve(((RaycastHit)(ref val)).collider); if (result.Harvest == null && result.HarvestRefusal == null) { result.HarvestRefusal = HarvestTarget.LastRefusal; } } if (!result.HasPoint && !flag) { result.Point = ((RaycastHit)(ref val)).point; result.HasPoint = true; result.HitName = (((Object)(object)((RaycastHit)(ref val)).collider == (Object)null) ? null : ((Object)((Component)((RaycastHit)(ref val)).collider).transform.root).name); } } else { if (componentInParent.IsDead() || (Object)(object)componentInParent == (Object)(object)Player.m_localPlayer) { continue; } if (componentInParent.IsPlayer() || componentInParent.IsTamed()) { if ((Object)(object)result.Friendly == (Object)null) { result.Friendly = componentInParent; } } else if ((Object)(object)result.Target == (Object)null) { result.Target = componentInParent; } } } return result; } private static bool IsTextInputActive() { if ((!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !Menu.IsVisible() && !InventoryGui.IsVisible() && !StoreGui.IsVisible()) { if ((Object)(object)TextInput.instance != (Object)null && (Object)(object)TextInput.instance.m_panel != (Object)null) { return TextInput.instance.m_panel.activeSelf; } return false; } return true; } private static IEnumerable InRange() { Player player = Player.m_localPlayer; float value = SkeletonCrewPlugin.CommandRadius.Value; bool limited = value > 0f; float radiusSq = value * value; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if ((Object)(object)item.Character == (Object)null || item.Character.IsDead() || !CrewSelection.Applies(item.Id)) { continue; } if (limited) { Vector3 val = ((Component)item.Character).transform.position - ((Component)player).transform.position; if (((Vector3)(ref val)).sqrMagnitude > radiusSq) { continue; } } yield return item; } } private static void Report(string order, int count) { SkeletonCrewPlugin.Log.LogInfo((object)$"Order '{order}': {count} summon(s)."); if (count > 0 && SkeletonCrewPlugin.ShowOrderMessages.Value) { _pendingMessage = $"Crew: {order} ({count})"; } } private static void FlushPendingMessage() { if (_pendingMessage != null && _tapCount != 0 && !(Time.time - _lastTapTime <= SkeletonCrewPlugin.MultiTapSeconds.Value)) { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, _pendingMessage, 0, (Sprite)null, false); } _pendingMessage = null; _tapCount = 0; } } } internal static class CrewHud { private sealed class Row { internal GameObject Root; internal Image Portrait; internal Text Label; internal Image BarFill; } private struct Entry { internal ZDOID Id; internal string Name; internal float Fraction; internal string Activity; internal ItemData Weapon; internal string Tag; internal bool Away; } private sealed class NameOrder : IComparer { public int Compare(Entry a, Entry b) { return string.CompareOrdinal(a.Name, b.Name); } } private static GameObject _panel; private static RectTransform _rect; private static Text _header; private static Transform _attachedTo; private static int _headerCrew = -1; private static CrewStance _headerStance; private static ZDOID _headerSelected; private static bool _headerAll; private static readonly List Rows = new List(); private const int RowHeight = 34; private const int PanelWidth = 210; private static readonly Dictionary Seen = new Dictionary(); private static readonly List Forgotten = new List(); private static readonly List Entries = new List(); private static readonly List Away = new List(); private static float _nextAwayScan; private const float AwayScanInterval = 0.5f; private static readonly NameOrder ByName = new NameOrder(); private static readonly string[] ActivityNames = BuildActivityNames(); internal static void Reset() { if ((Object)(object)_panel != (Object)null) { Object.Destroy((Object)(object)_panel); } _panel = null; _header = null; _attachedTo = null; Rows.Clear(); Seen.Clear(); } internal static void Tick() { if (!SkeletonCrewPlugin.ShowCrewHud.Value || (SummonRegistry.Owned.Count == 0 && Seen.Count == 0)) { if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); } return; } Transform val = CurrentParent(); if (!((Object)(object)val == (Object)null)) { if ((Object)(object)_panel == (Object)null) { Build(val); } if ((Object)(object)_attachedTo != (Object)(object)val) { _panel.transform.SetParent(val, false); _attachedTo = val; } _panel.SetActive(true); Refresh(); } } private static Transform CurrentParent() { return UiRoot.Current(); } private static void Build(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) Rows.Clear(); _panel = new GameObject("SkeletonCrewHud", new Type[2] { typeof(RectTransform), typeof(CanvasGroup) }); _panel.transform.SetParent(parent, false); _attachedTo = parent; _rect = _panel.GetComponent(); Anchor(_rect); CanvasGroup component = _panel.GetComponent(); component.interactable = false; component.blocksRaycasts = false; _header = MakeText(_panel.transform, 13, (FontStyle)1); RectTransform component2 = ((Component)_header).GetComponent(); component2.anchorMin = new Vector2(0f, 1f); component2.anchorMax = new Vector2(1f, 1f); component2.pivot = new Vector2(0f, 1f); component2.anchoredPosition = Vector2.zero; component2.sizeDelta = new Vector2(0f, 20f); } private static void Anchor(RectTransform rect) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0f, 1f); rect.anchorMax = new Vector2(0f, 1f); rect.pivot = new Vector2(0f, 1f); rect.anchoredPosition = new Vector2(SkeletonCrewPlugin.CrewHudX.Value, 0f - SkeletonCrewPlugin.CrewHudY.Value); rect.sizeDelta = new Vector2(210f, 200f); ((Transform)rect).localScale = Vector3.one * SkeletonCrewPlugin.CrewHudScale.Value; } internal static void CollectAway(List into) { //IL_002b: 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_0061: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair item in Seen) { bool flag = false; foreach (SummonRegistry.Summon item2 in SummonRegistry.Owned) { if (item2.Id == item.Key) { flag = true; break; } } if (!flag) { into.Add(item.Key); } } } internal static void Forget(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Seen.Remove(id); } private static void Refresh() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Anchor(_rect); List list = BuildEntries(); if (list.Count != _headerCrew || Stance.Current != _headerStance || CrewSelection.Selected != _headerSelected || CrewSelection.IsAll != _headerAll) { _headerCrew = list.Count; _headerStance = Stance.Current; _headerSelected = CrewSelection.Selected; _headerAll = CrewSelection.IsAll; _header.text = $"CREW [{list.Count}] — {Stance.Current.ToString().ToUpperInvariant()}" + " ▸ " + CrewSelection.Describe().ToUpperInvariant() + ""; } while (Rows.Count < list.Count) { Rows.Add(MakeRow(Rows.Count)); } for (int i = 0; i < Rows.Count; i++) { if (i >= list.Count) { Rows[i].Root.SetActive(false); continue; } Rows[i].Root.SetActive(true); Fill(Rows[i], list[i]); } } private static List BuildEntries() { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: 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_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_033a: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) bool flag = Time.time >= _nextAwayScan; if (flag) { _nextAwayScan = Time.time + 0.5f; } Away.Clear(); foreach (Entry entry in Entries) { if (entry.Away) { Away.Add(entry); } } Entries.Clear(); foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { Character character = item.Character; if (!((Object)(object)character == (Object)null)) { string hoverName = character.GetHoverName(); Seen[item.Id] = hoverName; Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); Entries.Add(new Entry { Id = item.Id, Name = hoverName, Fraction = Mathf.Clamp01(character.GetHealth() / Mathf.Max(1f, character.GetMaxHealth())), Activity = Activity(item), Weapon = ((val != null) ? val.GetCurrentWeapon() : null), Tag = RoleTag(val), Away = false }); } } if (!flag) { foreach (Entry item2 in Away) { if (!IsListed(item2.Id)) { Entries.Add(item2); } } return Entries; } ZDOMan instance = ZDOMan.instance; if (instance == null) { foreach (Entry item3 in Away) { if (!IsListed(item3.Id)) { Entries.Add(item3); } } return Entries; } Forgotten.Clear(); int count = Entries.Count; foreach (KeyValuePair item4 in Seen) { if (!IsListed(item4.Key)) { ZDO zDO = instance.GetZDO(item4.Key); if (zDO == null) { Forgotten.Add(item4.Key); continue; } float num = Mathf.Max(1f, zDO.GetFloat(ZDOVars.s_maxHealth, 1f)); Entries.Add(new Entry { Id = item4.Key, Name = item4.Value, Fraction = Mathf.Clamp01(zDO.GetFloat(ZDOVars.s_health, num) / num), Activity = "away", Weapon = null, Away = true }); } } foreach (ZDOID item5 in Forgotten) { Seen.Remove(item5); } Entries.Sort(count, Entries.Count - count, ByName); return Entries; } private static bool IsListed(ZDOID id) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) foreach (Entry entry in Entries) { if (entry.Id == id) { return true; } } return false; } private static void Fill(Row row, Entry e) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) ((Graphic)row.BarFill).rectTransform.anchorMax = new Vector2(e.Fraction, 1f); ((Graphic)row.BarFill).color = HealthColour(e.Fraction); Sprite val = WeaponIcon(e.Weapon); row.Portrait.sprite = val; ((Behaviour)row.Portrait).enabled = (Object)(object)val != (Object)null; bool flag = !e.Away && CrewSelection.Applies(e.Id); bool flag2 = flag && !CrewSelection.IsAll; ((Graphic)row.Label).color = (Color)(flag ? Color.white : new Color(0.45f, 0.45f, 0.45f)); ((Graphic)row.Portrait).color = (Color)(flag ? Color.white : new Color(1f, 1f, 1f, 0.35f)); row.Label.text = (flag2 ? "▸" : " ") + (e.Away ? string.Empty : ("" + e.Tag + " ")) + e.Name + " " + e.Activity + ""; } private static Sprite WeaponIcon(ItemData weapon) { Sprite[] array = weapon?.m_shared?.m_icons; if (array == null || array.Length == 0) { return null; } return array[Mathf.Clamp(weapon.m_variant, 0, array.Length - 1)]; } private static string RoleTag(Humanoid h) { SummonPreference? summonPreference = SummonClass.Of(h); string text = ((!summonPreference.HasValue) ? null : TagFor(summonPreference.Value)); if (text != null) { return text; } if (!WeaponChoice.IsRanged((h == null) ? null : h.GetCurrentWeapon()?.m_shared)) { return "[M]"; } return "[R]"; } private static string TagFor(SummonPreference pref) { return pref switch { SummonPreference.Defender => "[D]", SummonPreference.Healer => "[H]", SummonPreference.FireMage => "[F]", SummonPreference.IceMage => "[I]", _ => null, }; } private static string[] BuildActivityNames() { Activity[] array = (Activity[])Enum.GetValues(typeof(Activity)); string[] array2 = new string[array.Length]; for (int i = 0; i < array.Length; i++) { array2[(int)array[i]] = array[i].ToString(); } return array2; } private static string Activity(SummonRegistry.Summon s) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) int activity = (int)SummonRegistry.IntentFor(s.Id).Activity; if (activity < 0 || activity >= ActivityNames.Length) { return "?"; } return ActivityNames[activity]; } private static Color HealthColour(float fraction) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!(fraction > 0.6f)) { if (!(fraction > 0.3f)) { return new Color(0.85f, 0.3f, 0.25f); } return new Color(0.9f, 0.75f, 0.25f); } return new Color(0.45f, 0.8f, 0.4f); } private static Row MakeRow(int index) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_0050: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_010d: 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_0137: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Expected O, but got Unknown //IL_023e: 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_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Expected O, but got Unknown //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_0329: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject($"Row{index}", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_panel.transform, false); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(1f, 1f); component.pivot = new Vector2(0f, 1f); component.anchoredPosition = new Vector2(0f, -22f - (float)(index * 34)); component.sizeDelta = new Vector2(0f, 34f); GameObject val2 = new GameObject("Portrait", new Type[2] { typeof(RectTransform), typeof(Image) }); val2.transform.SetParent(val.transform, false); ((Behaviour)val2.GetComponent()).enabled = false; RectTransform component2 = val2.GetComponent(); component2.anchorMin = new Vector2(0f, 0.5f); component2.anchorMax = new Vector2(0f, 0.5f); component2.pivot = new Vector2(0f, 0.5f); component2.anchoredPosition = new Vector2(0f, 0f); component2.sizeDelta = new Vector2(28f, 28f); Text val3 = MakeText(val.transform, 12, (FontStyle)0); RectTransform component3 = ((Component)val3).GetComponent(); component3.anchorMin = new Vector2(0f, 1f); component3.anchorMax = new Vector2(1f, 1f); component3.pivot = new Vector2(0f, 1f); component3.anchoredPosition = new Vector2(32f, -1f); component3.sizeDelta = new Vector2(-32f, 16f); GameObject val4 = new GameObject("BarBack", new Type[2] { typeof(RectTransform), typeof(Image) }); val4.transform.SetParent(val.transform, false); ((Graphic)val4.GetComponent()).color = new Color(0f, 0f, 0f, 0.55f); RectTransform component4 = val4.GetComponent(); component4.anchorMin = new Vector2(0f, 1f); component4.anchorMax = new Vector2(1f, 1f); component4.pivot = new Vector2(0f, 1f); component4.anchoredPosition = new Vector2(32f, -18f); component4.sizeDelta = new Vector2(-36f, 8f); GameObject val5 = new GameObject("BarFill", new Type[2] { typeof(RectTransform), typeof(Image) }); val5.transform.SetParent(val4.transform, false); ((Graphic)val5.GetComponent()).color = HealthColour(1f); RectTransform component5 = val5.GetComponent(); component5.anchorMin = Vector2.zero; component5.anchorMax = Vector2.one; component5.offsetMin = Vector2.zero; component5.offsetMax = Vector2.zero; return new Row { Root = val, Portrait = val2.GetComponent(), Label = val3, BarFill = val5.GetComponent() }; } private static Text MakeText(Transform parent, int size, FontStyle style) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return UiRoot.MakeText(parent, size, style); } } internal static class MapPins { private static readonly Dictionary Pins = new Dictionary(); internal static void Tick() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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_0084: 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) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.ShowOnMap.Value) { if (Pins.Count > 0) { Clear(); } } else { if ((Object)(object)Minimap.instance == (Object)null) { return; } HashSet hashSet = new HashSet(); foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!SummonRegistry.Usable(item)) { continue; } hashSet.Add(item.Id); if (Pins.TryGetValue(item.Id, out var value)) { value.m_pos = ((Component)item.Character).transform.position; ApplyStyle(value); continue; } PinData val = Minimap.instance.AddPin(((Component)item.Character).transform.position, SkeletonCrewPlugin.MapPinType.Value, SkeletonCrewPlugin.ShowNamesOnMap.Value ? item.Character.GetHoverName() : string.Empty, false, false, 0L, default(PlatformUserID)); if (val != null) { ApplyStyle(val); Pins[item.Id] = val; } } if (Pins.Count == hashSet.Count) { return; } List list = new List(); foreach (KeyValuePair pin in Pins) { if (!hashSet.Contains(pin.Key)) { list.Add(pin.Key); } } foreach (ZDOID item2 in list) { Remove(item2); } } } private static void ApplyStyle(PinData pin) { pin.m_doubleSize = SkeletonCrewPlugin.MapPinDoubleSize.Value; pin.m_animate = SkeletonCrewPlugin.MapPinAnimate.Value; } private static void Remove(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (Pins.TryGetValue(id, out var value)) { if ((Object)(object)Minimap.instance != (Object)null) { Minimap.instance.RemovePin(value); } Pins.Remove(id); } } internal static void Clear() { foreach (KeyValuePair pin in Pins) { if ((Object)(object)Minimap.instance != (Object)null) { Minimap.instance.RemovePin(pin.Value); } } Pins.Clear(); } } [BepInPlugin("com.vash.skeletoncrew", "SkeletonCrew", "1.0.2")] [BepInProcess("valheim.exe")] public class SkeletonCrewPlugin : BaseUnityPlugin { public const string PluginGuid = "com.vash.skeletoncrew"; public const string PluginName = "SkeletonCrew"; public const string PluginVersion = "1.0.2"; private static readonly Dictionary> HealthScales = new Dictionary>(); private static readonly Dictionary> DamageScales = new Dictionary>(); private static readonly Dictionary> ResistScales = new Dictionary>(); private const float TickInterval = 0.5f; private Harmony _harmony; private float _nextTick; private float _lastTick; private int _errorCount; private float _lastErrorLog = -999f; internal static ManualLogSource Log { get; private set; } internal static SkeletonCrewPlugin Instance { get; private set; } internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry VerboseLogging { get; private set; } internal static ConfigEntry SummonPrefabPatterns { get; private set; } internal static ConfigEntry TraceOrders { get; private set; } internal static ConfigEntry DeepTrace { get; private set; } internal static ConfigEntry DeepTraceInterval { get; private set; } internal static ConfigEntry RegenPercentPerSecond { get; private set; } internal static ConfigEntry HarvestEnabled { get; private set; } internal static ConfigEntry HarvestTrees { get; private set; } internal static ConfigEntry HarvestRock { get; private set; } internal static ConfigEntry HarvestDestructibles { get; private set; } internal static ConfigEntry HarvestPickables { get; private set; } internal static ConfigEntry HarvestContinue { get; private set; } internal static ConfigEntry HarvestReach { get; private set; } internal static ConfigEntry HarvestSettleSeconds { get; private set; } internal static ConfigEntry HarvestLogSearchRadius { get; private set; } internal static ConfigEntry HarvestStructures { get; private set; } internal static ConfigEntry ProtectPlayerBase { get; private set; } internal static ConfigEntry HealthBloodMagicBonus { get; private set; } internal static ConfigEntry HarvestAreaRadius { get; private set; } internal static ConfigEntry HarvestAreasPerWorker { get; private set; } internal static ConfigEntry HarvestReachVertical { get; private set; } internal static ConfigEntry HarvestStallSeconds { get; private set; } internal static ConfigEntry HarvestSwingSeconds { get; private set; } internal static ConfigEntry HarvestDamage { get; private set; } internal static ConfigEntry SummonWeaponPreference { get; private set; } internal static ConfigEntry MenuKey { get; private set; } internal static ConfigEntry CycleSummonTypeKey { get; private set; } internal static ConfigEntry SelectionKey { get; private set; } internal static ConfigEntry EnableAdaptive { get; private set; } internal static ConfigEntry ClassMeleeItems { get; private set; } internal static ConfigEntry ClassArcherItems { get; private set; } internal static ConfigEntry ClassDefenderItems { get; private set; } internal static ConfigEntry ClassHealerItems { get; private set; } internal static ConfigEntry ClassFireMageItems { get; private set; } internal static ConfigEntry ClassIceMageItems { get; private set; } internal static ConfigEntry ClassMeleeAnimation { get; private set; } internal static ConfigEntry ClassArcherAnimation { get; private set; } internal static ConfigEntry ClassDefenderAnimation { get; private set; } internal static ConfigEntry ClassHealerAnimation { get; private set; } internal static ConfigEntry ClassFireMageAnimation { get; private set; } internal static ConfigEntry ClassIceMageAnimation { get; private set; } internal static ConfigEntry ClassHealerAttackFrom { get; private set; } internal static ConfigEntry ClassFireMageAttackFrom { get; private set; } internal static ConfigEntry ClassIceMageAttackFrom { get; private set; } internal static ConfigEntry ClassMeleeRange { get; private set; } internal static ConfigEntry ClassArcherRange { get; private set; } internal static ConfigEntry ClassDefenderRange { get; private set; } internal static ConfigEntry ClassHealerRange { get; private set; } internal static ConfigEntry ClassFireMageRange { get; private set; } internal static ConfigEntry ClassIceMageRange { get; private set; } internal static ConfigEntry ClassDefenderUnlock { get; private set; } internal static ConfigEntry ClassHealerUnlock { get; private set; } internal static ConfigEntry ClassFireMageUnlock { get; private set; } internal static ConfigEntry ClassIceMageUnlock { get; private set; } internal static ConfigEntry ProgressionBonusPerUnlock { get; private set; } internal static ConfigEntry ClassMeleeLimit { get; private set; } internal static ConfigEntry ClassArcherLimit { get; private set; } internal static ConfigEntry ClassDefenderLimit { get; private set; } internal static ConfigEntry ClassHealerLimit { get; private set; } internal static ConfigEntry ClassFireMageLimit { get; private set; } internal static ConfigEntry ClassIceMageLimit { get; private set; } internal static ConfigEntry SummonPresets { get; private set; } internal static ConfigEntry GroupCostPerExtra { get; private set; } internal static ConfigEntry HealerFormationScale { get; private set; } internal static ConfigEntry LavaEscapeSeconds { get; private set; } internal static ConfigEntry LavaRescueSeconds { get; private set; } internal static ConfigEntry RoleFormation { get; private set; } internal static ConfigEntry FrontLineRadiusScale { get; private set; } internal static ConfigEntry BackLineRadiusScale { get; private set; } internal static ConfigEntry BackLineEngageDistance { get; private set; } internal static ConfigEntry BackLineScreenDistance { get; private set; } internal static ConfigEntry BackLineFleeHealth { get; private set; } internal static ConfigEntry FrontLineViewMultiplier { get; private set; } internal static ConfigEntry FrontLineLeashScale { get; private set; } internal static ConfigEntry HealerHealsPlayers { get; private set; } internal static ConfigEntry HealerHealRadius { get; private set; } internal static ConfigEntry HealerWardInterval { get; private set; } internal static ConfigEntry HealerWardRadius { get; private set; } internal static ConfigEntry HealerWardSeconds { get; private set; } internal static ConfigEntry DevotionScaling { get; private set; } internal static ConfigEntry DevotionFloor { get; private set; } internal static ConfigEntry DevotionFullGear { get; private set; } internal static ConfigEntry DevotionFullArsenal { get; private set; } internal static ConfigEntry WardApproachRadius { get; private set; } internal static ConfigEntry HealerCriticalHealth { get; private set; } internal static ConfigEntry HealerEmergencyInterval { get; private set; } internal static ConfigEntry HealerCastHealPercent { get; private set; } internal static ConfigEntry PressureWindowSeconds { get; private set; } internal static ConfigEntry HealerRescueHealth { get; private set; } internal static ConfigEntry HealerHealThreshold { get; private set; } internal static ConfigEntry HealerCastsStaff { get; private set; } internal static ConfigEntry HealerCastInterval { get; private set; } internal static ConfigEntry HealerCastHealAmount { get; private set; } internal static ConfigEntry HealerCastHealRadius { get; private set; } internal static ConfigEntry MoveEndsInHold { get; private set; } internal static ConfigEntry PreciseHoldSlack { get; private set; } internal static ConfigEntry AvoidLava { get; private set; } internal static ConfigEntry LavaLookahead { get; private set; } internal static ConfigEntry LavaCrossingTolerance { get; private set; } internal static ConfigEntry LavaDodge { get; private set; } internal static ConfigEntry FocusFire { get; private set; } internal static ConfigEntry HazardMemorySeconds { get; private set; } internal static ConfigEntry HazardSettleSeconds { get; private set; } internal static ConfigEntry RouteDetourFactor { get; private set; } internal static ConfigEntry MarchingOrder { get; private set; } internal static ConfigEntry MarchSlack { get; private set; } internal static ConfigEntry PullTactics { get; private set; } internal static ConfigEntry PullReceiveDistance { get; private set; } internal static ConfigEntry PullTimeoutSeconds { get; private set; } internal static ConfigEntry AllowDismiss { get; private set; } internal static ConfigEntry AttackInWater { get; private set; } internal static ConfigEntry NoPlayerCollision { get; private set; } internal static ConfigEntry AdjustAttackGeometry { get; private set; } internal static ConfigEntry AttackHeight { get; private set; } internal static ConfigEntry AttackCharExtra { get; private set; } internal static ConfigEntry AttackRange { get; private set; } internal static ConfigEntry AttackRayWidth { get; private set; } internal static ConfigEntry AttackAngle { get; private set; } internal static ConfigEntry StanceSetting { get; private set; } internal static ConfigEntry StanceKey { get; private set; } internal static ConfigEntry OffensiveViewMultiplier { get; private set; } internal static ConfigEntry OffensiveReachMultiplier { get; private set; } internal static ConfigEntry DefensiveReachMultiplier { get; private set; } internal static ConfigEntry ChaseLeash { get; private set; } internal static ConfigEntry DefensiveLeash { get; private set; } internal static ConfigEntry GuardTheOwner { get; private set; } internal static ConfigEntry GuardPerimeterRadius { get; private set; } internal static ConfigEntry SmartWeaponChoice { get; private set; } internal static ConfigEntry ReplaceWeakestSummon { get; private set; } internal static ConfigEntry SummonCapBonus { get; private set; } internal static ConfigEntry SummonCapBonusAtLevel { get; private set; } internal static ConfigEntry SummonEitrCostMultiplier { get; private set; } internal static ConfigEntry SummonHealthCostMultiplier { get; private set; } internal static ConfigEntry MeleeSwitchDistance { get; private set; } internal static ConfigEntry RangedSwitchDistance { get; private set; } internal static ConfigEntry WeaponSwapSeconds { get; private set; } internal static ConfigEntry FriendlyFireConeDegrees { get; private set; } internal static ConfigEntry ArchersKeepDistance { get; private set; } internal static ConfigEntry KiteDistance { get; private set; } internal static ConfigEntry KiteInterval { get; private set; } internal static ConfigEntry KiteDuration { get; private set; } internal static ConfigEntry DefendInterruptsWork { get; private set; } internal static ConfigEntry DefendRadius { get; private set; } internal static ConfigEntry WorkResumeSeconds { get; private set; } internal static ConfigEntry ElementalResistAtMaxSkill { get; private set; } internal static ConfigEntry PhysicalResistAtMaxSkill { get; private set; } internal static ConfigEntry ShieldAbsorbBonusAtMaxSkill { get; private set; } internal static ConfigEntry DamageBonusAtMaxSkill { get; private set; } internal static ConfigEntry RegenBloodMagicBonus { get; private set; } internal static ConfigEntry RegenOutOfCombatSeconds { get; private set; } internal static ConfigEntry ShowCrewHud { get; private set; } internal static ConfigEntry CrewHudX { get; private set; } internal static ConfigEntry CrewHudY { get; private set; } internal static ConfigEntry CrewHudScale { get; private set; } internal static ConfigEntry ShowRegenText { get; private set; } internal static ConfigEntry ShowSummonGlow { get; private set; } internal static ConfigEntry GlowColour { get; private set; } internal static ConfigEntry GlowSelectedColour { get; private set; } internal static ConfigEntry GlowIntensity { get; private set; } internal static ConfigEntry GlowRange { get; private set; } internal static ConfigEntry GlowHeight { get; private set; } internal static ConfigEntry MarkerEffectPrefab { get; private set; } internal static ConfigEntry RegenTextInterval { get; private set; } internal static ConfigEntry RegenTextMinimum { get; private set; } internal static ConfigEntry EnableVanillaCommands { get; private set; } internal static ConfigEntry AutoFollowOnSummon { get; private set; } internal static ConfigEntry ShowOrderMessages { get; private set; } internal static ConfigEntry CommandRadius { get; private set; } internal static ConfigEntry TargetRange { get; private set; } internal static ConfigEntry TargetAssistRadius { get; private set; } internal static ConfigEntry RecallEnabled { get; private set; } internal static ConfigEntry RecallDistance { get; private set; } internal static ConfigEntry RecallStuckDistance { get; private set; } internal static ConfigEntry RecallStuckSeconds { get; private set; } internal static ConfigEntry RecallElevation { get; private set; } internal static ConfigEntry RecallElevationSeconds { get; private set; } internal static ConfigEntry RecallStrandedDistance { get; private set; } internal static ConfigEntry RecallSpread { get; private set; } internal static ConfigEntry RecallGraceSeconds { get; private set; } internal static ConfigEntry TeleportSummons { get; private set; } internal static ConfigEntry MaxTeleportSummons { get; private set; } internal static ConfigEntry EscortRadius { get; private set; } internal static ConfigEntry SpeedMultiplier { get; private set; } internal static ConfigEntry FormUpWhenIdle { get; private set; } internal static ConfigEntry FollowFormationRadius { get; private set; } internal static ConfigEntry FormUpDelaySeconds { get; private set; } internal static ConfigEntry FollowDriveDistance { get; private set; } internal static ConfigEntry MatchPlayerRunSpeed { get; private set; } internal static ConfigEntry CatchUpMargin { get; private set; } internal static ConfigEntry UseCustomAgent { get; private set; } internal static ConfigEntry SummonAgentClimb { get; private set; } internal static ConfigEntry AutoJumpInterval { get; private set; } internal static ConfigEntry DriveOrderedMovement { get; private set; } internal static ConfigEntry RunOnOrders { get; private set; } internal static ConfigEntry StepAssistEnabled { get; private set; } internal static ConfigEntry StepAssistStallSeconds { get; private set; } internal static ConfigEntry StepAssistCooldown { get; private set; } internal static ConfigEntry StepAssistMinDistance { get; private set; } internal static ConfigEntry ShowOnMap { get; private set; } internal static ConfigEntry ShowNamesOnMap { get; private set; } internal static ConfigEntry MapPinType { get; private set; } internal static ConfigEntry MapPinDoubleSize { get; private set; } internal static ConfigEntry MapPinAnimate { get; private set; } internal static ConfigEntry TeleportGuardSeconds { get; private set; } internal static ConfigEntry CommandButton { get; private set; } internal static ConfigEntry AttackOrderSeconds { get; private set; } internal static ConfigEntry MoveOrderSeconds { get; private set; } internal static ConfigEntry MoveArriveDistance { get; private set; } internal static ConfigEntry MoveSpread { get; private set; } internal static ConfigEntry MultiTapSeconds { get; private set; } internal static ConfigEntry GuardRadius { get; private set; } internal static ConfigEntry GuardPatrol { get; private set; } internal static ConfigEntry GuardPatrolRadius { get; private set; } internal static ConfigEntry GuardPatrolInterval { get; private set; } internal static ConfigEntry OrdersOverrideCombat { get; private set; } internal static ConfigEntry ObeySeconds { get; private set; } internal static ConfigEntry RegroupDistance { get; private set; } internal static ConfigEntry ClassItems(SummonPreference pref) { return (ConfigEntry)(pref switch { SummonPreference.Melee => ClassMeleeItems, SummonPreference.Archer => ClassArcherItems, SummonPreference.Defender => ClassDefenderItems, SummonPreference.Healer => ClassHealerItems, SummonPreference.FireMage => ClassFireMageItems, SummonPreference.IceMage => ClassIceMageItems, _ => null, }); } internal static ConfigEntry ClassAnimation(SummonPreference pref) { return (ConfigEntry)(pref switch { SummonPreference.Melee => ClassMeleeAnimation, SummonPreference.Archer => ClassArcherAnimation, SummonPreference.Defender => ClassDefenderAnimation, SummonPreference.Healer => ClassHealerAnimation, SummonPreference.FireMage => ClassFireMageAnimation, SummonPreference.IceMage => ClassIceMageAnimation, _ => null, }); } private ConfigEntry BindClassItems(string className, string items, string note) { return ((BaseUnityPlugin)this).Config.Bind("Classes", className + "Items", items, "Item prefabs this class is raised with, comma-separated. " + note); } internal static ConfigEntry ClassAttackFrom(SummonPreference pref) { return (ConfigEntry)(pref switch { SummonPreference.Healer => ClassHealerAttackFrom, SummonPreference.FireMage => ClassFireMageAttackFrom, SummonPreference.IceMage => ClassIceMageAttackFrom, _ => null, }); } private ConfigEntry BindClassAttackFrom(string className, string donor) { return ((BaseUnityPlugin)this).Config.Bind("Classes", className + "AttackFrom", donor, "Item prefab whose ATTACK this class borrows - the projectile, damage and effect - while holding the weapon named in " + className + "Items. Empty means keep the weapon's own attack. This exists because the creature staves that cast correctly have no model at all, so a summon holding one appears empty-handed."); } internal static ConfigEntry ClassLimit(SummonPreference pref) { return (ConfigEntry)(pref switch { SummonPreference.Melee => ClassMeleeLimit, SummonPreference.Archer => ClassArcherLimit, SummonPreference.Defender => ClassDefenderLimit, SummonPreference.Healer => ClassHealerLimit, SummonPreference.FireMage => ClassFireMageLimit, SummonPreference.IceMage => ClassIceMageLimit, _ => null, }); } private ConfigEntry BindClassLimit(string className, int limit) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind("Classes", className + "Limit", limit, new ConfigDescription("How many " + className + " summons may exist at once. 0 means no limit of its own, so the crew cap is the only ceiling. At the limit the next raise becomes a warrior, with a notice, rather than costing you the Eitr for nothing.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 12), Array.Empty())); } internal static ConfigEntry ClassHealthScale(SummonPreference pref) { if (!HealthScales.TryGetValue(pref, out var value)) { return null; } return value; } internal static ConfigEntry ClassDamageScale(SummonPreference pref) { if (!DamageScales.TryGetValue(pref, out var value)) { return null; } return value; } internal static ConfigEntry ClassResistScale(SummonPreference pref) { if (!ResistScales.TryGetValue(pref, out var value)) { return null; } return value; } private void BindClassStats(SummonPreference pref, float health, float damage, float resist) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown string text = pref.ToString(); HealthScales[pref] = ((BaseUnityPlugin)this).Config.Bind("Classes", text + "HealthScale", health, new ConfigDescription("Maximum health for a " + text + ", as a multiple of what the crew-wide Blood Magic scaling already gives. 1 is no difference.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 4f), Array.Empty())); DamageScales[pref] = ((BaseUnityPlugin)this).Config.Bind("Classes", text + "DamageScale", damage, new ConfigDescription("Damage a " + text + " deals, as a multiple of the crew-wide scaling.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 4f), Array.Empty())); ResistScales[pref] = ((BaseUnityPlugin)this).Config.Bind("Classes", text + "ResistScale", resist, new ConfigDescription("How much of the crew-wide damage REDUCTION a " + text + " gets. Above 1 means it takes less than the rest of the crew; below 1, more.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 4f), Array.Empty())); } internal static ConfigEntry ClassRange(SummonPreference pref) { return (ConfigEntry)(pref switch { SummonPreference.Melee => ClassMeleeRange, SummonPreference.Archer => ClassArcherRange, SummonPreference.Defender => ClassDefenderRange, SummonPreference.Healer => ClassHealerRange, SummonPreference.FireMage => ClassFireMageRange, SummonPreference.IceMage => ClassIceMageRange, _ => null, }); } internal static ConfigEntry ClassUnlock(SummonPreference pref) { return (ConfigEntry)(pref switch { SummonPreference.Defender => ClassDefenderUnlock, SummonPreference.Healer => ClassHealerUnlock, SummonPreference.FireMage => ClassFireMageUnlock, SummonPreference.IceMage => ClassIceMageUnlock, _ => null, }); } private ConfigEntry BindClassRange(string className) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind("Classes", className + "Range", 0f, new ConfigDescription("Attack range this class uses its weapon from, in metres. 0 keeps the weapon's own. Creature weapons are tuned for the creature that owns them - the Dverger heal staff assumes a Dverger in its own formation, not a summon trailing you - so a healer that barely reaches anyone is fixed here.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 40f), Array.Empty())); } private ConfigEntry BindClassUnlock(string className, string prefab) { return ((BaseUnityPlugin)this).Config.Bind("Classes", className + "Unlock", prefab, "Item prefab that unlocks this class - it becomes available once you have HELD one. Empty means always available. Named as a prefab like the rest of this section; the translation to the game's internal key is done for you."); } private ConfigEntry BindClassAnimation(string className, string animation) { return ((BaseUnityPlugin)this).Config.Bind("Classes", className + "Animation", animation, "Attack animation to force on this class's weapons. A summon can only play the animations its OWN weapons use - 'attack' and 'attack_bow' - so any staff needs rebinding or it will equip, take a target and never swing. Empty leaves the weapon as the game shipped it."); } private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Expected O, but got Unknown //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Expected O, but got Unknown //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Expected O, but got Unknown //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Expected O, but got Unknown //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Expected O, but got Unknown //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Expected O, but got Unknown //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Expected O, but got Unknown //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Expected O, but got Unknown //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Expected O, but got Unknown //IL_06e1: Unknown result type (might be due to invalid IL or missing references) //IL_06eb: Expected O, but got Unknown //IL_071e: Unknown result type (might be due to invalid IL or missing references) //IL_0728: Expected O, but got Unknown //IL_075b: Unknown result type (might be due to invalid IL or missing references) //IL_0765: Expected O, but got Unknown //IL_0798: Unknown result type (might be due to invalid IL or missing references) //IL_07a2: Expected O, but got Unknown //IL_07f5: Unknown result type (might be due to invalid IL or missing references) //IL_07ff: Expected O, but got Unknown //IL_0832: Unknown result type (might be due to invalid IL or missing references) //IL_083c: Expected O, but got Unknown //IL_086f: Unknown result type (might be due to invalid IL or missing references) //IL_0879: Expected O, but got Unknown //IL_08ac: Unknown result type (might be due to invalid IL or missing references) //IL_08b6: Expected O, but got Unknown //IL_08e9: Unknown result type (might be due to invalid IL or missing references) //IL_08f3: Expected O, but got Unknown //IL_0926: Unknown result type (might be due to invalid IL or missing references) //IL_0930: Expected O, but got Unknown //IL_0963: Unknown result type (might be due to invalid IL or missing references) //IL_096d: Expected O, but got Unknown //IL_09c0: Unknown result type (might be due to invalid IL or missing references) //IL_09ca: Expected O, but got Unknown //IL_0a1d: Unknown result type (might be due to invalid IL or missing references) //IL_0a27: Expected O, but got Unknown //IL_0a5a: Unknown result type (might be due to invalid IL or missing references) //IL_0a64: Expected O, but got Unknown //IL_0a97: Unknown result type (might be due to invalid IL or missing references) //IL_0aa1: Expected O, but got Unknown //IL_0ad4: Unknown result type (might be due to invalid IL or missing references) //IL_0ade: Expected O, but got Unknown //IL_0b11: Unknown result type (might be due to invalid IL or missing references) //IL_0b1b: Expected O, but got Unknown //IL_0b4e: Unknown result type (might be due to invalid IL or missing references) //IL_0b58: Expected O, but got Unknown //IL_0b8b: Unknown result type (might be due to invalid IL or missing references) //IL_0b95: Expected O, but got Unknown //IL_0bc8: Unknown result type (might be due to invalid IL or missing references) //IL_0bd2: Expected O, but got Unknown //IL_0c05: Unknown result type (might be due to invalid IL or missing references) //IL_0c0f: Expected O, but got Unknown //IL_0c42: Unknown result type (might be due to invalid IL or missing references) //IL_0c4c: Expected O, but got Unknown //IL_0c7f: Unknown result type (might be due to invalid IL or missing references) //IL_0c89: Expected O, but got Unknown //IL_0cbc: Unknown result type (might be due to invalid IL or missing references) //IL_0cc6: Expected O, but got Unknown //IL_0d19: Unknown result type (might be due to invalid IL or missing references) //IL_0d23: Expected O, but got Unknown //IL_0d56: Unknown result type (might be due to invalid IL or missing references) //IL_0d60: Expected O, but got Unknown //IL_0d93: Unknown result type (might be due to invalid IL or missing references) //IL_0d9d: Expected O, but got Unknown //IL_0dd0: Unknown result type (might be due to invalid IL or missing references) //IL_0dda: Expected O, but got Unknown //IL_0e77: Unknown result type (might be due to invalid IL or missing references) //IL_0ea5: Unknown result type (might be due to invalid IL or missing references) //IL_0f07: Unknown result type (might be due to invalid IL or missing references) //IL_0f11: Expected O, but got Unknown //IL_0f44: Unknown result type (might be due to invalid IL or missing references) //IL_0f4e: Expected O, but got Unknown //IL_0fc1: Unknown result type (might be due to invalid IL or missing references) //IL_0fcb: Expected O, but got Unknown //IL_0ffe: Unknown result type (might be due to invalid IL or missing references) //IL_1008: Expected O, but got Unknown //IL_103b: Unknown result type (might be due to invalid IL or missing references) //IL_1045: Expected O, but got Unknown //IL_1078: Unknown result type (might be due to invalid IL or missing references) //IL_1082: Expected O, but got Unknown //IL_10d5: Unknown result type (might be due to invalid IL or missing references) //IL_10df: Expected O, but got Unknown //IL_1132: Unknown result type (might be due to invalid IL or missing references) //IL_113c: Expected O, but got Unknown //IL_116f: Unknown result type (might be due to invalid IL or missing references) //IL_1179: Expected O, but got Unknown //IL_11ac: Unknown result type (might be due to invalid IL or missing references) //IL_11b6: Expected O, but got Unknown //IL_11f5: Unknown result type (might be due to invalid IL or missing references) //IL_12b7: Unknown result type (might be due to invalid IL or missing references) //IL_12c1: Expected O, but got Unknown //IL_12f4: Unknown result type (might be due to invalid IL or missing references) //IL_12fe: Expected O, but got Unknown //IL_1331: Unknown result type (might be due to invalid IL or missing references) //IL_133b: Expected O, but got Unknown //IL_1377: Unknown result type (might be due to invalid IL or missing references) //IL_13b9: Unknown result type (might be due to invalid IL or missing references) //IL_13c3: Expected O, but got Unknown //IL_13f6: Unknown result type (might be due to invalid IL or missing references) //IL_1400: Expected O, but got Unknown //IL_1433: Unknown result type (might be due to invalid IL or missing references) //IL_143d: Expected O, but got Unknown //IL_1470: Unknown result type (might be due to invalid IL or missing references) //IL_147a: Expected O, but got Unknown //IL_14ad: Unknown result type (might be due to invalid IL or missing references) //IL_14b7: Expected O, but got Unknown //IL_150a: Unknown result type (might be due to invalid IL or missing references) //IL_1514: Expected O, but got Unknown //IL_1547: Unknown result type (might be due to invalid IL or missing references) //IL_1551: Expected O, but got Unknown //IL_1584: Unknown result type (might be due to invalid IL or missing references) //IL_158e: Expected O, but got Unknown //IL_15b6: Unknown result type (might be due to invalid IL or missing references) //IL_15c0: Expected O, but got Unknown //IL_15e8: Unknown result type (might be due to invalid IL or missing references) //IL_15f2: Expected O, but got Unknown //IL_1665: Unknown result type (might be due to invalid IL or missing references) //IL_166f: Expected O, but got Unknown //IL_16a2: Unknown result type (might be due to invalid IL or missing references) //IL_16ac: Expected O, but got Unknown //IL_16df: Unknown result type (might be due to invalid IL or missing references) //IL_16e9: Expected O, but got Unknown //IL_171c: Unknown result type (might be due to invalid IL or missing references) //IL_1726: Expected O, but got Unknown //IL_1779: Unknown result type (might be due to invalid IL or missing references) //IL_1783: Expected O, but got Unknown //IL_17b6: Unknown result type (might be due to invalid IL or missing references) //IL_17c0: Expected O, but got Unknown //IL_17f3: Unknown result type (might be due to invalid IL or missing references) //IL_17fd: Expected O, but got Unknown //IL_1850: Unknown result type (might be due to invalid IL or missing references) //IL_185a: Expected O, but got Unknown //IL_188d: Unknown result type (might be due to invalid IL or missing references) //IL_1897: Expected O, but got Unknown //IL_18ca: Unknown result type (might be due to invalid IL or missing references) //IL_18d4: Expected O, but got Unknown //IL_1907: Unknown result type (might be due to invalid IL or missing references) //IL_1911: Expected O, but got Unknown //IL_1944: Unknown result type (might be due to invalid IL or missing references) //IL_194e: Expected O, but got Unknown //IL_1981: Unknown result type (might be due to invalid IL or missing references) //IL_198b: Expected O, but got Unknown //IL_19be: Unknown result type (might be due to invalid IL or missing references) //IL_19c8: Expected O, but got Unknown //IL_19fb: Unknown result type (might be due to invalid IL or missing references) //IL_1a05: Expected O, but got Unknown //IL_1a38: Unknown result type (might be due to invalid IL or missing references) //IL_1a42: Expected O, but got Unknown //IL_1a75: Unknown result type (might be due to invalid IL or missing references) //IL_1a7f: Expected O, but got Unknown //IL_1ab2: Unknown result type (might be due to invalid IL or missing references) //IL_1abc: Expected O, but got Unknown //IL_1b0f: Unknown result type (might be due to invalid IL or missing references) //IL_1b19: Expected O, but got Unknown //IL_1b4c: Unknown result type (might be due to invalid IL or missing references) //IL_1b56: Expected O, but got Unknown //IL_1b89: Unknown result type (might be due to invalid IL or missing references) //IL_1b93: Expected O, but got Unknown //IL_1c2e: Unknown result type (might be due to invalid IL or missing references) //IL_1c38: Expected O, but got Unknown //IL_1c6b: Unknown result type (might be due to invalid IL or missing references) //IL_1c75: Expected O, but got Unknown //IL_1ca8: Unknown result type (might be due to invalid IL or missing references) //IL_1cb2: Expected O, but got Unknown //IL_1d29: Unknown result type (might be due to invalid IL or missing references) //IL_1d33: Expected O, but got Unknown //IL_1d66: Unknown result type (might be due to invalid IL or missing references) //IL_1d70: Expected O, but got Unknown //IL_1dc3: Unknown result type (might be due to invalid IL or missing references) //IL_1dcd: Expected O, but got Unknown //IL_1ea4: Unknown result type (might be due to invalid IL or missing references) //IL_1eae: Expected O, but got Unknown //IL_1ee1: Unknown result type (might be due to invalid IL or missing references) //IL_1eeb: Expected O, but got Unknown //IL_1f1e: Unknown result type (might be due to invalid IL or missing references) //IL_1f28: Expected O, but got Unknown //IL_1f5b: Unknown result type (might be due to invalid IL or missing references) //IL_1f65: Expected O, but got Unknown //IL_1fb8: Unknown result type (might be due to invalid IL or missing references) //IL_1fc2: Expected O, but got Unknown //IL_1ff5: Unknown result type (might be due to invalid IL or missing references) //IL_1fff: Expected O, but got Unknown //IL_2032: Unknown result type (might be due to invalid IL or missing references) //IL_203c: Expected O, but got Unknown //IL_208f: Unknown result type (might be due to invalid IL or missing references) //IL_2099: Expected O, but got Unknown //IL_20ec: Unknown result type (might be due to invalid IL or missing references) //IL_20f6: Expected O, but got Unknown //IL_2129: Unknown result type (might be due to invalid IL or missing references) //IL_2133: Expected O, but got Unknown //IL_21c6: Unknown result type (might be due to invalid IL or missing references) //IL_21d0: Expected O, but got Unknown //IL_2203: Unknown result type (might be due to invalid IL or missing references) //IL_220d: Expected O, but got Unknown //IL_2240: Unknown result type (might be due to invalid IL or missing references) //IL_224a: Expected O, but got Unknown //IL_233d: Unknown result type (might be due to invalid IL or missing references) //IL_2347: Expected O, but got Unknown //IL_239a: Unknown result type (might be due to invalid IL or missing references) //IL_23a4: Expected O, but got Unknown //IL_23cd: Unknown result type (might be due to invalid IL or missing references) //IL_23d7: Expected O, but got Unknown //IL_240a: Unknown result type (might be due to invalid IL or missing references) //IL_2414: Expected O, but got Unknown //IL_2447: Unknown result type (might be due to invalid IL or missing references) //IL_2451: Expected O, but got Unknown //IL_2484: Unknown result type (might be due to invalid IL or missing references) //IL_248e: Expected O, but got Unknown //IL_24c1: Unknown result type (might be due to invalid IL or missing references) //IL_24cb: Expected O, but got Unknown //IL_24fe: Unknown result type (might be due to invalid IL or missing references) //IL_2508: Expected O, but got Unknown //IL_253b: Unknown result type (might be due to invalid IL or missing references) //IL_2545: Expected O, but got Unknown //IL_2578: Unknown result type (might be due to invalid IL or missing references) //IL_2582: Expected O, but got Unknown //IL_25b5: Unknown result type (might be due to invalid IL or missing references) //IL_25bf: Expected O, but got Unknown //IL_25db: Unknown result type (might be due to invalid IL or missing references) //IL_261d: Unknown result type (might be due to invalid IL or missing references) //IL_2627: Expected O, but got Unknown //IL_265a: Unknown result type (might be due to invalid IL or missing references) //IL_2664: Expected O, but got Unknown //IL_2697: Unknown result type (might be due to invalid IL or missing references) //IL_26a1: Expected O, but got Unknown //IL_26d4: Unknown result type (might be due to invalid IL or missing references) //IL_26de: Expected O, but got Unknown //IL_2711: Unknown result type (might be due to invalid IL or missing references) //IL_271b: Expected O, but got Unknown //IL_276e: Unknown result type (might be due to invalid IL or missing references) //IL_2778: Expected O, but got Unknown //IL_27ab: Unknown result type (might be due to invalid IL or missing references) //IL_27b5: Expected O, but got Unknown //IL_27e8: Unknown result type (might be due to invalid IL or missing references) //IL_27f2: Expected O, but got Unknown //IL_2845: Unknown result type (might be due to invalid IL or missing references) //IL_284f: Expected O, but got Unknown //IL_2882: Unknown result type (might be due to invalid IL or missing references) //IL_288c: Expected O, but got Unknown //IL_28a2: Unknown result type (might be due to invalid IL or missing references) //IL_28ac: Expected O, but got Unknown //IL_28da: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Instance = this; if ((int)SystemInfo.graphicsDeviceType == 4) { Log.LogInfo((object)"Headless process - SkeletonCrew disabled."); return; } Enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Master switch. When false nothing is patched or modified."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind("General", "VerboseLogging", false, "Developer logging: a breakdown of every summon discovered (components, unsummon distance, what it holds and whether the game can draw it), plus leash, escort, recall and loadout detail. Roughly 60 lines a minute with a full crew, so it is off unless you are diagnosing something."); RegenPercentPerSecond = ((BaseUnityPlugin)this).Config.Bind("Regen", "PercentOfMaxPerSecond", 1f, new ConfigDescription("Base health regenerated per second, as a percent of the summon's max health, before Blood Magic scaling. 0 disables regen entirely.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); HarvestEnabled = ((BaseUnityPlugin)this).Config.Bind("Harvest", "Enabled", false, "Master switch for harvesting. With this off, summons never target trees, ore or destructibles at all and the command button keeps its combat/move meanings."); HarvestTrees = ((BaseUnityPlugin)this).Config.Bind("Harvest", "Trees", true, "Allow felling trees and chopping the logs they leave."); HarvestRock = ((BaseUnityPlugin)this).Config.Bind("Harvest", "OreAndRock", true, "Allow mining ore nodes and rocks."); HarvestDestructibles = ((BaseUnityPlugin)this).Config.Bind("Harvest", "Destructibles", true, "Allow breaking stumps, small rocks and similar. Player buildings use WearNTear and are never targetable."); HarvestPickables = ((BaseUnityPlugin)this).Config.Bind("Harvest", "Pickables", false, "Let summons PICK berries, mushrooms and thistle for you. These are never destroyed either way - they respawn, so smashing one would lose the resource permanently. With this off they are simply ignored."); HarvestContinue = ((BaseUnityPlugin)this).Config.Bind("Harvest", "ContinueNearby", true, "After finishing, move to the next target of the SAME kind within the radius, so one order clears a copse."); HarvestSettleSeconds = ((BaseUnityPlugin)this).Config.Bind("Harvest", "SettleSeconds", 1f, new ConfigDescription("Pause after finishing a target before looking for the next. A felled tree's log spawns as the trunk dies, so searching immediately finds the stump instead.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); HarvestLogSearchRadius = ((BaseUnityPlugin)this).Config.Bind("Harvest", "LogSearchRadius", 12f, new ConfigDescription("How far from a felled tree to look for the log it dropped. Big trees fall some distance from their stump.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 40f), Array.Empty())); HarvestReach = ((BaseUnityPlugin)this).Config.Bind("Harvest", "Reach", 3.5f, new ConfigDescription("How close a summon must be to work its target.", (AcceptableValueBase)(object)new AcceptableValueRange(1.5f, 10f), Array.Empty())); ProtectPlayerBase = ((BaseUnityPlugin)this).Config.Bind("Harvest", "ProtectPlayerBase", true, "Refuse to harvest inside a player base, using the game's own player-base area - the one that stops monsters spawning there. This covers what the build-system check cannot: a grown crop is SPAWNED by the plant rather than placed, so it carries no piece and would otherwise be treated as wild. Wood is deliberately exempt - trees and logs can always be harvested, because a tree inside a base is a crop and replanting and felling is the point of a tree farm."); HarvestStructures = ((BaseUnityPlugin)this).Config.Bind("Harvest", "Structures", true, "Allow mining world-generated structures - marble and stone ruins, and the like. Anything a PLAYER placed is refused unconditionally and is not affected by this setting: that check uses the game's own Piece.IsPlacedByPlayer(), so your base and other players' builds are never targetable regardless."); HarvestAreaRadius = ((BaseUnityPlugin)this).Config.Bind("Harvest", "AreaRadius", 35f, new ConfigDescription("How far an AREA order reaches, measured from the target you pointed at and fixed there. Larger than ContinueRadius because the crew now covers it in parallel rather than one target at a time. Anchoring is what makes an area order END: searching around whoever finished last let the patch walk with them.", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 100f), Array.Empty())); HarvestAreasPerWorker = ((BaseUnityPlugin)this).Config.Bind("Harvest", "AreasPerWorker", 3, new ConfigDescription("How many intact chunks of an ore node one summon is worth. A big node has room for the whole crew and they should gang up on it; a bush has room for one, and a second summon there is one not clearing the rest of the patch. Scales with the areas LEFT, so a nearly-finished node releases the crew to spread out again.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), Array.Empty())); HarvestReachVertical = ((BaseUnityPlugin)this).Config.Bind("Harvest", "ReachVertical", 5f, new ConfigDescription("How far ABOVE or below itself a summon can work. Kept separate from Reach, which is horizontal: a single 3D radius cannot touch the chunks left on top of a tall ore node, so the crew clears almost all of it and then swings at nothing.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 15f), Array.Empty())); HarvestStallSeconds = ((BaseUnityPlugin)this).Config.Bind("Harvest", "StallSeconds", 12f, new ConfigDescription("Give up on an ore node after this long with no area destroyed. Some geometry is simply unreachable, and standing at it forever blocks the rest of an area order. Applies only to ore and rock, where progress is measurable - never to trees, where slow is not the same as stuck.", (AcceptableValueBase)(object)new AcceptableValueRange(3f, 60f), Array.Empty())); HarvestSwingSeconds = ((BaseUnityPlugin)this).Config.Bind("Harvest", "SwingSeconds", 1.2f, new ConfigDescription("Seconds between harvest swings.", (AcceptableValueBase)(object)new AcceptableValueRange(0.3f, 10f), Array.Empty())); HarvestDamage = ((BaseUnityPlugin)this).Config.Bind("Harvest", "DamagePerSwing", 25f, new ConfigDescription("Chop/pickaxe damage per swing. Tool TIER still gates what they may harvest at all - this only sets speed.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 500f), Array.Empty())); SummonWeaponPreference = ((BaseUnityPlugin)this).Config.Bind("Commands", "SummonWeaponPreference", SummonPreference.Melee, "What the NEXT summon should be armed as. Change it between casts to build a mixed crew - two archers then a warrior, say. Applies live, no restart.\nMelee and Ranged carry one kind of weapon and never swap. The classes carry their own kit and unlock as you craft the matching staff. Adaptive carries BOTH a melee weapon and a bow and switches between them deliberately (ammo, line of sight, whether something is on top of it, whether you are in the firing line, distance with hysteresis) - set EnableAdaptive to offer it."); EnableAdaptive = ((BaseUnityPlugin)this).Config.Bind("Commands", "EnableAdaptive", false, "Show the Adaptive summon type, which carries both a melee weapon and a bow and switches between them deliberately. Hidden by default while the class list is being worked out; the behaviour itself is untouched and comes straight back when this is on."); ClassMeleeItems = BindClassItems("Melee", "skeleton_sword2", "A sword and nothing else. Empty means 'use the creature's own weapon pool' instead, which also lets vanilla roll a shield - skeleton_sword_hildir is a fire sword that animates, if you want the variety."); ClassArcherItems = BindClassItems("Archer", "skeleton_bow2", "A bow and nothing else. Empty means 'use the creature's own weapon pool', filtered to ranged."); ClassDefenderItems = BindClassItems("Defender", "skeleton_sword2,ShieldWood", "Sword and shield. If the shield does not appear, its prefab name is wrong for this version - run skcrew_weapons shield to find the right one and put it here. A missing prefab is logged by name rather than silently dropped."); ClassHealerItems = BindClassItems("Healer", "StaffShield", "A staff a player can hold, so it can be SEEN. Its own attack is replaced by HealerAttackFrom."); ClassFireMageItems = BindClassItems("FireMage", "StaffFireball", "Staff of Embers. Its own fireball is used unless FireMageAttackFrom names another."); ClassIceMageItems = BindClassItems("IceMage", "StaffIceShards", "Staff of Frost. Its own attack is used unless IceMageAttackFrom names another."); ClassHealerAttackFrom = BindClassAttackFrom("Healer", "DvergerStaffHeal_heal"); ClassFireMageAttackFrom = BindClassAttackFrom("FireMage", string.Empty); ClassIceMageAttackFrom = BindClassAttackFrom("IceMage", string.Empty); ClassMeleeRange = BindClassRange("Melee"); ClassArcherRange = BindClassRange("Archer"); ClassDefenderRange = BindClassRange("Defender"); ClassHealerRange = BindClassRange("Healer"); ClassFireMageRange = BindClassRange("FireMage"); ClassIceMageRange = BindClassRange("IceMage"); ClassDefenderUnlock = BindClassUnlock("Defender", "ShieldBlackmetal"); ClassHealerUnlock = BindClassUnlock("Healer", "StaffShield"); ClassFireMageUnlock = BindClassUnlock("FireMage", "StaffFireball"); ClassIceMageUnlock = BindClassUnlock("IceMage", "StaffIceShards"); ClassMeleeLimit = BindClassLimit("Melee", 0); ClassArcherLimit = BindClassLimit("Archer", 0); ClassDefenderLimit = BindClassLimit("Defender", 2); ClassHealerLimit = BindClassLimit("Healer", 1); ClassFireMageLimit = BindClassLimit("FireMage", 1); ClassIceMageLimit = BindClassLimit("IceMage", 1); BindClassStats(SummonPreference.Defender, 1.35f, 0.8f, 1.3f); BindClassStats(SummonPreference.Melee, 1f, 1f, 1f); BindClassStats(SummonPreference.Archer, 0.9f, 1.1f, 0.9f); BindClassStats(SummonPreference.FireMage, 0.7f, 1.4f, 0.8f); BindClassStats(SummonPreference.IceMage, 0.7f, 1.4f, 0.8f); BindClassStats(SummonPreference.Healer, 0.8f, 0.6f, 1f); BindClassStats(SummonPreference.Adaptive, 1f, 1f, 1f); SummonPresets = ((BaseUnityPlugin)this).Config.Bind("Classes", "Presets", "Vanguard=1d,2a,1r | Warband=1d,1a,1r,1f,1i,1h | Casters=1d,1h,1f,1i | Skirmish=1d,1a,2r", "Named crew compositions, raised whole from a single cast. Separate presets with '|', write each as 'Name=count letter, ...'.\nd defender, a attacker (melee), r ranged (archer), f fire mage, i ice mage, h healer.\nThe cast costs one summon's health and Eitr PER summon raised, and the group is trimmed to the slots your staff still has free."); GroupCostPerExtra = ((BaseUnityPlugin)this).Config.Bind("Classes", "GroupCostPerExtra", 0f, new ConfigDescription("What each EXTRA summon in a group adds to the cast's price, as a fraction of one summon. At 0 a group costs exactly what one summon costs, however many it raises, rather than six times - because nobody carries six summons' worth of Eitr, and a preset priced that way either fails the cast or trims itself to one. 0 makes a group cost the same as one summon; 1 charges full price for every one.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); HealerFormationScale = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerFormationScale", 0.7f, new ConfigDescription("How far out the healer stands, as a multiple of the formation radius. Below 1 keeps it inside the crew rather than behind it: distance is a mage's protection, but a healer's job is proximity, and the back line's spacing left it trailing so far back it looked useless.", (AcceptableValueBase)(object)new AcceptableValueRange(0.2f, 2f), Array.Empty())); LavaRescueSeconds = ((BaseUnityPlugin)this).Config.Bind("Combat", "LavaRescueSeconds", 1.5f, new ConfigDescription("How long a summon may burn before it is lifted out to safe ground. Walking out works for one that wandered in at the edge; it does not for one that has gone off a ledge into a pit, and Ashlands lava kills faster than a skeleton can climb. 0 disables the rescue and lets them burn.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 30f), Array.Empty())); LavaEscapeSeconds = ((BaseUnityPlugin)this).Config.Bind("Combat", "LavaEscapeSeconds", 6f, new ConfigDescription("How long a summon commits to the exit it picked when it found itself in lava. Crossing lava is fine; standing in it is not - and re-deciding every tick is how a summon ends up dithering on the shore while it burns, because the in-lava test is a point sample that flickers as it walks.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 30f), Array.Empty())); RoleFormation = ((BaseUnityPlugin)this).Config.Bind("Classes", "RoleFormation", true, "Position the crew by ROLE rather than by join order: defenders between you and whatever is coming, melee flanking them, archers behind those, mages and the healer at the back. The ring orients on the nearest threat, or on the way you are facing when there is none."); FrontLineRadiusScale = ((BaseUnityPlugin)this).Config.Bind("Classes", "FrontLineRadiusScale", 1.35f, new ConfigDescription("How far out a Defender stands, as a multiple of the formation radius. Above 1 puts it AHEAD of the crew, which is what makes it meet a threat first.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 3f), Array.Empty())); BackLineRadiusScale = ((BaseUnityPlugin)this).Config.Bind("Classes", "BackLineRadiusScale", 1.15f, new ConfigDescription("How far behind the centre a mage or healer stands, as a multiple of the formation radius. They are on the far side of you from the threat, so this is depth rather than distance from the fight.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 3f), Array.Empty())); BackLineEngageDistance = ((BaseUnityPlugin)this).Config.Bind("Classes", "BackLineEngageDistance", 8f, new ConfigDescription("How close something must come before a mage or healer will pick a fight with it. Beyond this they leave the opening to the front line and keep formation - which is the whole of 'casters try not to aggro'. They always defend themselves, and an explicit order always overrides this.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 40f), Array.Empty())); BackLineScreenDistance = ((BaseUnityPlugin)this).Config.Bind("Classes", "BackLineScreenDistance", 12f, new ConfigDescription("How close a defender or warrior must be for a mage or healer to be SKIPPED by hostile target selection - the game's only expression of a taunt, since a hostile picks purely by distance and nothing can make a defender more appealing. Never latched on: a mage standing alone stays visible, because a hostile that finds nobody goes looking for you instead. 0 disables it.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 40f), Array.Empty())); BackLineFleeHealth = ((BaseUnityPlugin)this).Config.Bind("Classes", "BackLineFleeHealth", 0.35f, new ConfigDescription("Health fraction below which a mage or healer breaks off and backs away, using the game's own flee behaviour. It only applies while recently hurt, so they return once the pressure is off rather than running for good. 0 disables it.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.9f), Array.Empty())); FrontLineLeashScale = ((BaseUnityPlugin)this).Config.Bind("Classes", "FrontLineLeashScale", 0.5f, new ConfigDescription("How far a Defender will chase, as a fraction of the crew's leash. Below 1 keeps it near you - a screen that chases is a gap.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 1f), Array.Empty())); FrontLineViewMultiplier = ((BaseUnityPlugin)this).Config.Bind("Classes", "FrontLineViewMultiplier", 1.5f, new ConfigDescription("How much further a Defender sees and hears than the rest of the crew. Above 1 means it notices a threat first, and so is the one that goes to meet it.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 3f), Array.Empty())); HealerHealsPlayers = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerHealsPlayers", true, "A Healer summon mends you, the crew and your tames with its staff, in combat and out of it. The staff only ever aims at what the AI has targeted, so without this the class has nothing to do between fights - which is exactly when you want it. Everything the healer does is a cast: one flat heal, on one timer, so it is never confused with the out-of-combat regeneration in the Regen section."); HealerWardInterval = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerWardInterval", 30f, new ConfigDescription("Seconds between wards. The healer puts the staff's shield on whoever is TAKING the damage - the defender holding the line, or you - rather than on itself. Absorb applied before a blow is worth more than healing applied after it: it cannot overheal and it cannot arrive too late. This replaces the old 90-second self-shield, which protected the one summon hostiles are steered away from.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 300f), Array.Empty())); DevotionScaling = ((BaseUnityPlugin)this).Config.Bind("Classes", "DevotionScaling", true, "The crew is as strong as the caster who raised it. Wearing mage armour and carrying staves gives your summons the full skill and progression bonus; leaving the staves at base in iron armour gives you less of it. The BASE strength never changes, so a melee player's summons work exactly as well as they always did - they just do not get the specialist's multiplier on top. Measured once when a summon is raised and written onto it, so dying or changing gear cannot weaken a crew you already paid for; re-evaluation only ever raises it."); DevotionFloor = ((BaseUnityPlugin)this).Config.Bind("Classes", "DevotionFloor", 0.5f, new ConfigDescription("How much of the bonus a caster with no magic gear and no staves still gets. At 0.5 a full melee build keeps half; at 1 the whole feature is neutral.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); DevotionFullGear = ((BaseUnityPlugin)this).Config.Bind("Classes", "DevotionFullGear", 1.3f, new ConfigDescription("The equipment Eitr-regeneration total counted as a complete caster's outfit. This is the game's own GetEquipmentEitrRegenModifier, which excludes food, and the right value depends on which mage set you are wearing. The default is a measured figure rather than a guess: a full Ashlands caster outfit reads 1.30. The crew trace prints your current total, so if yours differs, set this to it.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 5f), Array.Empty())); DevotionFullArsenal = ((BaseUnityPlugin)this).Config.Bind("Classes", "DevotionFullArsenal", 3f, new ConfigDescription("How many magic weapons in your pack count as a full arsenal. Counted from the whole inventory rather than the hotbar - a staff two rows down is still one you hauled out here, and leaving them in a chest is what this is meant to notice.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 10f), Array.Empty())); HealerWardSeconds = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerWardSeconds", 120f, new ConfigDescription("How long a ward lasts on the crew, in seconds. Vanilla derives this from the staff's upgrade level, and a summon's staff is level 1 - so the crew got about a minute where your own level 4 staff gives four, and a ward that expires between fights is never up when one starts. 0 keeps whatever the game would have given.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 600f), Array.Empty())); HealerWardRadius = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerWardRadius", 8f, new ConfigDescription("How far the ward spreads from wherever the crew is bunched. One cast shields everybody inside this, so it decides whether a bubble protects the whole line or only the summon it was aimed at.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 30f), Array.Empty())); WardApproachRadius = ((BaseUnityPlugin)this).Config.Bind("Classes", "WardApproachRadius", 60f, new ConfigDescription("How far off a hostile can be and still have the healer put bubbles up. Deliberately much wider than the engage range, and measured from EVERY summon and from you rather than from the healer at the back: Ashlands creatures notice you from further off than a skeleton notices them, so a short radius means the first thing that happens is a Morgen arriving rather than a bubble going up. Anything already hunting you counts wherever it is inside this. Wide enough that in the Ashlands the crew will be warded most of the time, which is what not being blindsided costs. With nothing in range the healer stays quiet.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 120f), Array.Empty())); HealerCriticalHealth = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerCriticalHealth", 0.35f, new ConfigDescription("Below this fraction of health an ally is an emergency: it is healed first and the staff's ordinary cadence is broken to do it. A fixed cooldown is right for sustained healing and wrong for somebody about to die.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.9f), Array.Empty())); HealerEmergencyInterval = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerEmergencyInterval", 3f, new ConfigDescription("Shortest gap between emergency casts. Low enough to answer a spike, high enough that an ally kept below the critical mark does not turn into a second, faster cadence that ignores the cooldown entirely.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 60f), Array.Empty())); HealerCastHealPercent = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerCastHealPercent", 0.12f, new ConfigDescription("Heal as a fraction of the patient's MAXIMUM health, used when it beats the flat amount. One number cannot serve a 200hp archer and a 1000hp defender: flat is either an overheal on one or a rounding error on the other. 0 disables scaling.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.5f), Array.Empty())); HealerCastInterval = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerCastInterval", 8f, new ConfigDescription("Seconds between staff casts, and so the healer's entire cadence - there is no longer a quiet pulse between them. This was 20 while the bubble appeared to ignore its cooldown; the cause turned out to be the CanUseAttack postfix overriding every cooldown in the game, so the long interval was only buying a slow healer. A heal you ask for waits for the next cast window, so this is also the worst-case delay on an ordered heal.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 300f), Array.Empty())); HealerCastHealAmount = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerCastHealAmount", 60f, new ConfigDescription("Health restored by each staff cast, to whoever it was aimed at and anyone close to them. This is the healer's real contribution - no attack in the game heals another character, so the staff supplies the animation and the area effect while the healing is applied alongside it.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 200f), Array.Empty())); HealerCastHealRadius = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerCastHealRadius", 8f, new ConfigDescription("How far the cast's healing spreads around its target. The one it aimed at is always healed; this decides who else is caught by it.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 30f), Array.Empty())); HealerCastsStaff = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerCastsStaff", true, "Let the healer cast the staff it carries at whoever needs it. Turn this off and it mends by the direct heal alone - no cast, no animation, no area effect. Worth reaching for if the staff you have given it turns out to do something other than heal: the creature staves are named by their owner rather than their effect, and an area 'heal' can be a ward."); HealerHealThreshold = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerHealThreshold", 0.85f, new ConfigDescription("How hurt someone must be before the healer will cast on them, as a fraction of their health. At 0.85 it ignores scratches. Set to 1 and it will cast at anyone a single point below full, which - with the whole crew as patients - means casting more or less continuously.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 1f), Array.Empty())); HealerRescueHealth = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerRescueHealth", 0.5f, new ConfigDescription("Health fraction below which a healer leaves formation and walks to you. It stands at the back and its staff has a fixed reach, so without this it can be in the crew, alive, and simply too far away to help. 0 keeps it in formation whatever happens.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); HealerHealRadius = ((BaseUnityPlugin)this).Config.Bind("Classes", "HealerHealRadius", 20f, new ConfigDescription("The healer's REACH: how far it will look for a patient, and how far it will cast. Beyond this it walks closer rather than casting at nothing. Not to be confused with HealerCastHealRadius, which is how far the healing splashes once the cast lands.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 60f), Array.Empty())); ProgressionBonusPerUnlock = ((BaseUnityPlugin)this).Config.Bind("Classes", "ProgressionBonusPerUnlock", 0.1f, new ConfigDescription("How much stronger the whole crew gets for each class you have unlocked. 0.1 means +10% per unlock, applied to the existing health, resistance and damage scaling rather than as a separate bonus - so there is one set of numbers to reason about, not two. 0 disables the reward and leaves unlocks as pure access.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); ClassMeleeAnimation = BindClassAnimation("Melee", string.Empty); ClassArcherAnimation = BindClassAnimation("Archer", string.Empty); ClassDefenderAnimation = BindClassAnimation("Defender", string.Empty); ClassHealerAnimation = BindClassAnimation("Healer", "attack"); ClassFireMageAnimation = BindClassAnimation("FireMage", "attack"); ClassIceMageAnimation = BindClassAnimation("IceMage", "attack"); MenuKey = ((BaseUnityPlugin)this).Config.Bind("Commands", "MenuKey", new KeyboardShortcut((KeyCode)282, Array.Empty()), "Opens the crew menu: come back, hold, stance, summon type and the harvest toggles, navigated with the arrow keys. The command button keeps every aimed order, which is faster mid-fight - this is for the ones you cannot aim at or would not remember."); SelectionKey = ((BaseUnityPlugin)this).Config.Bind("Commands", "SelectionKey", new KeyboardShortcut((KeyCode)284, Array.Empty()), "Cycles which summon your orders apply to: everyone, then each summon in turn, then back to everyone. Lets you place the crew one at a time - send each where you want it, then cycle back to ALL and recall them together. The crew HUD always shows the current target, so it cannot be left pointing somewhere you have forgotten about."); AvoidLava = ((BaseUnityPlugin)this).Config.Bind("Combat", "AvoidLava", true, "Keep the crew out of lava: they will not chase something standing in it, will not wander into it, will climb out if caught, will refuse an order aimed at it, and will step around a patch in their way. The one exception is YOU - walk into lava yourself and they follow you in rather than bouncing at the edge."); LavaLookahead = ((BaseUnityPlugin)this).Config.Bind("Combat", "LavaLookahead", 3f, new ConfigDescription("Metres probed ahead while walking under orders, to spot lava before stepping in. This is a local sidestep, not routing - the navmesh has no idea lava exists, so it cannot find its way around a large field. Raise it to react earlier at the cost of wider detours; 0 disables just the sidestep.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 15f), Array.Empty())); LavaCrossingTolerance = ((BaseUnityPlugin)this).Config.Bind("Combat", "LavaCrossingTolerance", 8f, new ConfigDescription("How wide an unbroken run of lava has to be, in metres, before the crew treats it as a crossing they will not make to reach a fight. Refusing ANY lava on the line was the Ashlands problem: almost every line there crosses a vein, so orders were cancelled constantly and the avoidance became more disruptive than the lava. They will stride over a narrow vein and refuse a field. 0 refuses nothing.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 40f), Array.Empty())); LavaDodge = ((BaseUnityPlugin)this).Config.Bind("Combat", "LavaDodge", false, "Re-aim each step around lava while walking under orders. Off by default: the sidestep is chosen afresh every frame, so in a biome that is mostly lava the crew weaves instead of walking. Climbing out and refusing a fight across a field are handled separately and are unaffected by this."); FocusFire = ((BaseUnityPlugin)this).Config.Bind("Combat", "FocusFire", true, "The crew fights one thing at a time. Whatever is attacking a summon or you becomes everyone's target, so a summon under attack is helped rather than watched. A summon already defending itself against something else keeps that fight, your own orders always win, and the healer is never given a target."); PressureWindowSeconds = ((BaseUnityPlugin)this).Config.Bind("Combat", "PressureWindowSeconds", 4f, new ConfigDescription("How many seconds of damage count as 'being hit right now'. This is what decides who the healer wards and who it heals first - damage RATE, rather than who is missing the most health, because an archer at 40% standing safely behind is not the one in trouble. Wider reacts more slowly but ignores single stray hits.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 20f), Array.Empty())); HazardMemorySeconds = ((BaseUnityPlugin)this).Config.Bind("Combat", "HazardMemorySeconds", 2f, new ConfigDescription("How long after being hurt by the WORLD - lava, ground fire, the Ashlands ocean, poison gas - a summon still counts as standing in it. Each tick of damage pushes this out again, so it means 'the thing I am in is still burning me' rather than 'I was hurt once'. Too high and they run from hazards they have already left.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 10f), Array.Empty())); HazardSettleSeconds = ((BaseUnityPlugin)this).Config.Bind("Combat", "HazardSettleSeconds", 3f, new ConfigDescription("How long after climbing out of something a summon refuses to start another escape, unless it is genuinely still being burned. Without this an escape that ends on the shoreline restarts immediately and the summon stutters in and out of the fight it was in.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 15f), Array.Empty())); RouteDetourFactor = ((BaseUnityPlugin)this).Config.Bind("Combat", "RouteDetourFactor", 2f, new ConfigDescription("The longest way round the crew will accept, as a multiple of the direct distance. Above about 2 a detour stops looking like avoiding lava and starts looking like a summon wandering off; below about 1.3 almost nothing qualifies and they simply wait at the edge instead.", (AcceptableValueBase)(object)new AcceptableValueRange(1.1f, 5f), Array.Empty())); MarchingOrder = ((BaseUnityPlugin)this).Config.Bind("Combat", "MarchingOrder", true, "Hold the formation while you are WALKING, not only when you stop - defender ahead, the line flanking, casters and healer behind you. Without it the crew travels as a clump in whatever order vanilla's following left them, which is the worst possible arrangement for whatever you walk into next. They jog to dress the line and only run when genuinely catching up."); MarchSlack = ((BaseUnityPlugin)this).Config.Bind("Combat", "MarchSlack", 3f, new ConfigDescription("How far out of place a marching summon may be before it corrects, in metres. This is the whole reason a moving formation is liveable: the point it is aiming for moves with you every frame, so without slack they re-path constantly and walk like a machine. Lower is a tighter formation and more fidgeting.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 15f), Array.Empty())); PullTactics = ((BaseUnityPlugin)this).Config.Bind("Combat", "PullTactics", true, "Open a distant fight with the ranged summons while the front line holds its place, then engage together once the target arrives. The line joins instantly if anything attacks the crew or you, so this only ever applies to a fight you are STARTING. Note that Valheim also aggroes on noise, so a pull can still wake a neighbour."); PullTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind("Combat", "PullTimeoutSeconds", 6f, new ConfigDescription("How long the crew will hold while the puller tries to bring an enemy in. Past this everybody engages regardless of distance: something that has not closed by now is not going to, and standing still while one summon plinks a Morgen is worse than committing. 0 disables pulling entirely.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); PullReceiveDistance = ((BaseUnityPlugin)this).Config.Bind("Combat", "PullReceiveDistance", 15f, new ConfigDescription("How far away a target has to be for the crew to pull it rather than charge it, in metres. Inside this the whole crew simply engages - a pull that starts at arm's length is just the front line standing still for no reason.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); PreciseHoldSlack = ((BaseUnityPlugin)this).Config.Bind("Commands", "PreciseHoldSlack", 1f, new ConfigDescription("How far a summon placed on an exact spot may drift before it walks back, in metres. Ring guards use a much looser rule so a crew does not re-path every frame over a metre of slop; a placed summon cannot afford that slack, because 'close enough' is how one sent to a doorway ends up standing beside it. Below about 0.5 they will fidget.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 10f), Array.Empty())); MoveEndsInHold = ((BaseUnityPlugin)this).Config.Bind("Commands", "MoveEndsInHold", true, "A summon that reaches a move destination HOLDS it - standing exactly there, fighting what comes within the guard radius and then returning to the spot - until you recall it. Off restores the old behaviour, where the order simply expired and the summon idled wherever it happened to end up."); CycleSummonTypeKey = ((BaseUnityPlugin)this).Config.Bind("Commands", "CycleSummonTypeKey", new KeyboardShortcut((KeyCode)288, Array.Empty()), "Cycles Random -> Melee -> Ranged in-game, so you can pick a type without opening the config."); AllowDismiss = ((BaseUnityPlugin)this).Config.Bind("Commands", "AllowDismiss", true, "Add a dismiss option when hovering one of your summons, alongside the vanilla follow/stay interaction. Uses vanilla's own unsummon so the dissolve effect and networking are correct."); AttackInWater = ((BaseUnityPlugin)this).Config.Bind("Combat", "AttackInWater", true, "Let summons attack while swimming. Vanilla refuses every attack in water, so a summon that follows you in becomes useless until it reaches land."); NoPlayerCollision = ((BaseUnityPlugin)this).Config.Bind("Combat", "NoPlayerCollision", true, "Walk through your own summons. Also stops a clustered crew shoving you around, which is worst exactly when they gather on you."); AdjustAttackGeometry = ((BaseUnityPlugin)this).Config.Bind("Combat", "AdjustAttackGeometry", true, "Lower and widen the summon's swing so it connects with ground-level enemies (blobs, oozers, growths). Applied to a private copy of the weapon's data, so it reaches YOUR summons only - no other creature carrying the same weapon, and nothing on anyone else's screen."); AttackRange = ((BaseUnityPlugin)this).Config.Bind("Combat", "AttackRange", 1.9f, new ConfigDescription("Minimum reach for a summon's swing. Vanilla skeleton axe is 1.8. Raising much beyond 2 looks like they are hitting nothing. Applied live.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 6f), Array.Empty())); AttackRayWidth = ((BaseUnityPlugin)this).Config.Bind("Combat", "AttackRayWidth", 0.65f, new ConfigDescription("Minimum thickness of the swing. Vanilla is 0.6. Do not raise this much: a sweep wide enough to already contain the target at contact range hits NOTHING, because the spherecast ignores colliders overlapping its origin. Reach low targets with AttackHeight instead.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 3f), Array.Empty())); AttackAngle = ((BaseUnityPlugin)this).Config.Bind("Combat", "AttackAngle", 100f, new ConfigDescription("Minimum arc of the swing in degrees. Vanilla is 90; wider forgives a target stepping aside without looking like a phantom hit.", (AcceptableValueBase)(object)new AcceptableValueRange(30f, 360f), Array.Empty())); StanceSetting = ((BaseUnityPlugin)this).Config.Bind("Combat", "Stance", CrewStance.Balanced, "How eagerly the crew looks for a fight. Balanced intercepts hostiles near you. Offensive sweeps wider and goes to them. Defensive answers only what is actually coming for you or the crew, and will not chase far from you. Explicit orders always override this. Toggle in game with StanceKey."); StanceKey = ((BaseUnityPlugin)this).Config.Bind("Commands", "StanceKey", new KeyboardShortcut((KeyCode)92, Array.Empty()), "Cycles Balanced -> Offensive -> Defensive."); OffensiveViewMultiplier = ((BaseUnityPlugin)this).Config.Bind("Combat", "OffensiveViewMultiplier", 2f, new ConfigDescription("Offensive only: how much further summons see, hear and alert on their OWN, relative to the prefab. This is what makes them spot things independently rather than only reacting to what the escort sweep finds near you. Set 1 to leave perception alone. Affects only your summons - these ranges are per-creature.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 5f), Array.Empty())); OffensiveReachMultiplier = ((BaseUnityPlugin)this).Config.Bind("Combat", "OffensiveReachMultiplier", 2f, new ConfigDescription("How much further an offensive crew looks for something to engage, relative to EscortRadius.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 5f), Array.Empty())); DefensiveReachMultiplier = ((BaseUnityPlugin)this).Config.Bind("Combat", "DefensiveReachMultiplier", 0.6f, new ConfigDescription("How much of EscortRadius a defensive crew watches. Below 1 so they hold near you.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 1f), Array.Empty())); ChaseLeash = ((BaseUnityPlugin)this).Config.Bind("Combat", "ChaseLeash", 40f, new ConfigDescription("Balanced and Offensive: how far from you a fight the crew started THEMSELVES may travel before they are called off it. Ordered fights are never leashed. Without a cap an offensive crew chains interceptions and never returns to following.", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 200f), Array.Empty())); DefensiveLeash = ((BaseUnityPlugin)this).Config.Bind("Combat", "DefensiveLeash", 15f, new ConfigDescription("Defensive only: how far from YOU a fight may travel before the crew is called off it. Measured from the player, since the point is how far you have been left alone.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 60f), Array.Empty())); GuardTheOwner = ((BaseUnityPlugin)this).Config.Bind("Combat", "GuardTheOwner", true, "While you are standing inside a guarded area, the crew forms a ring around YOU rather than around the spot on the ground, and watches for intruders near you. Step outside the area and they go back to patrolling the post - a formation around nobody is just standing still."); GuardPerimeterRadius = ((BaseUnityPlugin)this).Config.Bind("Combat", "GuardPerimeterRadius", 4f, new ConfigDescription("How far out the ring sits when screening you. Each summon holds its own arc, fixed for the order, so they keep a shape instead of trading places.", (AcceptableValueBase)(object)new AcceptableValueRange(1.5f, 20f), Array.Empty())); SummonHealthCostMultiplier = ((BaseUnityPlugin)this).Config.Bind("Combat", "SummonHealthCostMultiplier", 0.5f, new ConfigDescription("Multiplies the HEALTH cost of raising a summon - the price that actually limits how freely a crew can be replaced, since part of it is taken from your CURRENT health and so bites hardest exactly when you are replacing losses. 0.5 is half price, 1 disables this, above 1 makes summoning dearer.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); SummonEitrCostMultiplier = ((BaseUnityPlugin)this).Config.Bind("Combat", "SummonEitrCostMultiplier", 0.75f, new ConfigDescription("Multiplies the Eitr cost of raising a summon. A gentler cut than the health one by default, since Eitr regenerates on its own. 1 disables this.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); SummonCapBonusAtLevel = ((BaseUnityPlugin)this).Config.Bind("Combat", "SummonCapBonusAtLevel", 4, new ConfigDescription("The raiser level at which the extra crew is earned. Below this the vanilla cap stands exactly as the game wrote it - the staff's upgrade level - so a partly upgraded Dead Raiser fields what it should rather than collecting the bonus for free. 1 restores the old flat behaviour.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), Array.Empty())); SummonCapBonus = ((BaseUnityPlugin)this).Config.Bind("Combat", "SummonCapBonus", 2, new ConfigDescription("Extra summons allowed beyond vanilla's cap. Vanilla's is the staff's upgrade level verbatim - a fully upgraded Dead Raiser allows four - so the default of 2 makes that six. Flat rather than scaled, so the number is predictable at every upgrade level. 0 restores vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 10), Array.Empty())); ReplaceWeakestSummon = ((BaseUnityPlugin)this).Config.Bind("Combat", "ReplaceWeakestSummon", true, "At the summon cap, raising another dismisses the summon with the LOWEST health instead of vanilla's pick. Raising a replacement mid-fight is exactly when you want the half-dead one to go, and losing a healthy one instead means the crew is weaker for the trouble. The cap itself is unchanged."); SmartWeaponChoice = ((BaseUnityPlugin)this).Config.Bind("Combat", "SmartWeaponChoice", true, "Let an ADAPTIVE summon swap between melee and ranged mid-fight, weighing ammo, line of sight, whether something is already on top of it, whether you are standing in the firing line, and distance with hysteresis so it cannot dither. Applies only to summons actually carrying both kinds - a Melee or Ranged summon has nothing to choose between and is never touched, so this cannot make a fixed loadout start swapping. Off makes even Adaptive summons fall back to vanilla's choice, which is a RANDOM pick among whatever is in range, re-rolled on a timer."); MeleeSwitchDistance = ((BaseUnityPlugin)this).Config.Bind("Combat", "MeleeSwitchDistance", 6f, new ConfigDescription("Inside this, always draw a melee weapon. Must stay well below RangedSwitchDistance: the gap between the two is the dead band that stops them flip-flopping.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 20f), Array.Empty())); RangedSwitchDistance = ((BaseUnityPlugin)this).Config.Bind("Combat", "RangedSwitchDistance", 10f, new ConfigDescription("Beyond this, switch to a bow. Between the two distances whatever is already in hand is kept, which is what makes the choice stable rather than twitchy.", (AcceptableValueBase)(object)new AcceptableValueRange(4f, 40f), Array.Empty())); WeaponSwapSeconds = ((BaseUnityPlugin)this).Config.Bind("Combat", "WeaponSwapSeconds", 1.5f, new ConfigDescription("Shortest gap between weapon changes. A floor on how often we act, so our choice and vanilla's random re-roll cannot take turns and make the summon fidget.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 10f), Array.Empty())); FriendlyFireConeDegrees = ((BaseUnityPlugin)this).Config.Bind("Combat", "FriendlyFireConeDegrees", 12f, new ConfigDescription("How wide the 'someone is in the way' check is. A cone rather than a line because arrows travel and shooters lead their target, so an exact line protects nobody. 0 disables the check entirely - they will shoot through you.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); ArchersKeepDistance = ((BaseUnityPlugin)this).Config.Bind("Combat", "ArchersKeepDistance", true, "While holding a bow, use vanilla's own target-circling to keep the summon at range instead of walking into melee. Restored to the prefab behaviour the moment it draws a melee weapon."); KiteDistance = ((BaseUnityPlugin)this).Config.Bind("Combat", "KiteDistance", 12f, new ConfigDescription("How far a bow-armed summon tries to stay from its target.", (AcceptableValueBase)(object)new AcceptableValueRange(4f, 40f), Array.Empty())); KiteInterval = ((BaseUnityPlugin)this).Config.Bind("Combat", "KiteIntervalSeconds", 3f, new ConfigDescription("How often it repositions while holding a bow.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 30f), Array.Empty())); KiteDuration = ((BaseUnityPlugin)this).Config.Bind("Combat", "KiteDurationSeconds", 3f, new ConfigDescription("How long each reposition lasts.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 30f), Array.Empty())); DefendInterruptsWork = ((BaseUnityPlugin)this).Config.Bind("Combat", "DefendInterruptsWork", true, "Drop harvesting to fight when you or the summon is threatened. Work is suspended, not cancelled, so it resumes afterwards."); DefendRadius = ((BaseUnityPlugin)this).Config.Bind("Combat", "DefendRadius", 20f, new ConfigDescription("How close a hostile must be to you or the summon to count as a threat worth abandoning work for.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 60f), Array.Empty())); WorkResumeSeconds = ((BaseUnityPlugin)this).Config.Bind("Combat", "WorkResumeSeconds", 8f, new ConfigDescription("How long after the last threat before suspended work resumes.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 60f), Array.Empty())); AttackHeight = ((BaseUnityPlugin)this).Config.Bind("Combat", "AttackHeight", 0.9f, new ConfigDescription("Height the swing sweeps at. Vanilla is 1.2, which passes over a blob entirely. Lowering the ORIGIN is the safe way to reach short targets - widening the sweep instead makes it miss everything (see AttackRayWidth). Only ever lowered, never raised.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 2f), Array.Empty())); AttackCharExtra = ((BaseUnityPlugin)this).Config.Bind("Combat", "AttackCharExtraWidth", 0.15f, new ConfigDescription("Extra hit width against creatures, added to AttackRayWidth. Counts toward the overlap limit described there, so keep the total under about 0.8.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); ElementalResistAtMaxSkill = ((BaseUnityPlugin)this).Config.Bind("Combat", "ElementalResistAtMaxSkill", 0.5f, new ConfigDescription("Fraction of fire/frost/lightning/poison/spirit damage ignored at Blood Magic 100, scaling linearly from 0 at skill 0. Physical damage is untouched - resisting the elements is thematic, blanket reduction would just make them tanky. 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.95f), Array.Empty())); ShieldAbsorbBonusAtMaxSkill = ((BaseUnityPlugin)this).Config.Bind("Combat", "ShieldAbsorbBonusAtMaxSkill", 1f, new ConfigDescription("Extra damage the protection staff's shield absorbs at skill 100, scaling linearly from 0 at skill 0. 1 doubles it at 100 and adds 50% at 50. Vanilla ships the mechanism for this but never feeds it - the staff applies its effect with a hardcoded skill level of zero, so the shield is always its flat base. 0 restores that behaviour.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); PhysicalResistAtMaxSkill = ((BaseUnityPlugin)this).Config.Bind("Combat", "PhysicalResistAtMaxSkill", 0.3f, new ConfigDescription("Fraction of blunt/slash/pierce damage ignored at Blood Magic 100, scaling linearly from 0 at skill 0. Deliberately lower than the elemental ceiling: this is the one that decides whether a crew survives being swarmed, and it is easy to make them unkillable with it. Vanilla's 'true' damage is never reduced. 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.95f), Array.Empty())); DamageBonusAtMaxSkill = ((BaseUnityPlugin)this).Config.Bind("Combat", "DamageBonusAtMaxSkill", 0.5f, new ConfigDescription("Extra damage a summon DEALS at Blood Magic 100, scaling linearly from 0 at skill 0. 0.5 is +50% at 100 and +25% at 50. Chop and pickaxe damage are excluded, so this stays a combat setting rather than a harvesting-speed one. 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); HealthBloodMagicBonus = ((BaseUnityPlugin)this).Config.Bind("Combat", "HealthBloodMagicBonus", 1f, new ConfigDescription("Extra maximum health at Blood Magic 100, as a fraction of the summon's normal health, scaling linearly from 0 at skill 0. 0.5 means +50% at 100 and +25% at 50. Stacks on top of the star level vanilla already grants by skill, so keep it modest - this is meant to stop a boss one-shotting the crew, not to make them tanks. 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); RegenBloodMagicBonus = ((BaseUnityPlugin)this).Config.Bind("Regen", "BloodMagicBonusPercent", 2f, new ConfigDescription("Extra regen at Blood Magic 100, added to the base rate and scaled linearly by skill. With the defaults: 1%/s at skill 0 rising to 3%/s at 100. 0 disables scaling.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 20f), Array.Empty())); RegenOutOfCombatSeconds = ((BaseUnityPlugin)this).Config.Bind("Regen", "OutOfCombatSeconds", 15f, new ConfigDescription("Seconds a summon must be out of combat - no target and no damage taken - before regen begins.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 120f), Array.Empty())); ShowCrewHud = ((BaseUnityPlugin)this).Config.Bind("Hud", "ShowCrewHud", true, "Show a panel listing each summon: the icon of the weapon it carries, its health, and what it is currently doing. The crew's stance is the header."); CrewHudX = ((BaseUnityPlugin)this).Config.Bind("Hud", "OffsetX", 20f, new ConfigDescription("Distance from the left edge, in pixels.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2000f), Array.Empty())); CrewHudY = ((BaseUnityPlugin)this).Config.Bind("Hud", "OffsetY", 220f, new ConfigDescription("Distance down from the top edge, in pixels.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1200f), Array.Empty())); CrewHudScale = ((BaseUnityPlugin)this).Config.Bind("Hud", "Scale", 1f, new ConfigDescription("Overall size of the panel.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 2f), Array.Empty())); ShowSummonGlow = ((BaseUnityPlugin)this).Config.Bind("Hud", "ShowSummonGlow", true, "Put a light on each of your summons so they can be told from everything else in a fight. The summon currently selected with the order-target key glows brighter and in its own colour, which puts that selection in the world rather than only in the HUD. Client-side and invisible to everyone else - it answers 'which of these are mine'."); GlowColour = ((BaseUnityPlugin)this).Config.Bind("Hud", "GlowColour", "#66CCFF", "Colour of the crew's glow, as HTML hex."); GlowSelectedColour = ((BaseUnityPlugin)this).Config.Bind("Hud", "GlowSelectedColour", "#FFD479", "Colour of the summon your orders are currently aimed at. Matches the HUD's marker."); GlowIntensity = ((BaseUnityPlugin)this).Config.Bind("Hud", "GlowIntensity", 1.2f, new ConfigDescription("Brightness of the glow. The selected summon is drawn brighter still, so the two are distinguishable without relying on telling the colours apart.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 8f), Array.Empty())); GlowRange = ((BaseUnityPlugin)this).Config.Bind("Hud", "GlowRange", 3f, new ConfigDescription("How far the glow reaches, in metres. Larger lights the ground around them too.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 20f), Array.Empty())); GlowHeight = ((BaseUnityPlugin)this).Config.Bind("Hud", "GlowHeight", 1.2f, new ConfigDescription("Height of the light above the summon's feet. Roughly chest height reads as an aura; at zero it becomes a puddle on the floor.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 4f), Array.Empty())); MarkerEffectPrefab = ((BaseUnityPlugin)this).Config.Bind("Hud", "MarkerEffectPrefab", string.Empty, "Optional: the name of a vanilla effect prefab to attach to each summon as well as the light - a wisp or similar. Run skcrew_prefabs in the console to list candidates. Anything carrying a ZNetView is refused, because instantiating one would spawn a networked object every client sees and the world keeps. Empty uses the light alone."); ShowRegenText = ((BaseUnityPlugin)this).Config.Bind("Regen", "ShowRegenText", true, "Float a green '+N' over a summon as it mends, using the game's own damage-number system. Batched rather than shown per tick, which would be an unreadable stream of '+0'."); RegenTextInterval = ((BaseUnityPlugin)this).Config.Bind("Regen", "TextIntervalSeconds", 2f, new ConfigDescription("Shortest gap between regen numbers on the same summon.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 30f), Array.Empty())); RegenTextMinimum = ((BaseUnityPlugin)this).Config.Bind("Regen", "TextMinimum", 1f, new ConfigDescription("Least healing worth showing. Below this the number keeps accruing instead, so a slow regen produces occasional real numbers rather than a permanent '+0'.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 200f), Array.Empty())); DeepTrace = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "DeepTrace", true, "Writes one line per summon every few seconds saying everything at once: role, class, intent, destination, target and range to it, health, incoming damage, lava and hazard state, whether a ward is up, whether it is following, alerted, and what it is holding. The per-transition trace says what CHANGED; this says what is TRUE, which is the question you actually have when a summon is standing there doing nothing."); DeepTraceInterval = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "DeepTraceInterval", 5f, new ConfigDescription("Seconds between crew state dumps. At 5 a session is a few hundred readable lines; below about 2 it becomes the thing it was written to replace.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 60f), Array.Empty())); TraceOrders = ((BaseUnityPlugin)this).Config.Bind("General", "TraceOrders", false, "Developer logging: what a move order is doing, twice a second per summon - distance, follow target, patrol point, velocity - plus every change of intent. A working and a broken order look identical otherwise, which makes intermittent faults impossible to chase, but it is hundreds of lines a minute during a move order. On only while chasing one."); SummonPrefabPatterns = ((BaseUnityPlugin)this).Config.Bind("General", "SummonPrefabPatterns", "Skeleton,Draugr,Bonemass_Summon", "Extra prefab-name fragments treated as commandable summons, on top of anything with an unsummon distance. Comma separated, case-insensitive. Watch the log for 'SKIPPED tamed ...' lines naming a creature that should be obeying you."); EnableVanillaCommands = ((BaseUnityPlugin)this).Config.Bind("Commands", "EnableVanillaCommandInteraction", true, "Vanilla ships summons with commanding disabled. Enabling it restores the stock 'press E to toggle follow/stay' interaction, with vanilla's own persistence."); AutoFollowOnSummon = ((BaseUnityPlugin)this).Config.Bind("Commands", "AutoFollowOnSummon", true, "Set new summons to follow you the moment they appear. Enabling vanilla commanding hands follow state to vanilla, whose default is 'stay' - without this, freshly summoned skeletons just stand there, which vanilla did not do."); ShowOrderMessages = ((BaseUnityPlugin)this).Config.Bind("Commands", "ShowOrderMessages", true, "Show a brief on-screen confirmation when an order is issued."); CommandRadius = ((BaseUnityPlugin)this).Config.Bind("Commands", "CommandRadius", 0f, new ConfigDescription("Metres within which summons obey an order. 0 = no limit, which is the default: a cut-off means the summon that wandered off is also the one you cannot recall.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 200f), Array.Empty())); TargetAssistRadius = ((BaseUnityPlugin)this).Config.Bind("Targeting", "AimAssistRadius", 0.6f, new ConfigDescription("Thickness of the aim check, in metres. A plain ray demands pixel-perfect aim at a trunk or node; 0 restores that.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); TargetRange = ((BaseUnityPlugin)this).Config.Bind("Commands", "TargetRange", 50f, new ConfigDescription("How far the attack order looks for a target under the crosshair.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 200f), Array.Empty())); SpeedMultiplier = ((BaseUnityPlugin)this).Config.Bind("Movement", "SpeedMultiplier", 1.25f, new ConfigDescription("Multiplies summon walk/run/turn speed. 1 = vanilla. Raising this helps them keep pace and round corners.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 4f), Array.Empty())); FormUpWhenIdle = ((BaseUnityPlugin)this).Config.Bind("Movement", "FormUpWhenIdle", true, "When you stop moving, the crew spreads into a ring around you instead of bunching on your back. Same shape as a guard order: two hold a line, three a triangle, four a square. They break formation the moment you move or something needs fighting."); FollowFormationRadius = ((BaseUnityPlugin)this).Config.Bind("Movement", "FollowFormationRadius", 3f, new ConfigDescription("How far out that ring sits. Tighter than a guard perimeter, since this is an escort standing around you rather than a picket line.", (AcceptableValueBase)(object)new AcceptableValueRange(1.5f, 15f), Array.Empty())); FormUpDelaySeconds = ((BaseUnityPlugin)this).Config.Bind("Movement", "FormUpDelaySeconds", 2f, new ConfigDescription("How long you must be still before they form up. Without a delay they would try to reposition during every pause in your movement.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 15f), Array.Empty())); FollowDriveDistance = ((BaseUnityPlugin)this).Config.Bind("Movement", "FollowDriveDistance", 8f, new ConfigDescription("How far behind you a summon may drift before we steer it back ourselves instead of leaving it to vanilla's tamed follow. Below this, vanilla keeps its natural spacing. Raise it and they will lag further before catching up; lower it and they stick to you more rigidly.", (AcceptableValueBase)(object)new AcceptableValueRange(3f, 40f), Array.Empty())); MatchPlayerRunSpeed = ((BaseUnityPlugin)this).Config.Bind("Movement", "MatchPlayerRunSpeed", true, "Raise summon run speed to match YOUR current effective run speed, so they can close a gap instead of trailing. Acts as a floor - SpeedMultiplier still applies and wins if it is higher. Your speed moves with Run skill, stamina and gear, which a fixed multiplier cannot track."); CatchUpMargin = ((BaseUnityPlugin)this).Config.Bind("Movement", "CatchUpMargin", 1.05f, new ConfigDescription("How much faster than you they are allowed to be while catching up. Keep it small: meaningfully faster and they shove you around and overshoot corners.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 1.5f), Array.Empty())); UseCustomAgent = ((BaseUnityPlugin)this).Config.Bind("Movement", "UseCustomNavmeshAgent", true, "Give summons their own pathfinding agent with a raised step height, instead of the vanilla humanoid one that cannot climb a 30cm ledge. Only summons are affected; every other creature keeps vanilla pathing."); SummonAgentClimb = ((BaseUnityPlugin)this).Config.Bind("Movement", "SummonAgentClimb", 0.6f, new ConfigDescription("Step height, in metres, the summon navmesh treats as walkable. Vanilla humanoid is 0.3. Higher lets them reach places you may not expect.", (AcceptableValueBase)(object)new AcceptableValueRange(0.3f, 2f), Array.Empty())); AutoJumpInterval = ((BaseUnityPlugin)this).Config.Bind("Movement", "AutoJumpInterval", 0f, new ConfigDescription("Seconds between vanilla auto-jumps. 0 keeps the prefab's own value (normally off). Only worth raising if the navmesh agent is disabled.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); DriveOrderedMovement = ((BaseUnityPlugin)this).Config.Bind("Movement", "DriveOrderedMovement", true, "Steer summons directly while they are under a move or guard order, instead of setting a destination and hoping vanilla honours it. Tracing showed vanilla's idle behaviour steering them away from a correctly-set goal."); RunOnOrders = ((BaseUnityPlugin)this).Config.Bind("Movement", "RunOnOrders", true, "Sprint to an ordered destination. Vanilla walks to patrol points, so an ordered summon plodded while the same creature sprinted to follow you."); StepAssistEnabled = ((BaseUnityPlugin)this).Config.Bind("Movement", "StepAssist", false, "Make a summon jump when it is trying to move but has actually stopped. Rarely needed now the navmesh agent can climb; enable only if they still snag on geometry."); StepAssistStallSeconds = ((BaseUnityPlugin)this).Config.Bind("Movement", "StepAssistStallSeconds", 0.5f, new ConfigDescription("How long a summon must be stalled while trying to move before it jumps.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())); StepAssistMinDistance = ((BaseUnityPlugin)this).Config.Bind("Movement", "StepAssistMinDistance", 5f, new ConfigDescription("A summon must be at least this far from where it is heading before a jump is considered. Below this it is simply idling near you, not obstructed.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 30f), Array.Empty())); StepAssistCooldown = ((BaseUnityPlugin)this).Config.Bind("Movement", "StepAssistCooldown", 1f, new ConfigDescription("Minimum seconds between assisted jumps, so an unreachable spot does not turn into a pogo stick.", (AcceptableValueBase)(object)new AcceptableValueRange(0.2f, 10f), Array.Empty())); ShowOnMap = ((BaseUnityPlugin)this).Config.Bind("Map", "ShowOnMap", true, "Show your summons on the map and minimap."); ShowNamesOnMap = ((BaseUnityPlugin)this).Config.Bind("Map", "ShowNames", false, "Label each pin with the summon's name. Off by default - a pile of names around your own position is more clutter than information."); MapPinType = ((BaseUnityPlugin)this).Config.Bind("Map", "PinType", (PinType)7, "Icon used for summons. Distinctive options: Shout, Boss, Ping, Death, EventArea. Icon0-4 are the plain dots used for your own pins."); MapPinDoubleSize = ((BaseUnityPlugin)this).Config.Bind("Map", "DoubleSize", true, "Draw summon pins at double size. Valheim offers no pin tint, so size and animation are how a marker is made to stand out."); MapPinAnimate = ((BaseUnityPlugin)this).Config.Bind("Map", "Animate", true, "Pulse the summon pins so moving crew is easy to pick out at a glance."); RecallEnabled = ((BaseUnityPlugin)this).Config.Bind("Recall", "Enabled", true, "Bring followers to you when they fall too far behind, get stuck, or approach the distance at which the game despawns them."); RecallGraceSeconds = ((BaseUnityPlugin)this).Config.Bind("Recall", "GraceAfterOrderSeconds", 25f, new ConfigDescription("After you order them back, how long the automatic teleport-recall holds off so they can walk. Only the despawn-range safety overrides it.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 120f), Array.Empty())); TeleportSummons = ((BaseUnityPlugin)this).Config.Bind("Recall", "TeleportSummons", true, "Bring summons through portals with you. Without this vanilla deletes any left behind, since a portal hop instantly exceeds their 150m unsummon distance."); TeleportGuardSeconds = ((BaseUnityPlugin)this).Config.Bind("Recall", "TeleportGuardSeconds", 20f, new ConfigDescription("How long the distance-based unsummon is suppressed around a teleport. Mid-hop the owner is enormously far from the summon whichever end it sits at, and vanilla would delete it.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 120f), Array.Empty())); MaxTeleportSummons = ((BaseUnityPlugin)this).Config.Bind("Recall", "MaxTeleportSummons", 10, new ConfigDescription("Safety cap on how many summons are carried through a single teleport.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), Array.Empty())); EscortRadius = ((BaseUnityPlugin)this).Config.Bind("Escort", "Radius", 20f, new ConfigDescription("How far around YOU to look for threats. Vanilla summons only react to their own small alert range, which is why they ignore things attacking you.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 60f), Array.Empty())); RecallDistance = ((BaseUnityPlugin)this).Config.Bind("Recall", "MaxFollowDistance", 60f, new ConfigDescription("Recall a follower once it is this far away.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 140f), Array.Empty())); RecallStuckDistance = ((BaseUnityPlugin)this).Config.Bind("Recall", "StuckMinDistance", 15f, new ConfigDescription("Only consider a follower stuck once it is at least this far away, so it is not yanked around at your heels.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 50f), Array.Empty())); RecallElevation = ((BaseUnityPlugin)this).Config.Bind("Recall", "ElevationGap", 4f, new ConfigDescription("Vertical gap that counts as unreachable, judged separately from distance. Up a tower you are only a few metres away, so neither the too-far nor the stuck rule fires and the crew waits at the bottom indefinitely. They are still only brought up once they have stopped making progress, so a summon climbing the stairs behind you is left to finish.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 30f), Array.Empty())); RecallElevationSeconds = ((BaseUnityPlugin)this).Config.Bind("Recall", "ElevationSeconds", 3f, new ConfigDescription("How long a summon must fail to close a vertical gap before it is brought up. Shorter than the normal stuck delay: on a tower there is usually no route at all, so waiting the full window is time spent staring at an empty stairwell.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 30f), Array.Empty())); RecallStrandedDistance = ((BaseUnityPlugin)this).Config.Bind("Recall", "StrandedDistance", 150f, new ConfigDescription("Beyond this many metres a summon is brought back regardless of orders, guard posts or an ongoing fight - none of which it can act on from that far away. Defaults to the vanilla unsummon distance, past which the game deletes it anyway.", (AcceptableValueBase)(object)new AcceptableValueRange(50f, 500f), Array.Empty())); RecallStuckSeconds = ((BaseUnityPlugin)this).Config.Bind("Recall", "StuckSeconds", 8f, new ConfigDescription("Seconds without getting any closer before a follower counts as stuck.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 30f), Array.Empty())); RecallSpread = ((BaseUnityPlugin)this).Config.Bind("Recall", "ArrivalSpread", 4f, new ConfigDescription("How far behind you recalled summons land, and how far they spread sideways. They always arrive behind, never in your path.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 15f), Array.Empty())); CommandButton = ((BaseUnityPlugin)this).Config.Bind("Targeting", "CommandButton", new KeyboardShortcut((KeyCode)96, Array.Empty()), "One tap: attack what you are looking at, or move there. Two taps: come back. Three taps: hold position and guard it. Harvest orders live in the F1 menu, where the target and radius are named before you commit to them."); MoveOrderSeconds = ((BaseUnityPlugin)this).Config.Bind("Targeting", "MoveOrderSeconds", 30f, new ConfigDescription("How long a move order keeps being re-applied before giving up, if they cannot reach the spot.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 180f), Array.Empty())); MoveSpread = ((BaseUnityPlugin)this).Config.Bind("Targeting", "MoveSpread", 2.5f, new ConfigDescription("Radius each summon is scattered within around an ordered spot. Sending several to one exact point makes them shove each other and take turns.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); MoveArriveDistance = ((BaseUnityPlugin)this).Config.Bind("Targeting", "MoveArriveDistance", 3f, new ConfigDescription("How close counts as arrived, at which point the move order stops being re-applied.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 15f), Array.Empty())); AttackOrderSeconds = ((BaseUnityPlugin)this).Config.Bind("Targeting", "AttackOrderSeconds", 20f, new ConfigDescription("How long an ordered attack is re-asserted. MonsterAI overwrites its target on a timer, so the order must be held, not set once.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 120f), Array.Empty())); MultiTapSeconds = ((BaseUnityPlugin)this).Config.Bind("Targeting", "MultiTapSeconds", 0.3f, new ConfigDescription("Maximum gap between taps to count as a multi-tap. A single tap is held for this long before firing, so raising it makes single orders feel laggy.", (AcceptableValueBase)(object)new AcceptableValueRange(0.15f, 1f), Array.Empty())); GuardPatrol = ((BaseUnityPlugin)this).Config.Bind("Targeting", "GuardPatrol", true, "Guarding summons pace a small perimeter around their post instead of standing frozen. Patrol points are always derived from the post, so they cannot drift off station."); GuardPatrolRadius = ((BaseUnityPlugin)this).Config.Bind("Targeting", "GuardPatrolRadius", 4f, new ConfigDescription("How far from its post a guard wanders while patrolling. Keep well under GuardRadius so it stays in position to intercept.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 20f), Array.Empty())); GuardPatrolInterval = ((BaseUnityPlugin)this).Config.Bind("Targeting", "GuardPatrolInterval", 8f, new ConfigDescription("Seconds before a patrolling guard picks a new spot, if it has not already arrived at the current one.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 60f), Array.Empty())); GuardRadius = ((BaseUnityPlugin)this).Config.Bind("Targeting", "GuardRadius", 15f, new ConfigDescription("How far from its post a guarding summon will engage intruders before returning.", (AcceptableValueBase)(object)new AcceptableValueRange(3f, 60f), Array.Empty())); OrdersOverrideCombat = ((BaseUnityPlugin)this).Config.Bind("Targeting", "OrdersOverrideCombat", true, "An explicit order makes summons disengage. Without this a skeleton already fighting simply ignores 'follow me' and keeps swinging."); RegroupDistance = ((BaseUnityPlugin)this).Config.Bind("Targeting", "RegroupDistance", 8f, new ConfigDescription("After 'come back', summons stay disengaged until this close to you. A time limit alone was not enough - it expired and they rejoined the fight they were told to leave.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 40f), Array.Empty())); ObeySeconds = ((BaseUnityPlugin)this).Config.Bind("Targeting", "ObeySeconds", 4f, new ConfigDescription("How long after an order a summon refuses to re-acquire a target. Clearing it once is not enough - the AI re-targets within a frame or two.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 20f), Array.Empty())); AssertEverySettingBound(); ReportStaleSettings(); _harmony = new Harmony("com.vash.skeletoncrew"); _harmony.PatchAll(typeof(SkeletonCrewPlugin).Assembly); Log.LogInfo((object)("SkeletonCrew 1.0.2 loaded. Orders: " + $"{CommandButton.Value} x1 attack/move, x2 come back, x3 hold position.")); } private void ReportStaleSettings() { List list = new List(); if (!(typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is IDictionary dictionary)) { return; } foreach (DictionaryEntry item in dictionary) { list.Add($"{item.Key} = {item.Value}"); } if (list.Count > 0) { Log.LogWarning((object)("Settings left over from an older version, still in your config file and no longer used by this build: " + string.Join(", ", list.ToArray()) + ". They are inert - delete them, or ignore them.")); } } private static void AssertEverySettingBound() { List list = new List(); PropertyInfo[] properties = typeof(SkeletonCrewPlugin).GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (typeof(ConfigEntryBase).IsAssignableFrom(propertyInfo.PropertyType) && propertyInfo.GetValue(null) == null) { list.Add(propertyInfo.Name); } } if (list.Count != 0) { Log.LogError((object)(string.Format("{0} setting(s) declared but never bound: {1}. ", list.Count, string.Join(", ", list.ToArray())) + "Anything reading one of these will throw every frame it runs. Bind them in Awake.")); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Run(string name, Action step) { try { step(); } catch (Exception arg) { _errorCount++; if (_errorCount <= 3 || Time.time - _lastErrorLog > 30f) { _lastErrorLog = Time.time; Log.LogError((object)$"{name} failed (occurrence {_errorCount}, other systems continue): {arg}"); } } } private void Update() { if (Enabled == null || !Enabled.Value) { return; } try { Run("Input", Commands.HandleInput); if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } Run("Intents", SummonRegistry.ResolveIntents); Run("Obedience", SummonRegistry.TickObedience); Run("AttackOrders", SummonRegistry.TickAttackOrders); Run("MoveOrders", SummonRegistry.TickMoveOrders); Run("StepAssist", StepAssist.Tick); Run("CrewMenu", CrewMenu.Tick); Run("CrewHud", CrewHud.Tick); Run("CrewMarker", CrewMarker.Tick); if (!(Time.time < _nextTick)) { float dt = Time.time - _lastTick; _lastTick = Time.time; _nextTick = Time.time + 0.5f; Run("ThreatScan", ThreatScan.Refresh); Run("CrewFocus", CrewFocus.Tick); Run("Hazard", Hazard.Forget); Run("Trace", CrewTrace.Tick); Run("Pressure", Pressure.Forget); Run("Registry", SummonRegistry.Tick); Run("Regen", delegate { Regen.Apply(dt); }); Run("Recall", Recall.Tick); Run("Guards", SummonRegistry.TickGuards); Run("Jobs", SummonRegistry.TickJobs); Run("Stance", Stance.TickLeash); Run("Perception", Stance.TickPerception); Run("WeaponChoice", WeaponChoice.Tick); Run("Visuals", EquipmentVisuals.Tick); Run("Healer", Healer.Tick); Run("MapPins", MapPins.Tick); } } catch (Exception arg) { _errorCount++; if (_errorCount <= 3 || Time.time - _lastErrorLog > 30f) { _lastErrorLog = Time.time; Log.LogError((object)$"Update failed (occurrence {_errorCount}, continuing): {arg}"); } } } } internal static class Progression { private static readonly Dictionary Keys = new Dictionary(); internal static bool IsUnlocked(SummonPreference pref) { string text = SkeletonCrewPlugin.ClassUnlock(pref)?.Value; if (string.IsNullOrEmpty(text)) { return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return true; } string text2 = KeyFor(text); if (!string.IsNullOrEmpty(text2)) { return localPlayer.IsKnownMaterial(text2); } return true; } internal static int UnlockedCount() { int num = 0; SummonPreference[] array = (SummonPreference[])Enum.GetValues(typeof(SummonPreference)); foreach (SummonPreference pref in array) { if (!string.IsNullOrEmpty(SkeletonCrewPlugin.ClassUnlock(pref)?.Value) && IsUnlocked(pref)) { num++; } } return num; } internal static float CrewMultiplier() { return CrewMultiplier(null); } internal static float CrewMultiplier(Character summon) { float num = ((SkeletonCrewPlugin.ProgressionBonusPerUnlock == null) ? 0f : SkeletonCrewPlugin.ProgressionBonusPerUnlock.Value); if (num <= 0f) { return 1f; } return 1f + num * (float)UnlockedCount() * Devotion.Factor(summon); } private static string KeyFor(string prefab) { if (Keys.TryGetValue(prefab, out var value)) { return value; } string text = string.Empty; ObjectDB instance = ObjectDB.instance; GameObject obj = ((instance != null) ? instance.GetItemPrefab(prefab) : null); SharedData val = ((obj == null) ? null : obj.GetComponent()?.m_itemData?.m_shared); if (val != null) { text = val.m_name; } else { if ((Object)(object)ObjectDB.instance == (Object)null) { return string.Empty; } SkeletonCrewPlugin.Log.LogWarning((object)("Unlock item '" + prefab + "' is not in ObjectDB - that class will stay locked. Check the prefab name with skcrew_weapons.")); } Keys[prefab] = text; if (!string.IsNullOrEmpty(text)) { SkeletonCrewPlugin.Log.LogInfo((object)("Unlock item '" + prefab + "' resolves to '" + text + "'.")); } return text; } } internal static class Recall { private sealed class Progress { internal float BestDistance = float.MaxValue; internal float LastImprovedTime; } private static readonly Dictionary Tracked = new Dictionary(); internal static void Reset() { Tracked.Clear(); } internal static void Tick() { //IL_008f: 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_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0160: 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_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.RecallEnabled.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsTeleporting()) { return; } float value = SkeletonCrewPlugin.RecallDistance.Value; float value2 = SkeletonCrewPlugin.RecallStuckSeconds.Value; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { Character character = item.Character; if ((Object)(object)character == (Object)null || character.IsDead() || (Object)(object)item.AI == (Object)null) { continue; } float num = Vector3.Distance(((Component)character).transform.position, ((Component)localPlayer).transform.position); if (num > SkeletonCrewPlugin.RecallStrandedDistance.Value) { Bring(item, localPlayer, "stranded", num); Tracked.Remove(item.Id); continue; } if ((Object)(object)item.AI.GetFollowTarget() == (Object)null) { Tracked.Remove(item.Id); continue; } if (SummonRegistry.HasStandingOrder(item.Id)) { Tracked.Remove(item.Id); continue; } if ((Object)(object)((BaseAI)item.AI).GetTargetCreature() != (Object)null) { Tracked.Remove(item.Id); continue; } float num2 = num; if (!Tracked.TryGetValue(item.Id, out var value3)) { value3 = new Progress { BestDistance = num2, LastImprovedTime = Time.time }; Tracked[item.Id] = value3; } if (num2 < value3.BestDistance - 1f) { value3.BestDistance = num2; value3.LastImprovedTime = Time.time; } bool flag = (Object)(object)item.Tameable != (Object)null && item.Tameable.m_unsummonDistance > 0f && num2 > item.Tameable.m_unsummonDistance * 0.6f; if (flag || !SummonRegistry.HasRecallGrace(item.Id)) { bool flag2 = num2 > value; bool flag3 = num2 > SkeletonCrewPlugin.RecallStuckDistance.Value && Time.time - value3.LastImprovedTime > value2; float num3 = Mathf.Abs(((Component)character).transform.position.y - ((Component)localPlayer).transform.position.y); bool flag4 = num3 > SkeletonCrewPlugin.RecallElevation.Value && Time.time - value3.LastImprovedTime > SkeletonCrewPlugin.RecallElevationSeconds.Value; if (flag2 || flag3 || flag || flag4) { string reason = (flag ? "near despawn range" : (flag2 ? "too far" : (flag4 ? $"{num3:F0}m below/above and not climbing" : "stuck"))); Bring(item, localPlayer, reason, num2); Tracked.Remove(item.Id); } } } } internal static void Bring(SummonRegistry.Summon s, Player player, string reason, float fromDistance) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) BringTo(s, ((Component)player).transform.position, player, reason, fromDistance); } internal static void BringTo(SummonRegistry.Summon s, Vector3 destination, Player player, string reason, float fromDistance) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) Character character = s.Character; ZNetView nview = character.m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid()) { if (!nview.IsOwner()) { nview.ClaimOwnership(); } Vector3 position = ScatterBehind(destination, player); if ((Object)(object)character.m_body != (Object)null) { character.m_body.linearVelocity = Vector3.zero; character.m_body.angularVelocity = Vector3.zero; } ((Component)character).transform.position = position; nview.GetZDO().SetPosition(position); ((BaseAI)s.AI).ResetPatrolPoint(); s.AI.SetFollowTarget(((Component)player).gameObject); if (SkeletonCrewPlugin.VerboseLogging.Value) { SkeletonCrewPlugin.Log.LogInfo((object)$"Recalled '{character.GetHoverName()}' ({reason}, was {fromDistance:F0}m)."); } } } private static Vector3 ScatterBehind(Vector3 centre, Player player) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_004a: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) float value = SkeletonCrewPlugin.RecallSpread.Value; Vector3 val = (((Object)(object)player != (Object)null) ? ((Component)player).transform.forward : Vector3.forward); val.y = 0f; val = ((((Vector3)(ref val)).sqrMagnitude < 0.01f) ? Vector3.forward : ((Vector3)(ref val)).normalized); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(val.z, 0f, 0f - val.x); Vector3 val3 = centre - val * Random.Range(1.5f, Mathf.Max(2f, value)) + val2 * Random.Range((0f - value) * 0.5f, value * 0.5f); float y = default(float); if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.FindFloor(val3 + Vector3.up * 2f, ref y)) { val3.y = y; } return val3; } } internal static class Regen { internal static float CurrentRegenPercent() { float value = SkeletonCrewPlugin.RegenPercentPerSecond.Value; float value2 = SkeletonCrewPlugin.RegenBloodMagicBonus.Value; if (value2 <= 0f) { return value; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return value; } Skills skills = ((Character)localPlayer).GetSkills(); if ((Object)(object)skills == (Object)null) { return value; } float skillFactor = skills.GetSkillFactor((SkillType)10); return value + value2 * skillFactor; } internal static void Apply(float dt) { if (SkeletonCrewPlugin.RegenPercentPerSecond.Value <= 0f) { return; } float value = SkeletonCrewPlugin.RegenOutOfCombatSeconds.Value; float num = CurrentRegenPercent() / 100f; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { Character character = item.Character; if ((Object)(object)character == (Object)null || character.IsDead() || item.SinceCombat < value) { continue; } float maxHealth = character.GetMaxHealth(); float health = character.GetHealth(); if (!(health >= maxHealth)) { float num2 = Mathf.Min(maxHealth * num * dt, maxHealth - health); if (!(num2 <= 0f)) { character.Heal(num2, false); ShowRegenText(item, character, num2); } } } } private static void ShowRegenText(SummonRegistry.Summon summon, Character c, float heal) { //IL_001b: 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) if (SkeletonCrewPlugin.ShowRegenText.Value && !((Object)(object)DamageText.instance == (Object)null) && SummonRegistry.AccumulateRegen(summon.Id, heal, out var total)) { DamageText.instance.ShowText((TextType)4, c.GetTopPoint(), Mathf.Round(total), false); } } } internal enum CrewStance { Balanced, Offensive, Defensive } internal static class Stance { private static readonly Dictionary CalledOff = new Dictionary(); private const float LeashHeadroom = 1.25f; internal static CrewStance Current => SkeletonCrewPlugin.StanceSetting.Value; internal static bool OnlyAnswersAggression => Current == CrewStance.Defensive; internal static void Cycle() { CrewStance crewStance = ((Current == CrewStance.Balanced) ? CrewStance.Offensive : ((Current == CrewStance.Offensive) ? CrewStance.Defensive : CrewStance.Balanced)); SkeletonCrewPlugin.StanceSetting.Value = crewStance; SkeletonCrewPlugin.Log.LogInfo((object)$"Stance: {crewStance}."); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, "Crew stance: " + Describe(crewStance), 0, (Sprite)null, false); } } private static string Describe(CrewStance s) { return s switch { CrewStance.Offensive => "Offensive - seek out targets", CrewStance.Defensive => "Defensive - stay close, guard me", _ => "Balanced", }; } internal static float EscortRadius() { float value = SkeletonCrewPlugin.EscortRadius.Value; return Current switch { CrewStance.Offensive => value * SkeletonCrewPlugin.OffensiveReachMultiplier.Value, CrewStance.Defensive => value * SkeletonCrewPlugin.DefensiveReachMultiplier.Value, _ => value, }; } internal static void TickPerception() { SummonRegistry.ApplyPerception((Current == CrewStance.Offensive) ? SkeletonCrewPlugin.OffensiveViewMultiplier.Value : 1f); } internal static bool SuppressesVanillaAcquisition(MonsterAI ai) { //IL_003e: 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_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ai == (Object)null) { return false; } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (item.AI != ai) { continue; } if ((Object)(object)item.Character == (Object)null || SummonRegistry.HasStandingOrder(item.Id)) { return false; } if ((Object)(object)CrewFocus.Current != (Object)null && (Object)(object)item.AI != (Object)null && (Object)(object)((BaseAI)item.AI).GetTargetCreature() == (Object)(object)CrewFocus.Current) { return false; } if (SkeletonCrewPlugin.RoleFormation.Value && !SummonRole.Initiates(SummonRegistry.RoleOf(item.Id))) { if ((Object)(object)ThreatScan.AttackerOf(item.Character) != (Object)null) { return false; } float value = SkeletonCrewPlugin.BackLineEngageDistance.Value; return value > 0f && !ThreatScan.AggressorNear(((Component)item.Character).transform.position, value); } if (Current != CrewStance.Defensive) { return false; } float radius = EscortRadius(); Player localPlayer = Player.m_localPlayer; return (!((Object)(object)localPlayer != (Object)null) || !ThreatScan.AggressorNear(((Component)localPlayer).transform.position, radius)) && !ThreatScan.AggressorNear(((Component)item.Character).transform.position, radius); } return false; } private static Character LastCalledOff(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!CalledOff.TryGetValue(id, out var value)) { return null; } return value; } private static float Leash() { return Mathf.Max((Current == CrewStance.Defensive) ? SkeletonCrewPlugin.DefensiveLeash.Value : SkeletonCrewPlugin.ChaseLeash.Value, EscortRadius() * 1.25f); } internal static void TickLeash() { //IL_001c: 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_0167: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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) //IL_00f1: 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_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: 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_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } float num = Leash(); Vector3 position = ((Component)localPlayer).transform.position; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if ((Object)(object)item.AI == (Object)null || (Object)(object)item.Character == (Object)null || item.Character.IsDead()) { continue; } Character targetCreature = ((BaseAI)item.AI).GetTargetCreature(); if ((Object)(object)targetCreature != (Object)null && !item.Character.InLava() && LavaGuard.Between(((Component)item.Character).transform.position, ((Component)targetCreature).transform.position)) { SummonRegistry.Disengage(item.Id, item.AI, SkeletonCrewPlugin.ObeySeconds.Value); if (SkeletonCrewPlugin.VerboseLogging.Value && (Object)(object)LastCalledOff(item.Id) != (Object)(object)targetCreature) { CalledOff[item.Id] = targetCreature; SkeletonCrewPlugin.Log.LogInfo((object)("Stance: '" + item.Character.GetHoverName() + "' will not cross lava for '" + targetCreature.GetHoverName() + "'.")); } } else { if (SummonRegistry.HasStandingOrder(item.Id) || SummonRegistry.IsHoldingPosition(item.Tameable) || (Object)(object)targetCreature == (Object)null) { continue; } float num2 = num; if (SkeletonCrewPlugin.RoleFormation.Value && SummonRegistry.RoleOf(item.Id) == Role.Front) { num2 = Mathf.Max(num2 * SkeletonCrewPlugin.FrontLineLeashScale.Value, EscortRadius() * 1.05f); } Vector3 val = ((Component)targetCreature).transform.position - position; if (!(((Vector3)(ref val)).sqrMagnitude <= num2 * num2) && !ThreatScan.Aggressing(targetCreature)) { SummonRegistry.Disengage(item.Id, item.AI, SkeletonCrewPlugin.ObeySeconds.Value); ((BaseAI)item.AI).ResetPatrolPoint(); if (SkeletonCrewPlugin.VerboseLogging.Value && (Object)(object)LastCalledOff(item.Id) != (Object)(object)targetCreature) { CalledOff[item.Id] = targetCreature; SkeletonCrewPlugin.Log.LogInfo((object)("Stance: '" + item.Character.GetHoverName() + "' called off '" + targetCreature.GetHoverName() + "' (beyond leash).")); } } } } } } internal static class StepAssist { private sealed class Probe { internal Vector3 LastPos; internal float StalledSince = -1f; internal float LastJump = -999f; } private static readonly Dictionary Probes = new Dictionary(); internal static void Reset() { Probes.Clear(); } private static bool TryGetDestination(SummonRegistry.Summon s, out Vector3 destination) { //IL_001c: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) Character targetCreature = ((BaseAI)s.AI).GetTargetCreature(); if ((Object)(object)targetCreature != (Object)null) { destination = ((Component)targetCreature).transform.position; return true; } Vector3 val = default(Vector3); if (((BaseAI)s.AI).GetPatrolPoint(ref val)) { destination = val; return true; } GameObject followTarget = s.AI.GetFollowTarget(); if ((Object)(object)followTarget != (Object)null) { destination = followTarget.transform.position; return true; } destination = Vector3.zero; return false; } internal static void Tick() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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_0131: 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_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.StepAssistEnabled.Value) { return; } float time = Time.time; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!SummonRegistry.Usable(item)) { continue; } if (!item.Character.IsOnGround() || item.Character.IsSwimming()) { Probes.Remove(item.Id); continue; } if (!TryGetDestination(item, out var destination)) { Probes.Remove(item.Id); continue; } if (Vector3.Distance(((Component)item.Character).transform.position, destination) < SkeletonCrewPlugin.StepAssistMinDistance.Value) { Probes.Remove(item.Id); continue; } if (!Probes.TryGetValue(item.Id, out var value)) { value = new Probe { LastPos = ((Component)item.Character).transform.position }; Probes[item.Id] = value; } Vector3 position = ((Component)item.Character).transform.position; Vector3 val = position - value.LastPos; val.y = 0f; value.LastPos = position; if (!(((Vector3)(ref val)).sqrMagnitude < 0.0004f)) { value.StalledSince = -1f; } else if (value.StalledSince < 0f) { value.StalledSince = time; } else if (!(time - value.StalledSince < SkeletonCrewPlugin.StepAssistStallSeconds.Value) && !(time - value.LastJump < SkeletonCrewPlugin.StepAssistCooldown.Value)) { value.LastJump = time; value.StalledSince = -1f; item.Character.Jump(false); if (SkeletonCrewPlugin.TraceOrders.Value) { SkeletonCrewPlugin.Log.LogInfo((object)("StepAssist: '" + item.Character.GetHoverName() + "' jumped, " + $"{Vector3.Distance(((Component)item.Character).transform.position, destination):F1}m from goal.")); } } } } } internal static class SummonRegistry { private sealed class State { internal float LastHealth = -1f; internal float LastCombatTime = -999f; internal float ObeyUntil; internal Character AttackTarget; internal float AttackUntil; internal HarvestJob Job; internal Vector3 MovePoint; internal float MoveUntil; internal float JobSuspendedUntil; internal bool JobContinue; internal Vector3 AreaCentre; internal float AreaRadius; internal float JobSettleAt; internal Vector3? LastWorkPoint; internal Intent Current; internal int GuardSlot; internal int GuardSlotCount; internal int FormationSeed; internal int FollowRank; internal int FollowRankCount; internal Role Role; internal int RoleRank; internal int RoleCount; internal Vector3 LavaExit; internal float LavaExitUntil; internal float HazardSettleUntil; internal float BurningSince; internal float NextTraceLog; internal int Acquires; internal int Releases; internal string LastRelease; internal Character LastTarget; internal Vector3 RouteVia; internal Vector3 RouteTo; internal float RouteUntil; internal float RouteNext; internal bool FleeCaptured; internal float BaseFleeHealth; internal float BaseFleeSinceHurt; internal Vector3 GuardAnchor; internal float NextWeaponSwap; internal bool CirclingCaptured; internal float BaseCircleInterval; internal float BaseCircleDuration; internal float BaseCircleDistance; internal bool PerceptionCaptured; internal float VanillaView; internal float VanillaHear; internal float VanillaAlert; internal float RegenPending; internal float RegenTextAt; internal float RecallGraceUntil; internal bool Regrouping; internal bool CapturedAgent; internal AgentType OriginalAgent; internal bool CapturedJump; internal float OriginalJumpInterval; internal float BaseWalk = -1f; internal float BaseRun; internal float BaseTurn; internal float BaseRunTurn; internal bool CapturedLava; internal bool BaseAvoidLava; internal bool BaseAvoidLavaFlee; internal bool BaseSkipLavaTargets; internal bool BaseFleeInLava; } internal sealed class Summon { internal Character Character; internal Tameable Tameable; internal MonsterAI AI; internal ZDOID Id; internal float SinceCombat; } private struct AttackGeometry { internal float Height; internal float Range; internal float RayWidth; internal float CharExtra; internal float Angle; } private static Vector3 _lastOwnerPos; private static float _ownerStillSince; internal static readonly List Owned = new List(); private static readonly HashSet Reported = new HashSet(); private static readonly Dictionary States = new Dictionary(); private static int _lastReportedCrewSize = -1; private static int _nextFormationSeed; private static readonly Dictionary RoleCounts = new Dictionary(); private static int _crewCapacity; private static readonly Dictionary VanillaGeometry = new Dictionary(); private static float _nextTrace; private static readonly Dictionary Claims = new Dictionary(); private static readonly Dictionary ClaimScratch = new Dictionary(); private static readonly Dictionary Stations = new Dictionary(); private static readonly HashSet SkippedReported = new HashSet(); private static readonly Dictionary Patrols = new Dictionary(); private static readonly HashSet PrecisePosts = new HashSet(); private static bool PlayerHasSettled { get { if (_ownerStillSince > 0f) { return Time.time - _ownerStillSince >= SkeletonCrewPlugin.FormUpDelaySeconds.Value; } return false; } } private static void TrackOwnerStillness() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { _ownerStillSince = 0f; return; } Vector3 position = ((Component)localPlayer).transform.position; if (_ownerStillSince <= 0f || Vector3.Distance(position, _lastOwnerPos) > 1.5f) { _lastOwnerPos = position; _ownerStillSince = Time.time; } } internal static void ResolveIntents() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) TrackOwnerStillness(); foreach (Summon item in Owned) { if (Usable(item) && States.TryGetValue(item.Id, out var value)) { Character val = (((Object)(object)item.AI == (Object)null) ? null : ((BaseAI)item.AI).GetTargetCreature()); if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)value.LastTarget) { value.Acquires++; } value.LastTarget = val; Intent intent = Route(item, value, Resolve(item, value)); if ((intent.Activity != value.Current.Activity || intent.Source != value.Current.Source) && SkeletonCrewPlugin.TraceOrders.Value && Time.time >= value.NextTraceLog) { value.NextTraceLog = Time.time + 0.5f; SkeletonCrewPlugin.Log.LogInfo((object)$"Intent '{item.Character.GetHoverName()}': {value.Current} -> {intent}"); } value.Current = intent; } } } internal static float ArriveDistance(MonsterAI ai) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (TryFind(ai, out var found) && IntentFor(found.Id).Activity == Activity.Working) { return SkeletonCrewPlugin.HarvestReach.Value * 0.4f; } return SkeletonCrewPlugin.MoveArriveDistance.Value; } internal static Intent IntentFor(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!States.TryGetValue(id, out var value)) { return Intent.Follow; } return value.Current; } private static bool TryFind(MonsterAI ai, out Summon found) { found = null; if ((Object)(object)ai == (Object)null) { return false; } foreach (Summon item in Owned) { if (item.AI == ai) { found = item; return true; } } return false; } private static Intent Route(Summon s, State st, Intent intent) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_014b: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0173: 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_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.AvoidLava.Value || (Object)(object)s.Character == (Object)null || s.Character.InLava()) { return intent; } Vector3 position = ((Component)s.Character).transform.position; float value = SkeletonCrewPlugin.MoveArriveDistance.Value; Vector3 lastSafe; if (Time.time < st.RouteUntil) { lastSafe = position - st.RouteVia; if (((Vector3)(ref lastSafe)).sqrMagnitude > value * value) { return new Intent(intent.Activity, intent.Source, st.RouteVia, intent.Target); } st.RouteUntil = 0f; } if (Time.time < st.RouteNext) { return intent; } st.RouteNext = Time.time + 1f; Vector3? val = intent.Destination; if (!val.HasValue) { Vector3? val2 = null; if (intent.Activity == Activity.Following && (Object)(object)Player.m_localPlayer != (Object)null) { val2 = ((Component)Player.m_localPlayer).transform.position; } if (!val2.HasValue || !LavaGuard.Crosses(position, val2.Value)) { return intent; } val = val2; } Vector3 value2 = val.Value; if (Time.time < st.LavaExitUntil) { return intent; } AgentType agent = (AgentType)(((Object)(object)s.AI == (Object)null) ? 13 : ((int)((BaseAI)s.AI).m_pathAgentType)); if (LavaGuard.PathIsClear(position, value2, agent, out var lastSafe2)) { return intent; } if (LavaGuard.TryRoute(position, value2, out var waypoint)) { lastSafe = waypoint - value2; if (!(((Vector3)(ref lastSafe)).sqrMagnitude <= 1f) && LavaGuard.PathIsClear(position, waypoint, agent, out lastSafe)) { st.RouteVia = waypoint; st.RouteTo = value2; st.RouteUntil = Time.time + 15f; if (SkeletonCrewPlugin.TraceOrders.Value) { SkeletonCrewPlugin.Log.LogInfo((object)("Route '" + s.Character.GetHoverName() + "': going round the lava via " + $"{waypoint.x:F0},{waypoint.z:F0} rather than straight across.")); } return new Intent(intent.Activity, intent.Source, waypoint, intent.Target); } } lastSafe = lastSafe2 - position; Vector3 value3 = (st.RouteVia = ((((Vector3)(ref lastSafe)).sqrMagnitude > 1f) ? lastSafe2 : LavaGuard.StopShortOf(position, value2))); st.RouteTo = value2; st.RouteUntil = Time.time + 3f; if (SkeletonCrewPlugin.TraceOrders.Value) { SkeletonCrewPlugin.Log.LogInfo((object)("Route '" + s.Character.GetHoverName() + "': no way round the lava - waiting at the edge.")); } return new Intent(intent.Activity, intent.Source, value3, intent.Target); } private static Intent Resolve(Summon s, State st) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0177: 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) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_042d: Unknown result type (might be due to invalid IL or missing references) //IL_0432: Unknown result type (might be due to invalid IL or missing references) //IL_0433: Unknown result type (might be due to invalid IL or missing references) //IL_0438: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_03db: Unknown result type (might be due to invalid IL or missing references) //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03ae: Unknown result type (might be due to invalid IL or missing references) //IL_03b3: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: 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_0312: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Unknown result type (might be due to invalid IL or missing references) //IL_04c4: Unknown result type (might be due to invalid IL or missing references) //IL_04e0: Unknown result type (might be due to invalid IL or missing references) //IL_04e7: Unknown result type (might be due to invalid IL or missing references) //IL_0505: Unknown result type (might be due to invalid IL or missing references) //IL_0530: Unknown result type (might be due to invalid IL or missing references) //IL_053f: Unknown result type (might be due to invalid IL or missing references) //IL_0544: Unknown result type (might be due to invalid IL or missing references) //IL_0552: Unknown result type (might be due to invalid IL or missing references) //IL_0553: Unknown result type (might be due to invalid IL or missing references) //IL_0555: Unknown result type (might be due to invalid IL or missing references) //IL_055a: Unknown result type (might be due to invalid IL or missing references) //IL_0596: Unknown result type (might be due to invalid IL or missing references) //IL_05a5: Unknown result type (might be due to invalid IL or missing references) //IL_056c: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; Vector3 position = ((Component)s.Character).transform.position; if (s.Character.InLava() || Hazard.Hurting(s.Character)) { if (st.BurningSince <= 0f) { st.BurningSince = Time.time; if (SkeletonCrewPlugin.VerboseLogging.Value) { SkeletonCrewPlugin.Log.LogInfo((object)("'" + s.Character.GetHoverName() + "' is in lava - " + $"{SkeletonCrewPlugin.LavaRescueSeconds.Value:0.#}s to get clear.")); } } else if ((Object)(object)localPlayer != (Object)null && Time.time - st.BurningSince > SkeletonCrewPlugin.LavaRescueSeconds.Value) { Vector3 safe; Vector3 destination = (LavaGuard.TryFindSafeGround(position, out safe) ? safe : ((Component)localPlayer).transform.position); st.BurningSince = 0f; st.LavaExitUntil = 0f; Recall.BringTo(s, destination, localPlayer, "burning and out of time", 0f); return new Intent(Activity.Following, IntentSource.Rescue); } } else { st.BurningSince = 0f; } if ((!(Time.time < st.HazardSettleUntil) || Hazard.Hurting(s.Character)) && LavaGuard.TryEscape(s.Character, localPlayer, ref st.LavaExit, ref st.LavaExitUntil, out var destination2)) { st.HazardSettleUntil = Time.time + SkeletonCrewPlugin.HazardSettleSeconds.Value; return new Intent(Activity.MovingTo, IntentSource.Rescue, destination2); } if ((Object)(object)localPlayer != (Object)null && Vector3.Distance(position, ((Component)localPlayer).transform.position) > SkeletonCrewPlugin.RecallStrandedDistance.Value) { return new Intent(Activity.Regrouping, IntentSource.Rescue, ((Component)localPlayer).transform.position); } if (Healer.ShouldClose(s, localPlayer, out var destination3)) { return new Intent(Activity.MovingTo, IntentSource.Rescue, destination3); } if ((Object)(object)st.AttackTarget != (Object)null && Time.time < st.AttackUntil) { Character attackTarget = st.AttackTarget; return new Intent(Activity.Attacking, IntentSource.PlayerOrder, null, attackTarget); } if (st.MoveUntil > 0f) { return new Intent(Activity.MovingTo, IntentSource.PlayerOrder, st.MovePoint); } if (st.Regrouping && (Object)(object)localPlayer != (Object)null) { return new Intent(Activity.Regrouping, IntentSource.PlayerOrder, ((Component)localPlayer).transform.position); } if (Stations.TryGetValue(s.Id, out var value)) { Character val = (((Object)(object)s.AI == (Object)null) ? null : ((BaseAI)s.AI).GetTargetCreature()); float value2 = SkeletonCrewPlugin.GuardRadius.Value; Vector3 val2 = GuardCentre(s, value); if ((Object)(object)val == (Object)null || Vector3.Distance(((Component)val).transform.position, val2) > value2) { val = ThreatScan.Nearest(val2, value2); } if ((Object)(object)val != (Object)null && Vector3.Distance(((Component)val).transform.position, val2) <= value2) { Character attackTarget = val; return new Intent(Activity.Attacking, IntentSource.Threat, null, attackTarget); } return new Intent(Activity.HoldingAt, IntentSource.PlayerOrder, GuardDestination(s, value)); } if (Time.time < st.ObeyUntil) { Character val3 = ThreatScan.AttackerOf(s.Character); if ((Object)(object)val3 != (Object)null) { Character attackTarget = val3; return new Intent(Activity.Attacking, IntentSource.Threat, null, attackTarget); } return new Intent(Activity.Following, IntentSource.PlayerOrder); } if (st.Job != null && SkeletonCrewPlugin.HarvestEnabled.Value && Time.time >= st.JobSuspendedUntil) { if (!st.Job.IsFinished) { Vector3 value3 = st.Job.Target.WorkPosition(position); st.LastWorkPoint = value3; return new Intent(Activity.Working, IntentSource.Work, value3); } return new Intent(Activity.Working, IntentSource.Work, st.LastWorkPoint ?? position); } Character val4 = (((Object)(object)s.AI == (Object)null) ? null : ((BaseAI)s.AI).GetTargetCreature()); if ((Object)(object)val4 != (Object)null) { Character character = s.Character; float num = WeaponChoice.ReachOf((Humanoid)(object)((character is Humanoid) ? character : null)); Vector3 val5 = ((Component)val4).transform.position - position; val5.y = 0f; if (!(((Vector3)(ref val5)).magnitude > num)) { Character attackTarget = val4; return new Intent(Activity.Attacking, IntentSource.Threat, null, attackTarget); } return new Intent(Activity.Attacking, IntentSource.Threat, ((Component)val4).transform.position, val4); } if ((Object)(object)s.AI != (Object)null && (Object)(object)s.AI.GetFollowTarget() == (Object)null) { return new Intent(Activity.Idle, IntentSource.Default); } if ((Object)(object)localPlayer != (Object)null) { if (Fighting(position, localPlayer) || (Object)(object)CrewFocus.Current != (Object)null) { return Intent.Follow; } if (Vector3.Distance(position, ((Component)localPlayer).transform.position) > SkeletonCrewPlugin.FollowDriveDistance.Value) { return new Intent(Activity.Following, IntentSource.Default, ((Component)localPlayer).transform.position); } if (SkeletonCrewPlugin.MarchingOrder.Value && !PlayerHasSettled) { Vector3 val6 = FormationPoint(s, ((Component)localPlayer).transform.position, SkeletonCrewPlugin.FollowFormationRadius.Value); float value4 = SkeletonCrewPlugin.MarchSlack.Value; Vector3 val7 = position - val6; if (((Vector3)(ref val7)).sqrMagnitude > value4 * value4) { return new Intent(Activity.Following, IntentSource.Default, val6); } } if (SkeletonCrewPlugin.FormUpWhenIdle.Value && PlayerHasSettled) { return new Intent(Activity.Following, IntentSource.Default, FormationPoint(s, ((Component)localPlayer).transform.position, SkeletonCrewPlugin.FollowFormationRadius.Value)); } } return Intent.Follow; } private static bool Fighting(Vector3 here, Player player) { //IL_0006: 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) float radius = Stance.EscortRadius(); if (!((Object)(object)ThreatScan.Nearest(here, radius) != (Object)null)) { if ((Object)(object)player != (Object)null) { return (Object)(object)ThreatScan.Nearest(((Component)player).transform.position, radius) != (Object)null; } return false; } return true; } internal static void Tick() { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) Owned.Clear(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsDead() || allCharacter.IsPlayer() || !allCharacter.IsTamed()) { continue; } Tameable component = ((Component)allCharacter).GetComponent(); if (!IsSummon(allCharacter, component)) { ReportSkipped(allCharacter, component); continue; } ZNetView nview = allCharacter.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner() || !IsOurs(nview)) { continue; } ZDOID uid = nview.GetZDO().m_uid; if (!States.TryGetValue(uid, out var value)) { value = new State { FormationSeed = _nextFormationSeed++ }; States[uid] = value; } float health = allCharacter.GetHealth(); if (value.LastHealth >= 0f && health < value.LastHealth - 0.01f) { value.LastCombatTime = Time.time; } value.LastHealth = health; MonsterAI component2 = ((Component)allCharacter).GetComponent(); if ((Object)(object)component2 != (Object)null && (Object)(object)((BaseAI)component2).GetTargetCreature() != (Object)null) { value.LastCombatTime = Time.time; } SummonClass.EnsureTagged((Humanoid)(object)((allCharacter is Humanoid) ? allCharacter : null)); Devotion.Ratchet((Humanoid)(object)((allCharacter is Humanoid) ? allCharacter : null), localPlayer); Summon summon = new Summon { Character = allCharacter, Tameable = component, AI = ((Component)allCharacter).GetComponent(), Id = uid, SinceCombat = Time.time - value.LastCombatTime }; if (SkeletonCrewPlugin.EnableVanillaCommands.Value && !component.m_commandable) { component.m_commandable = true; if (SkeletonCrewPlugin.VerboseLogging.Value) { SkeletonCrewPlugin.Log.LogInfo((object)("Enabled vanilla commands on '" + allCharacter.GetHoverName() + "'.")); } if (SkeletonCrewPlugin.AutoFollowOnSummon.Value && (Object)(object)summon.AI != (Object)null && (Object)(object)summon.AI.GetFollowTarget() == (Object)null) { ((BaseAI)summon.AI).ResetPatrolPoint(); summon.AI.SetFollowTarget(((Component)localPlayer).gameObject); } } ApplySpeed(allCharacter, value); ApplyHealth(allCharacter); ApplyHazardAvoidance(summon.AI, value); ApplyJump(summon.AI, value); ApplyAgent(summon.AI, value); ApplyAttackGeometry(allCharacter); Owned.Add(summon); Report(summon, nview); } RankCrew(); ApplyNoCollision(); ApplyScreening(); ReportCrewSize(); } private static void ReportCrewSize() { if (Owned.Count != _lastReportedCrewSize) { _lastReportedCrewSize = Owned.Count; SkeletonCrewPlugin.Log.LogInfo((object)$"Crew size: {Owned.Count}."); } } private static void RankCrew() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) Owned.Sort(CompareBySeed); RoleCounts.Clear(); for (int i = 0; i < Owned.Count; i++) { if (States.TryGetValue(Owned[i].Id, out var value)) { value.FollowRank = i; value.FollowRankCount = Owned.Count; ref Role role = ref value.Role; Character character = Owned[i].Character; role = SummonRole.Of((Humanoid)(object)((character is Humanoid) ? character : null)); RoleCounts.TryGetValue(value.Role, out var value2); value.RoleRank = value2; RoleCounts[value.Role] = value2 + 1; } } foreach (Summon item in Owned) { if (States.TryGetValue(item.Id, out var value3) && RoleCounts.TryGetValue(value3.Role, out var value4)) { value3.RoleCount = value4; } } } private static void ApplyScreening() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) bool flag = SkeletonCrewPlugin.RoleFormation.Value && SkeletonCrewPlugin.BackLineScreenDistance.Value > 0f; float value = SkeletonCrewPlugin.BackLineScreenDistance.Value; float screenSq = value * value; foreach (Summon item in Owned) { if (!((Object)(object)item.Character == (Object)null) && States.TryGetValue(item.Id, out var value2)) { bool flag2 = SkeletonCrewPlugin.RoleFormation.Value && !SummonRole.Initiates(value2.Role); bool flag3 = flag && flag2 && Screened(item, screenSq); if (item.Character.m_aiSkipTarget != flag3) { item.Character.m_aiSkipTarget = flag3; } ApplyFlee(item, value2, flag2); } } } private static void ApplyFlee(Summon s, State st, bool back) { if (!((Object)(object)s.AI == (Object)null)) { if (!st.FleeCaptured) { st.BaseFleeHealth = s.AI.m_fleeIfLowHealth; st.BaseFleeSinceHurt = s.AI.m_fleeTimeSinceHurt; st.FleeCaptured = true; } float value = SkeletonCrewPlugin.BackLineFleeHealth.Value; if (!back || value <= 0f) { s.AI.m_fleeIfLowHealth = st.BaseFleeHealth; s.AI.m_fleeTimeSinceHurt = st.BaseFleeSinceHurt; } else { s.AI.m_fleeIfLowHealth = value; s.AI.m_fleeTimeSinceHurt = Mathf.Max(st.BaseFleeSinceHurt, 5f); } } } private static bool Screened(Summon back, float screenSq) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)back.Character).transform.position; foreach (Summon item in Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead() && !(item.Id == back.Id) && States.TryGetValue(item.Id, out var value) && SummonRole.Initiates(value.Role)) { Vector3 val = ((Component)item.Character).transform.position - position; if (((Vector3)(ref val)).sqrMagnitude <= screenSq) { return true; } } } return false; } internal static void NoteCrewCapacity(int capacity) { _crewCapacity = Mathf.Max(_crewCapacity, capacity); } internal static int CrewCapacity() { if (_crewCapacity <= 0) { return Owned.Count + 1; } return _crewCapacity; } internal static void ClearTarget(MonsterAI ai, string why = null) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ai == (Object)null)) { if (why != null && (Object)(object)ai.m_targetCreature != (Object)null && TryFind(ai, out var found) && States.TryGetValue(found.Id, out var value)) { value.Releases++; value.LastRelease = why; } ai.m_targetCreature = null; ai.m_targetStatic = null; ai.m_timeSinceAttacking = 0f; } } internal static void Retarget(MonsterAI ai, Character target) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ai == (Object)null) && !((Object)(object)target == (Object)null)) { if ((Object)(object)((BaseAI)ai).GetTargetCreature() != (Object)(object)target) { ClearTarget(ai, "retargeted"); ai.SetTarget(target); } ((BaseAI)ai).SetAlerted(true); ai.m_timeSinceSensedTargetCreature = 0f; ai.m_lastKnownTargetPos = ((Component)target).transform.position; } } internal static Role RoleOf(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!States.TryGetValue(id, out var value)) { return Role.Line; } return value.Role; } private static int CompareBySeed(Summon a, Summon b) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) States.TryGetValue(a.Id, out var value); States.TryGetValue(b.Id, out var value2); return (value?.FormationSeed ?? 0).CompareTo(value2?.FormationSeed ?? 0); } internal static Vector3 FormationPoint(Summon s, Vector3 centre, float radius) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: 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) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: 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_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if (!States.TryGetValue(s.Id, out var value) || value.FollowRankCount <= 1) { return centre; } float num; if (SkeletonCrewPlugin.RoleFormation.Value) { num = RoleAngle(s, value, Facing(centre)); float num2 = radius; Character character = s.Character; radius = num2 * SummonRole.RadiusScale((Humanoid)(object)((character is Humanoid) ? character : null), value.Role); } else { num = (float)Math.PI * 2f / (float)value.FollowRankCount * (float)value.FollowRank; } Vector3 val = OnGround(Slot(centre, num, radius)); if (!LavaGuard.Safe(val)) { for (int i = 1; i <= 6; i++) { float num3 = (float)i * ((float)Math.PI / 6f); Vector3 val2 = OnGround(Slot(centre, num + num3, radius)); if (LavaGuard.Safe(val2)) { return val2; } Vector3 val3 = OnGround(Slot(centre, num - num3, radius)); if (LavaGuard.Safe(val3)) { return val3; } } return centre; } return val; } private static Vector3 Slot(Vector3 centre, float angle, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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) return new Vector3(centre.x + Mathf.Cos(angle) * radius, centre.y, centre.z + Mathf.Sin(angle) * radius); } private static Vector3 OnGround(Vector3 point) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) float y = default(float); if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.FindFloor(point + Vector3.up * 2f, ref y)) { point.y = y; } return point; } private static float Facing(Vector3 centre) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_004f: 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_0070: Unknown result type (might be due to invalid IL or missing references) Character val = ThreatScan.Nearest(centre, SkeletonCrewPlugin.EscortRadius.Value); Vector3 val2; if ((Object)(object)val != (Object)null) { val2 = ((Component)val).transform.position - centre; } else { Player localPlayer = Player.m_localPlayer; val2 = (((Object)(object)localPlayer == (Object)null) ? Vector3.forward : ((Component)localPlayer).transform.forward); } val2.y = 0f; if (!(((Vector3)(ref val2)).sqrMagnitude < 0.01f)) { return Mathf.Atan2(val2.z, val2.x); } return 0f; } private static float RoleAngle(Summon s, State st, float facing) { Character character = s.Character; float num = SummonRole.Bearing((Humanoid)(object)((character is Humanoid) ? character : null), st.Role); if (st.RoleCount > 1 && num < 0.2f) { num = 0.35f; } if (st.RoleCount > 1 && num > 2.9415927f) { num = 2.7915928f; } int num2 = st.RoleRank / 2; float num3 = ((st.RoleRank % 2 == 0) ? 1f : (-1f)); return facing + num3 * (num + (float)num2 * 0.45f); } private static void ApplyHealth(Character c) { float value = SkeletonCrewPlugin.HealthBloodMagicBonus.Value; if (value <= 0f) { return; } Player localPlayer = Player.m_localPlayer; Skills val = ((localPlayer != null) ? ((Character)localPlayer).GetSkills() : null); if ((Object)(object)val == (Object)null) { return; } float num = c.GetMaxHealthBase() * (float)Mathf.Max(1, c.GetLevel()); if (!(num <= 0f)) { float num2 = num * (1f + value * val.GetSkillFactor((SkillType)10)) * Progression.CrewMultiplier(c) * SummonRole.HealthScale((Humanoid)(object)((c is Humanoid) ? c : null)); if (!(Mathf.Abs(c.GetMaxHealth() - num2) < 0.5f)) { c.SetMaxHealth(num2); } } } private static void ApplyHazardAvoidance(MonsterAI ai, State st) { if (!((Object)(object)ai == (Object)null)) { if (!st.CapturedLava) { st.CapturedLava = true; st.BaseAvoidLava = ((BaseAI)ai).m_avoidLava; st.BaseAvoidLavaFlee = ((BaseAI)ai).m_avoidLavaFlee; st.BaseSkipLavaTargets = ((BaseAI)ai).m_skipLavaTargets; st.BaseFleeInLava = ai.m_fleeInLava; } if (!SkeletonCrewPlugin.AvoidLava.Value) { ((BaseAI)ai).m_avoidLava = st.BaseAvoidLava; ((BaseAI)ai).m_avoidLavaFlee = st.BaseAvoidLavaFlee; ((BaseAI)ai).m_skipLavaTargets = st.BaseSkipLavaTargets; ai.m_fleeInLava = st.BaseFleeInLava; } else { ((BaseAI)ai).m_avoidLava = true; ((BaseAI)ai).m_avoidLavaFlee = true; ((BaseAI)ai).m_skipLavaTargets = true; Player localPlayer = Player.m_localPlayer; ai.m_fleeInLava = (Object)(object)localPlayer == (Object)null || !Hazard.Hurting((Character)(object)localPlayer); } } } private static void ApplySpeed(Character c, State state) { if (state.BaseWalk < 0f) { state.BaseWalk = c.m_walkSpeed; state.BaseRun = c.m_runSpeed; state.BaseTurn = c.m_turnSpeed; state.BaseRunTurn = c.m_runTurnSpeed; } float value = SkeletonCrewPlugin.SpeedMultiplier.Value; c.m_walkSpeed = state.BaseWalk * value; c.m_runSpeed = state.BaseRun * value; c.m_turnSpeed = state.BaseTurn * value; c.m_runTurnSpeed = state.BaseRunTurn * value; c.m_runSpeed = Mathf.Max(c.m_runSpeed, PlayerMatchedRunSpeed(c)); } private static float PlayerMatchedRunSpeed(Character c) { if (!SkeletonCrewPlugin.MatchPlayerRunSpeed.Value) { return 0f; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return 0f; } return ((Character)localPlayer).m_runSpeed * ((Character)localPlayer).GetRunSpeedFactor() * SkeletonCrewPlugin.CatchUpMargin.Value / Mathf.Max(0.1f, c.GetRunSpeedFactor()); } private static void ApplyJump(MonsterAI ai, State state) { if (!((Object)(object)ai == (Object)null)) { if (!state.CapturedJump) { state.CapturedJump = true; state.OriginalJumpInterval = ((BaseAI)ai).m_jumpInterval; } float value = SkeletonCrewPlugin.AutoJumpInterval.Value; ((BaseAI)ai).m_jumpInterval = ((value > 0f) ? value : state.OriginalJumpInterval); } } private static void ApplyNoCollision() { if (!SkeletonCrewPlugin.NoPlayerCollision.Value) { return; } Collider val = (Collider)(object)((Character)(Player.m_localPlayer?)).m_collider; for (int i = 0; i < Owned.Count; i++) { Collider val2 = (Collider)(object)(((Object)(object)Owned[i].Character == (Object)null) ? null : Owned[i].Character.m_collider); if ((Object)(object)val2 == (Object)null) { continue; } if ((Object)(object)val != (Object)null) { Physics.IgnoreCollision(val2, val, true); } for (int j = i + 1; j < Owned.Count; j++) { Collider val3 = (Collider)(object)(((Object)(object)Owned[j].Character == (Object)null) ? null : Owned[j].Character.m_collider); if ((Object)(object)val3 != (Object)null) { Physics.IgnoreCollision(val2, val3, true); } } } } private static void ApplyAttackGeometry(Character c) { if (!SkeletonCrewPlugin.AdjustAttackGeometry.Value) { return; } Humanoid val = (Humanoid)(object)((c is Humanoid) ? c : null); if (val == null) { return; } ItemData currentWeapon = val.GetCurrentWeapon(); if (currentWeapon?.m_shared?.m_attack != null) { SharedData val2 = WeaponRebind.Privatise(currentWeapon); Attack attack = val2.m_attack; AttackGeometry value; bool num = !VanillaGeometry.TryGetValue(val2, out value); if (num) { value = new AttackGeometry { Height = attack.m_attackHeight, Range = attack.m_attackRange, RayWidth = attack.m_attackRayWidth, CharExtra = attack.m_attackRayWidthCharExtra, Angle = attack.m_attackAngle }; VanillaGeometry[val2] = value; } attack.m_attackHeight = Mathf.Min(value.Height, SkeletonCrewPlugin.AttackHeight.Value); attack.m_attackRayWidthCharExtra = Mathf.Max(value.CharExtra, SkeletonCrewPlugin.AttackCharExtra.Value); attack.m_attackRange = Mathf.Max(value.Range, SkeletonCrewPlugin.AttackRange.Value); attack.m_attackRayWidth = Mathf.Max(value.RayWidth, SkeletonCrewPlugin.AttackRayWidth.Value); attack.m_attackAngle = Mathf.Max(value.Angle, SkeletonCrewPlugin.AttackAngle.Value); if (num && SkeletonCrewPlugin.VerboseLogging.Value) { SkeletonCrewPlugin.Log.LogInfo((object)($"Retuned '{val2.m_name}': vanilla height={value.Height:F2} range={value.Range:F2} " + $"width={value.RayWidth:F2} charExtra={value.CharExtra:F2} angle={value.Angle:F0} -> " + $"height={attack.m_attackHeight:F2} range={attack.m_attackRange:F2} " + $"width={attack.m_attackRayWidth:F2} charExtra={attack.m_attackRayWidthCharExtra:F2} " + $"angle={attack.m_attackAngle:F0}")); } } } private static void ApplyAgent(MonsterAI ai, State state) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ai == (Object)null)) { if (!state.CapturedAgent) { state.CapturedAgent = true; state.OriginalAgent = ((BaseAI)ai).m_pathAgentType; } ((BaseAI)ai).m_pathAgentType = (AgentType)(SkeletonCrewPlugin.UseCustomAgent.Value ? 13 : ((int)state.OriginalAgent)); } } private static void Report(Summon summon, ZNetView nview) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.VerboseLogging.Value) { return; } ZDOID uid = nview.GetZDO().m_uid; if (!Reported.Add(uid)) { return; } Character character = summon.Character; Tameable tameable = summon.Tameable; SkeletonCrewPlugin.Log.LogInfo((object)("SUMMON '" + ((Object)character).name + "' (" + character.GetHoverName() + ") " + $"hp={character.GetHealth():F0}/{character.GetMaxHealth():F0} " + $"tameable={(Object)(object)tameable != (Object)null} commandable={tameable.m_commandable} " + $"unsummonDist={tameable.m_unsummonDistance:F0} " + $"unsummonOnLogout={tameable.m_unsummonOnOwnerLogoutSeconds:F0}s " + $"monsterAI={(Object)(object)summon.AI != (Object)null} " + $"owner={nview.IsOwner()}")); if ((Object)(object)summon.AI != (Object)null && States.TryGetValue(uid, out var value) && value.CapturedAgent) { string arg = "unknown"; if ((Object)(object)Pathfinding.instance != (Object)null) { AgentSettings settings = Pathfinding.instance.GetSettings(value.OriginalAgent); AgentSettings settings2 = Pathfinding.instance.GetSettings(((BaseAI)summon.AI).m_pathAgentType); if (settings != null && settings2 != null) { arg = $"was climb={((NavMeshBuildSettings)(ref settings.m_build)).agentClimb} (id {((NavMeshBuildSettings)(ref settings.m_build)).agentTypeID}) " + $"-> now climb={((NavMeshBuildSettings)(ref settings2.m_build)).agentClimb} (id {((NavMeshBuildSettings)(ref settings2.m_build)).agentTypeID})"; } } SkeletonCrewPlugin.Log.LogInfo((object)$" NAV: agent {value.OriginalAgent} -> {((BaseAI)summon.AI).m_pathAgentType}; {arg}"); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)((localPlayer != null) ? ((Character)localPlayer).GetSkills() : null) != (Object)null) { float skillLevel = ((Character)localPlayer).GetSkills().GetSkillLevel((SkillType)10); SkeletonCrewPlugin.Log.LogInfo((object)($" REGEN: BloodMagic {skillLevel:F0} -> {Regen.CurrentRegenPercent():F2}%/s " + $"({Regen.CurrentRegenPercent() / 100f * character.GetMaxHealth():F1} hp/s at {character.GetMaxHealth():F0} max)")); } Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val != null) { ItemData currentWeapon = val.GetCurrentWeapon(); if (currentWeapon?.m_shared?.m_attack != null) { Attack attack = currentWeapon.m_shared.m_attack; ManualLogSource log = SkeletonCrewPlugin.Log; string[] obj = new string[11] { " WEAPON '", currentWeapon.m_shared.m_name, "' (", null, null, null, null, null, null, null, null }; GameObject dropPrefab = currentWeapon.m_dropPrefab; obj[3] = ((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? "?"; obj[4] = "): "; obj[5] = $"aiRange={currentWeapon.m_shared.m_aiAttackRange:F1} "; obj[6] = $"aiMin={currentWeapon.m_shared.m_aiAttackRangeMin:F1} "; obj[7] = $"aiInterval={currentWeapon.m_shared.m_aiAttackInterval:F1} "; obj[8] = $"range={attack.m_attackRange:F2} height={attack.m_attackHeight:F2} "; obj[9] = $"rayWidth={attack.m_attackRayWidth:F2} charExtra={attack.m_attackRayWidthCharExtra:F2} "; obj[10] = $"angle={attack.m_attackAngle:F0} offset={attack.m_attackOffset:F2}"; log.LogInfo((object)string.Concat(obj)); } SkeletonCrewPlugin.Log.LogInfo((object)(" VISUALS: " + EquipmentVisuals.Describe(val))); } if ((Object)(object)summon.AI != (Object)null) { GameObject followTarget = summon.AI.GetFollowTarget(); SkeletonCrewPlugin.Log.LogInfo((object)(" AI: followTarget=" + (((Object)(object)followTarget == (Object)null) ? "" : ((Object)followTarget).name) + " " + $"alerted={((BaseAI)summon.AI).IsAlerted()} " + "target=" + (((Object)(object)((BaseAI)summon.AI).GetTargetCreature() == (Object)null) ? "" : ((Object)((BaseAI)summon.AI).GetTargetCreature()).name))); } } internal static void OrderAttack(ZDOID id, Character target) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { value.AttackTarget = target; value.AttackUntil = Time.time + SkeletonCrewPlugin.AttackOrderSeconds.Value; value.ObeyUntil = 0f; } } internal static void OrderMove(ZDOID id, Vector3 point, bool spread = true) { //IL_0005: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { if (spread) { Vector2 val = Random.insideUnitCircle * SkeletonCrewPlugin.MoveSpread.Value; value.MovePoint = new Vector3(point.x + val.x, point.y, point.z + val.y); } else { value.MovePoint = point; } value.MoveUntil = Time.time + SkeletonCrewPlugin.MoveOrderSeconds.Value; value.AttackTarget = null; value.AttackUntil = 0f; } } internal static void NotifyArrived(MonsterAI ai) { //IL_0024: 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) foreach (Summon item in Owned) { if (item.AI != ai || !States.TryGetValue(item.Id, out var value)) { continue; } if (!(value.MoveUntil <= 0f)) { value.MoveUntil = 0f; if ((Object)(object)item.AI.GetFollowTarget() != (Object)null) { item.AI.SetFollowTarget((GameObject)null); ((BaseAI)item.AI).SetPatrolPoint(); } HoldWhereSent(item.Id, value); } break; } } private static void TraceMoveOrders() { //IL_0052: 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_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.TraceOrders.Value || Time.time < _nextTrace) { return; } _nextTrace = Time.time + 0.5f; Vector3 val = default(Vector3); foreach (Summon item in Owned) { if (Usable(item) && States.TryGetValue(item.Id, out var value) && !(value.MoveUntil <= 0f)) { GameObject followTarget = item.AI.GetFollowTarget(); bool patrolPoint = ((BaseAI)item.AI).GetPatrolPoint(ref val); float num = (patrolPoint ? Vector3.Distance(val, value.MovePoint) : (-1f)); ManualLogSource log = SkeletonCrewPlugin.Log; string[] obj = new string[12] { "TRACE move '", item.Character.GetHoverName(), "': ", $"dist={Vector3.Distance(((Component)item.Character).transform.position, value.MovePoint):F1} ", "follow=", ((Object)(object)followTarget == (Object)null) ? "" : ((Object)followTarget).name, " patrol=", patrolPoint ? $"set, {num:F1}m from goal" : "NONE", " target=", ((Object)(object)((BaseAI)item.AI).GetTargetCreature() == (Object)null) ? "" : ((Object)((BaseAI)item.AI).GetTargetCreature()).name, " ", null }; Vector3 velocity = item.Character.GetVelocity(); obj[11] = $"vel={((Vector3)(ref velocity)).magnitude:F1}"; log.LogInfo((object)string.Concat(obj)); } } } internal static void TickMoveOrders() { //IL_002e: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_0124: 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_0101: Unknown result type (might be due to invalid IL or missing references) TraceMoveOrders(); Vector3 val = default(Vector3); foreach (Summon item in Owned) { if (!Usable(item) || !States.TryGetValue(item.Id, out var value) || value.MoveUntil <= 0f) { continue; } bool flag = Vector3.Distance(((Component)item.Character).transform.position, value.MovePoint) <= SkeletonCrewPlugin.MoveArriveDistance.Value; if (flag || Time.time >= value.MoveUntil) { value.MoveUntil = 0f; if (flag && (Object)(object)item.AI.GetFollowTarget() != (Object)null) { item.AI.SetFollowTarget((GameObject)null); ((BaseAI)item.AI).SetPatrolPoint(); } HoldWhereSent(item.Id, value); continue; } if ((Object)(object)item.AI.GetFollowTarget() != (Object)null) { item.AI.SetFollowTarget((GameObject)null); } if (!((BaseAI)item.AI).GetPatrolPoint(ref val) || Vector3.Distance(val, value.MovePoint) > 1f) { ((BaseAI)item.AI).ResetPatrolPoint(); ((BaseAI)item.AI).SetPatrolPoint(value.MovePoint); } } } private static void HoldWhereSent(ZDOID id, State st) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.MoveEndsInHold.Value) { SetStation(id, st.MovePoint, pace: false); } } internal static void GrantRecallGrace(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { value.RecallGraceUntil = Time.time + SkeletonCrewPlugin.RecallGraceSeconds.Value; } } internal static bool HasStandingOrder(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (Stations.ContainsKey(id)) { return true; } Intent intent = IntentFor(id); if (!intent.IsCommanded) { return intent.Source == IntentSource.Work; } return true; } private static bool HasNonWorkOrder(ZDOID id) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return IntentFor(id).IsCommanded; } internal static bool HasRecallGrace(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { return Time.time < value.RecallGraceUntil; } return false; } internal static void ClearMove(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { value.MoveUntil = 0f; } } internal static void TickAttackOrders() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) Vector3 val = default(Vector3); foreach (Summon item in Owned) { if (!Usable(item) || !States.TryGetValue(item.Id, out var value) || (Object)(object)value.AttackTarget == (Object)null) { continue; } if (Time.time >= value.AttackUntil || value.AttackTarget.IsDead()) { value.AttackTarget = null; value.AttackUntil = 0f; } else if (!item.Character.InLava() && LavaGuard.Between(((Component)item.Character).transform.position, ((Component)value.AttackTarget).transform.position)) { SkeletonCrewPlugin.Log.LogInfo((object)("Attack order on '" + value.AttackTarget.GetHoverName() + "' cancelled for '" + item.Character.GetHoverName() + "': lava in the way.")); value.AttackTarget = null; value.AttackUntil = 0f; Disengage(item.Id, item.AI, SkeletonCrewPlugin.ObeySeconds.Value); } else { if ((Object)(object)((BaseAI)item.AI).GetTargetCreature() != (Object)(object)value.AttackTarget) { Retarget(item.AI, value.AttackTarget); ((BaseAI)item.AI).SetAlerted(true); } if (((BaseAI)item.AI).GetPatrolPoint(ref val)) { ((BaseAI)item.AI).ResetPatrolPoint(); } item.AI.m_timeSinceSensedTargetCreature = 0f; item.AI.m_lastKnownTargetPos = ((Component)value.AttackTarget).transform.position; } } } internal static void Obey(ZDOID id, MonsterAI ai) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.OrdersOverrideCombat.Value) { if (States.TryGetValue(id, out var value)) { value.ObeyUntil = Time.time + SkeletonCrewPlugin.ObeySeconds.Value; value.AttackTarget = null; value.AttackUntil = 0f; } if ((Object)(object)ai != (Object)null) { ClearTarget(ai, "order: stand down"); ((BaseAI)ai).SetAlerted(false); } } } internal static void Disengage(ZDOID id, MonsterAI ai, float seconds) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { value.ObeyUntil = Mathf.Max(value.ObeyUntil, Time.time + seconds); } ClearTarget(ai, "disengaged"); if (ai != null) { ((BaseAI)ai).SetAlerted(false); } } internal static void OrderHarvest(ZDOID id, HarvestTarget target, bool continueNearby, Vector3 areaCentre) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { value.Job = new HarvestJob(target); value.JobContinue = continueNearby; value.AreaCentre = areaCentre; value.AreaRadius = (continueNearby ? SkeletonCrewPlugin.HarvestAreaRadius.Value : 0f); value.JobSuspendedUntil = 0f; value.JobSettleAt = 0f; value.AttackTarget = null; value.AttackUntil = 0f; value.MoveUntil = 0f; value.Regrouping = false; value.ObeyUntil = 0f; } } private static bool IsThreatened(Summon s, State st) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((BaseAI)s.AI).GetTargetCreature() != (Object)null) { return true; } if (Time.time - st.LastCombatTime < 3f) { return true; } float value = SkeletonCrewPlugin.DefendRadius.Value; Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer != (Object)null) || !ThreatScan.AggressorNear(((Component)localPlayer).transform.position, value)) { return ThreatScan.AggressorNear(((Component)s.Character).transform.position, value); } return true; } internal static bool AccumulateRegen(ZDOID id, float healed, out float total) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) total = 0f; if (!States.TryGetValue(id, out var value)) { return false; } value.RegenPending += healed; total = value.RegenPending; if (total < SkeletonCrewPlugin.RegenTextMinimum.Value || Time.time < value.RegenTextAt) { return false; } value.RegenPending = 0f; value.RegenTextAt = Time.time + SkeletonCrewPlugin.RegenTextInterval.Value; return true; } internal static void ApplyPerception(float multiplier) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) foreach (Summon item in Owned) { if (Usable(item) && !((Object)(object)item.AI == (Object)null) && States.TryGetValue(item.Id, out var value)) { if (!value.PerceptionCaptured) { value.VanillaView = ((BaseAI)item.AI).m_viewRange; value.VanillaHear = ((BaseAI)item.AI).m_hearRange; value.VanillaAlert = item.AI.m_alertRange; value.PerceptionCaptured = true; } float num = multiplier; if (SkeletonCrewPlugin.RoleFormation.Value && value.Role == Role.Front) { num *= SkeletonCrewPlugin.FrontLineViewMultiplier.Value; } ((BaseAI)item.AI).m_viewRange = value.VanillaView * num; ((BaseAI)item.AI).m_hearRange = value.VanillaHear * num; item.AI.m_alertRange = value.VanillaAlert * num; } } } internal static bool MaySwapWeapon(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { return Time.time >= value.NextWeaponSwap; } return false; } internal static void NoteWeaponSwap(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { value.NextWeaponSwap = Time.time + SkeletonCrewPlugin.WeaponSwapSeconds.Value; } } internal static void ApplyCircling(Summon s, bool ranged) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s.AI == (Object)null || !States.TryGetValue(s.Id, out var value)) { return; } if (!value.CirclingCaptured) { value.BaseCircleInterval = s.AI.m_circleTargetInterval; value.BaseCircleDuration = s.AI.m_circleTargetDuration; value.BaseCircleDistance = s.AI.m_circleTargetDistance; value.CirclingCaptured = true; } if (!ranged) { s.AI.m_circleTargetInterval = value.BaseCircleInterval; s.AI.m_circleTargetDuration = value.BaseCircleDuration; s.AI.m_circleTargetDistance = value.BaseCircleDistance; return; } s.AI.m_circleTargetInterval = SkeletonCrewPlugin.KiteInterval.Value; s.AI.m_circleTargetDuration = SkeletonCrewPlugin.KiteDuration.Value; float num = SkeletonCrewPlugin.KiteDistance.Value; if (SkeletonCrewPlugin.RoleFormation.Value && value.Role == Role.Back) { Character character = s.Character; Character obj = ((character is Humanoid) ? character : null); float valueOrDefault = ((obj == null) ? ((float?)null) : ((Humanoid)obj).GetCurrentWeapon()?.m_shared?.m_aiAttackRange).GetValueOrDefault(); if (valueOrDefault > 0f) { num = Mathf.Max(num, valueOrDefault * 0.8f); } } s.AI.m_circleTargetDistance = num; } internal static void ClearJob(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { value.Job = null; } } private static void RebuildClaims() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) Claims.Clear(); foreach (Summon item in Owned) { if (States.TryGetValue(item.Id, out var value) && value.Job != null && !value.Job.IsFinished && value.Job.Target != null && (Object)(object)value.Job.Target.Root != (Object)null) { Claims[item.Id] = value.Job.Target.Root; } } } private static Dictionary ClaimsExcept(ZDOID id) { //IL_0021: 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) ClaimScratch.Clear(); foreach (KeyValuePair claim in Claims) { if (!(claim.Key == id)) { ClaimScratch.TryGetValue(claim.Value, out var value); ClaimScratch[claim.Value] = value + 1; } } return ClaimScratch; } internal static void TickJobs() { //IL_003b: 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_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.HarvestEnabled.Value) { return; } RebuildClaims(); foreach (Summon item in Owned) { if (!Usable(item) || !States.TryGetValue(item.Id, out var value) || value.Job == null) { continue; } if (HasNonWorkOrder(item.Id)) { value.Job = null; if (SkeletonCrewPlugin.VerboseLogging.Value) { SkeletonCrewPlugin.Log.LogInfo((object)("Harvest: '" + item.Character.GetHoverName() + "' dropped work for a new order.")); } continue; } if (SkeletonCrewPlugin.DefendInterruptsWork.Value && IsThreatened(item, value)) { value.JobSuspendedUntil = Time.time + SkeletonCrewPlugin.WorkResumeSeconds.Value; } if (Time.time < value.JobSuspendedUntil) { continue; } value.Job.Tick(item.Character); if (!value.Job.IsFinished) { continue; } if (value.JobSettleAt <= 0f) { value.JobSettleAt = Time.time + SkeletonCrewPlugin.HarvestSettleSeconds.Value; } else if (!(Time.time < value.JobSettleAt)) { value.JobSettleAt = 0f; Dictionary claimed = ClaimsExcept(item.Id); HarvestTarget harvestTarget = HarvestScan.LogFor(value.Job.Target, claimed); if (harvestTarget == null && value.JobContinue && SkeletonCrewPlugin.HarvestContinue.Value) { harvestTarget = HarvestScan.NextNear(value.Job.Target, value.AreaCentre, value.AreaRadius, ((Component)item.Character).transform.position, claimed); } if (harvestTarget != null) { value.Job = new HarvestJob(harvestTarget); SkeletonCrewPlugin.Log.LogInfo((object)("Harvest: '" + item.Character.GetHoverName() + "' moving to '" + harvestTarget.Name + "'.")); } else { value.Job = null; SkeletonCrewPlugin.Log.LogInfo((object)("Harvest: '" + item.Character.GetHoverName() + "' finished.")); } } } } internal static void SetRegrouping(ZDOID id, bool value) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value2)) { value2.Regrouping = value; } } internal static void TickObedience() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; foreach (Summon item in Owned) { if (!Usable(item) || !States.TryGetValue(item.Id, out var value)) { continue; } bool flag = Time.time < value.ObeyUntil || value.MoveUntil > 0f; if (value.Regrouping && (Object)(object)localPlayer != (Object)null) { if (Vector3.Distance(((Component)item.Character).transform.position, ((Component)localPlayer).transform.position) <= SkeletonCrewPlugin.RegroupDistance.Value) { value.Regrouping = false; } else { flag = true; } } if (flag && !((Object)(object)ThreatScan.AttackerOf(item.Character) != (Object)null) && (Object)(object)((BaseAI)item.AI).GetTargetCreature() != (Object)null) { ClearTarget(item.AI, "obey window"); ((BaseAI)item.AI).SetAlerted(false); } } } internal static void MarkOwned(Character c, Player player) { if ((Object)(object)c == (Object)null || (Object)(object)player == (Object)null) { return; } ZNetView nview = c.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner()) { return; } ZDO zDO = nview.GetZDO(); if (zDO != null) { string playerName = player.GetPlayerName(); if (zDO.GetString(ZDOVars.s_follow, string.Empty) != playerName) { zDO.Set(ZDOVars.s_follow, playerName); } } } internal static void PreserveOwnerAfterCommand(Tameable tameable, ZDOID commander) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)tameable == (Object)null || commander != ((Character)localPlayer).GetZDOID()) { return; } Character character = tameable.m_character; if ((Object)(object)character == (Object)null || !IsSummon(character, tameable)) { return; } ZNetView nview = character.m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner()) { ZDO zDO = nview.GetZDO(); if (zDO != null && zDO.GetString(ZDOVars.s_follow, string.Empty).Length <= 0) { zDO.Set(ZDOVars.s_follow, localPlayer.GetPlayerName()); } } } internal static int AdoptOrphans(Player player, float radius) { //IL_00f3: 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_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0; } string playerName = player.GetPlayerName(); float num = ((radius > 0f) ? (radius * radius) : float.MaxValue); int num2 = 0; foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsDead() || allCharacter.IsPlayer() || !allCharacter.IsTamed()) { continue; } Tameable component = ((Component)allCharacter).GetComponent(); if ((Object)(object)component == (Object)null || !IsSummon(allCharacter, component)) { continue; } ZNetView nview = allCharacter.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner()) { continue; } ZDO zDO = nview.GetZDO(); if (zDO == null || zDO.GetString(ZDOVars.s_follow, string.Empty).Length > 0) { continue; } Vector3 val = ((Component)allCharacter).transform.position - ((Component)player).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > num)) { zDO.Set(ZDOVars.s_follow, playerName); MonsterAI monsterAI = component.m_monsterAI; if ((Object)(object)monsterAI != (Object)null && (Object)(object)monsterAI.GetFollowTarget() == (Object)null) { ((BaseAI)monsterAI).ResetPatrolPoint(); monsterAI.SetFollowTarget(((Component)player).gameObject); } num2++; } } if (num2 > 0) { SkeletonCrewPlugin.Log.LogInfo((object)($"Adopted {num2} unclaimed summon(s) - they now carry an owner name and " + "count toward the summon cap again.")); } return num2; } private static bool IsOurs(ZNetView nview) { if ((Object)(object)nview != (Object)null) { return IsOurs(nview.GetZDO()); } return false; } internal static bool IsOurs(ZDO zdo) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || zdo == null) { return false; } string text = zdo.GetString(ZDOVars.s_follow, string.Empty); if (text.Length > 0) { return text == localPlayer.GetPlayerName(); } return false; } private static bool IsSummon(Character c, Tameable tameable) { string signal; return IsSummon(c, tameable, out signal); } internal static bool IsSummonPrefab(Character c) { Tameable val = (((Object)(object)c == (Object)null) ? null : ((Component)c).GetComponent()); if ((Object)(object)val != (Object)null) { return val.m_unsummonDistance > 0f; } return false; } internal static bool IsSummon(Character c, Tameable tameable, out string signal) { if ((Object)(object)tameable != (Object)null && tameable.m_unsummonDistance > 0f) { signal = $"unsummonDistance {tameable.m_unsummonDistance:F0} > 0"; return true; } string value = SkeletonCrewPlugin.SummonPrefabPatterns.Value; if (string.IsNullOrEmpty(value)) { signal = "no unsummon distance, and SummonPrefabPatterns is empty"; return false; } string name = ((Object)c).name; string[] array = value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0 && name.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { signal = "name matches pattern '" + text + "'"; return true; } } signal = "no unsummon distance, and no name pattern matches"; return false; } private static void ReportSkipped(Character c, Tameable tameable) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.VerboseLogging.Value && !((Object)(object)c.m_nview == (Object)null) && c.m_nview.IsValid() && SkippedReported.Add(c.m_nview.GetZDO().m_uid)) { SkeletonCrewPlugin.Log.LogInfo((object)("SKIPPED tamed '" + ((Object)c).name + "' (" + c.GetHoverName() + ") - not treated as a summon. " + $"tameable={(Object)(object)tameable != (Object)null} " + $"unsummonDist={(((Object)(object)tameable == (Object)null) ? (-1f) : tameable.m_unsummonDistance)}. " + "Add a matching word to SummonPrefabPatterns if it should obey orders.")); } } internal static bool IsOwnedSummon(Character c) { if ((Object)(object)c == (Object)null) { return false; } foreach (Summon item in Owned) { if (item.Character == c) { return true; } } return false; } internal static bool Usable(Summon s) { if (s != null && (Object)(object)s.Character != (Object)null && (Object)(object)s.AI != (Object)null && !s.Character.IsDead() && (Object)(object)s.Character.m_nview != (Object)null) { return s.Character.m_nview.IsValid(); } return false; } internal static bool HasOrderedTarget(MonsterAI ai) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!TryFind(ai, out var found)) { return false; } Intent intent = IntentFor(found.Id); if (intent.Activity == Activity.Attacking) { return intent.Source == IntentSource.PlayerOrder; } return false; } internal static bool TryGetOrderedDestination(MonsterAI ai, out Vector3 destination) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_00a5: Unknown result type (might be due to invalid IL or missing references) destination = Vector3.zero; if ((Object)(object)ai == (Object)null) { return false; } if (!TryFind(ai, out var found)) { return false; } Intent intent = IntentFor(found.Id); if (!intent.Destination.HasValue) { return false; } if ((Object)(object)found.AI != (Object)null && (Object)(object)((BaseAI)found.AI).GetTargetCreature() != (Object)null && intent.Source != IntentSource.Rescue && intent.Activity != Activity.Attacking && intent.Activity != Activity.MovingTo && intent.Activity != Activity.Regrouping && intent.Activity != Activity.Working) { return false; } destination = intent.Destination.Value; return true; } internal static bool ShouldRunToOrder(BaseAI ai) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ai == (Object)null) { return false; } foreach (Summon item in Owned) { if ((object)item.AI != ai) { continue; } Intent intent = IntentFor(item.Id); if (intent.Activity == Activity.Following) { if (!intent.Destination.HasValue) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { Vector3 val = ((Component)item.Character).transform.position - ((Component)localPlayer).transform.position; if (((Vector3)(ref val)).sqrMagnitude < SkeletonCrewPlugin.FollowDriveDistance.Value * SkeletonCrewPlugin.FollowDriveDistance.Value) { return false; } } return true; } return intent.IsCommanded && (intent.Activity == Activity.MovingTo || intent.Activity == Activity.HoldingAt || intent.Activity == Activity.Regrouping); } return false; } internal static bool IsSuppressingCombat(MonsterAI ai) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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) if ((Object)(object)ai == (Object)null) { return false; } if (!TryFind(ai, out var found)) { return false; } if ((Object)(object)ThreatScan.AttackerOf(found.Character) != (Object)null) { return false; } Intent intent = IntentFor(found.Id); switch (intent.Activity) { case Activity.MovingTo: case Activity.Working: return true; case Activity.Regrouping: { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { return Vector3.Distance(((Component)found.Character).transform.position, ((Component)localPlayer).transform.position) > SkeletonCrewPlugin.RegroupDistance.Value; } return true; } case Activity.Following: return intent.Source == IntentSource.PlayerOrder; default: return false; } } internal static bool IsHoldingPosition(Tameable tameable) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)tameable == (Object)null) { return false; } foreach (Summon item in Owned) { if (item.Tameable == tameable) { Activity activity = IntentFor(item.Id).Activity; return activity == Activity.HoldingAt || activity == Activity.MovingTo; } } return false; } internal static bool HasStandingOrder(Tameable tameable) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)tameable == (Object)null) { return false; } foreach (Summon item in Owned) { if (item.Tameable == tameable) { return HasStandingOrder(item.Id); } } return false; } internal static bool IsFollowSuppressed(Tameable tameable) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)tameable == (Object)null) { return false; } foreach (Summon item in Owned) { if (item.Tameable == tameable) { Activity activity = IntentFor(item.Id).Activity; if (activity == Activity.Idle || (uint)(activity - 3) <= 1u) { return true; } return false; } } return false; } internal static void SetStation(ZDOID id, Vector3 point, bool pace = true) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_0020: Unknown result type (might be due to invalid IL or missing references) Stations[id] = point; Patrols.Remove(id); if (pace) { PrecisePosts.Remove(id); } else { PrecisePosts.Add(id); } } private static Vector3 GuardCentre(Summon s, Vector3 post) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) State value; Vector3 val = ((States.TryGetValue(s.Id, out value) && value.GuardSlotCount > 0) ? value.GuardAnchor : post); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !SkeletonCrewPlugin.GuardTheOwner.Value) { return val; } if (!(Vector3.Distance(((Component)localPlayer).transform.position, val) <= SkeletonCrewPlugin.GuardRadius.Value)) { return val; } return ((Component)localPlayer).transform.position; } private static Vector3 GuardDestination(Summon s, Vector3 post) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: 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_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) if (PrecisePosts.Contains(s.Id)) { return post; } if (!States.TryGetValue(s.Id, out var value) || value.GuardSlotCount <= 0) { return GuardPatrolTarget(s, post); } Vector3 val = GuardCentre(s, post); float num = SkeletonCrewPlugin.GuardPerimeterRadius.Value; float num2; if (SkeletonCrewPlugin.RoleFormation.Value) { num2 = RoleAngle(s, value, Facing(val)); float num3 = num; Character character = s.Character; num = num3 * SummonRole.RadiusScale((Humanoid)(object)((character is Humanoid) ? character : null), value.Role); } else { num2 = (float)Math.PI * 2f / (float)value.GuardSlotCount * (float)value.GuardSlot; } Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(val.x + Mathf.Cos(num2) * num, val.y, val.z + Mathf.Sin(num2) * num); float y = default(float); if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.FindFloor(val2 + Vector3.up * 2f, ref y)) { val2.y = y; } return val2; } internal static void SetGuardSlot(ZDOID id, int slot, int total, Vector3 anchor) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (States.TryGetValue(id, out var value)) { value.GuardSlot = slot; value.GuardSlotCount = total; value.GuardAnchor = anchor; } } private static Vector3 GuardPatrolTarget(Summon s, Vector3 post) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.GuardPatrol.Value) { return post; } float value = SkeletonCrewPlugin.GuardPatrolRadius.Value; if (Patrols.TryGetValue(s.Id, out (Vector3, float) value2) && !(Time.time >= value2.Item2) && !(Vector3.Distance(((Component)s.Character).transform.position, value2.Item1) <= 1.5f)) { return value2.Item1; } Vector3 val = post; Vector3 val3 = default(Vector3); float y = default(float); for (int i = 0; i < 8; i++) { Vector2 val2 = Random.insideUnitCircle * value; ((Vector3)(ref val3))..ctor(post.x + val2.x, post.y, post.z + val2.y); if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.FindFloor(val3 + Vector3.up * 2f, ref y)) { val3.y = y; } if (LavaGuard.Safe(val3)) { val = val3; break; } } Patrols[s.Id] = (val, Time.time + SkeletonCrewPlugin.GuardPatrolInterval.Value); return val; } internal static void ClearStation(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) Stations.Remove(id); Patrols.Remove(id); PrecisePosts.Remove(id); } internal static bool HoldsCrewFocus(MonsterAI ai) { Character current = CrewFocus.Current; Summon found; if ((Object)(object)current != (Object)null && (Object)(object)ai != (Object)null && (Object)(object)((BaseAI)ai).GetTargetCreature() == (Object)(object)current) { return TryFind(ai, out found); } return false; } internal static void NoteVanillaDrop(MonsterAI ai, string why) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (TryFind(ai, out var found) && States.TryGetValue(found.Id, out var value)) { value.Releases++; value.LastRelease = why; } } internal static string Churn(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!States.TryGetValue(id, out var value)) { return "-"; } return $"{value.Acquires}/{value.Releases}" + ((value.LastRelease == null) ? string.Empty : (" last='" + value.LastRelease + "'")); } internal static int CapFor(int quality) { int num = ((SkeletonCrewPlugin.SummonCapBonus != null) ? ((quality >= SkeletonCrewPlugin.SummonCapBonusAtLevel.Value) ? SkeletonCrewPlugin.SummonCapBonus.Value : 0) : 0); return Mathf.Max(1, quality + num); } internal static bool IsStationed(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return Stations.ContainsKey(id); } internal static bool IsStationed(ZDOID id, out Vector3 post) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return Stations.TryGetValue(id, out post); } internal static void TickGuards() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: 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_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0154: 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_0135: Unknown result type (might be due to invalid IL or missing references) if (Stations.Count == 0) { return; } float value = SkeletonCrewPlugin.GuardRadius.Value; Vector3 val5 = default(Vector3); foreach (Summon item in Owned) { if (!Usable(item) || !Stations.TryGetValue(item.Id, out var value2) || (Object)(object)((BaseAI)item.AI).GetTargetCreature() != (Object)null) { continue; } Vector3 val = GuardCentre(item, value2); Character character = item.Character; if (SummonClass.Of((Humanoid)(object)((character is Humanoid) ? character : null)) == SummonPreference.Healer) { continue; } Character val2 = NearestHostile(val, value); if ((Object)(object)val2 != (Object)null) { Retarget(item.AI, val2); ((BaseAI)item.AI).SetAlerted(true); continue; } Vector3 val3 = GuardDestination(item, value2); bool num = PrecisePosts.Contains(item.Id); float num2 = (num ? SkeletonCrewPlugin.PreciseHoldSlack.Value : (value * 0.5f)); Vector3 val4 = (num ? val3 : val); if (!(Vector3.Distance(((Component)item.Character).transform.position, val4) <= num2)) { item.AI.SetFollowTarget((GameObject)null); if (!((BaseAI)item.AI).GetPatrolPoint(ref val5) || Vector3.Distance(val5, val3) > 1f) { ((BaseAI)item.AI).ResetPatrolPoint(); ((BaseAI)item.AI).SetPatrolPoint(val3); } } } } private static Character NearestHostile(Vector3 point, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return ThreatScan.Nearest(point, radius); } internal static void Reset() { Reported.Clear(); States.Clear(); Stations.Clear(); Owned.Clear(); } } internal static class CrewSelection { internal static ZDOID Selected { get; private set; } = ZDOID.None; internal static bool IsAll => Selected == ZDOID.None; private static void Validate() { //IL_001c: 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_0048: Unknown result type (might be due to invalid IL or missing references) if (IsAll) { return; } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (item.Id == Selected) { return; } } Selected = ZDOID.None; } internal static bool Applies(ZDOID id) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) Validate(); if (!IsAll) { return Selected == id; } return true; } internal static void SelectAll() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Selected = ZDOID.None; Announce(); } internal static void Select(ZDOID id) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Selected = id; Validate(); Announce(); } internal static void Cycle() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) Validate(); List owned = SummonRegistry.Owned; if (owned.Count == 0) { Selected = ZDOID.None; Announce(); return; } if (IsAll) { Selected = owned[0].Id; Announce(); return; } int num = -1; for (int i = 0; i < owned.Count; i++) { if (owned[i].Id == Selected) { num = i; break; } } Selected = ((num < 0 || num + 1 >= owned.Count) ? ZDOID.None : owned[num + 1].Id); Announce(); } internal static string Describe() { //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) Validate(); if (IsAll) { return "ALL"; } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (item.Id == Selected && (Object)(object)item.Character != (Object)null) { return item.Character.GetHoverName(); } } return "ALL"; } private static void Announce() { string text = Describe(); SkeletonCrewPlugin.Log.LogInfo((object)("Orders apply to: " + text + ".")); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, "Orders: " + text, 0, (Sprite)null, false); } } } internal static class TeleportGuard { private static readonly HashSet Protected = new HashSet(); private static float _until; internal static bool IsActive => Time.time < _until; internal static void Begin(float seconds) { _until = Time.time + seconds; } internal static void Protect(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Protected.Add(id); } internal static bool Protects(Tameable tameable) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) if (!IsActive || Protected.Count == 0 || (Object)(object)tameable == (Object)null) { return false; } ZNetView nview = tameable.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return false; } return Protected.Contains(nview.GetZDO().m_uid); } internal static void Clear() { Protected.Clear(); _until = 0f; } } internal static class PluginInfo { public const string Guid = "com.vash.skeletoncrew"; public const string Name = "SkeletonCrew"; public const string Version = "1.0.2"; } } namespace SkeletonCrew.Ui { internal static class CrewMarker { private struct Marker { internal GameObject Go; internal Light Light; } private const string MarkerName = "SkeletonCrewGlow"; private static readonly Dictionary Markers = new Dictionary(); private static Color _normal; private static Color _selected; private static string _normalText; private static string _selectedText; private static readonly List Stale = new List(); private static readonly HashSet Live = new HashSet(); private static string _effectName; private static GameObject _effectPrefab; private static bool _effectResolved; internal static void Reset() { foreach (KeyValuePair marker in Markers) { if ((Object)(object)marker.Value.Go != (Object)null) { Object.Destroy((Object)(object)marker.Value.Go); } } Markers.Clear(); _effectResolved = false; _effectPrefab = null; } internal static void Tick() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.ShowSummonGlow.Value) { if (Markers.Count > 0) { Reset(); } return; } Live.Clear(); foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null)) { Live.Add(item.Id); Apply(item); } } Prune(); } private static void Prune() { //IL_0026: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) Stale.Clear(); foreach (KeyValuePair marker in Markers) { if (!Live.Contains(marker.Key) || (Object)(object)marker.Value.Go == (Object)null) { Stale.Add(marker.Key); } } foreach (ZDOID item in Stale) { if (Markers.TryGetValue(item, out var value) && (Object)(object)value.Go != (Object)null) { Object.Destroy((Object)(object)value.Go); } Markers.Remove(item); } } private static void Apply(SummonRegistry.Summon s) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) if (!Markers.TryGetValue(s.Id, out var value) || (Object)(object)value.Go == (Object)null) { value = Build(s.Character); Markers[s.Id] = value; } Light light = value.Light; if (!((Object)(object)light == (Object)null)) { bool flag = !CrewSelection.IsAll && CrewSelection.Applies(s.Id); string value2 = SkeletonCrewPlugin.GlowColour.Value; if (value2 != _normalText) { _normalText = value2; _normal = Parse(value2); } string value3 = SkeletonCrewPlugin.GlowSelectedColour.Value; if (value3 != _selectedText) { _selectedText = value3; _selected = Parse(value3); } light.color = (flag ? _selected : _normal); value.Go.transform.localPosition = new Vector3(0f, SkeletonCrewPlugin.GlowHeight.Value, 0f); light.intensity = SkeletonCrewPlugin.GlowIntensity.Value * (flag ? 1.8f : 1f); light.range = SkeletonCrewPlugin.GlowRange.Value; } } private static Marker Build(Character c) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("SkeletonCrewGlow"); val.transform.SetParent(((Component)c).transform, false); val.transform.localPosition = new Vector3(0f, SkeletonCrewPlugin.GlowHeight.Value, 0f); Light val2 = val.AddComponent(); val2.type = (LightType)2; val2.shadows = (LightShadows)0; AttachEffect(val); return new Marker { Go = val, Light = val2 }; } private static void AttachEffect(GameObject marker) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) GameObject val = ResolveEffect(); if (!((Object)(object)val == (Object)null)) { GameObject obj = Object.Instantiate(val, marker.transform.position, Quaternion.identity); obj.transform.SetParent(marker.transform, false); obj.transform.localPosition = Vector3.zero; } } private static GameObject ResolveEffect() { string value = SkeletonCrewPlugin.MarkerEffectPrefab.Value; if (_effectResolved && value == _effectName) { return _effectPrefab; } _effectResolved = true; _effectName = value; _effectPrefab = null; if (string.IsNullOrEmpty(value) || (Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject val = ZNetScene.instance.GetPrefab(value); if ((Object)(object)val == (Object)null) { foreach (GameObject nonNetViewPrefab in ZNetScene.instance.m_nonNetViewPrefabs) { if ((Object)(object)nonNetViewPrefab != (Object)null && ((Object)nonNetViewPrefab).name == value) { val = nonNetViewPrefab; break; } } } if ((Object)(object)val == (Object)null) { SkeletonCrewPlugin.Log.LogWarning((object)("MarkerEffectPrefab '" + value + "' not found - using the light alone. Run skcrew_prefabs to list what exists.")); return null; } if ((Object)(object)val.GetComponent() != (Object)null) { SkeletonCrewPlugin.Log.LogWarning((object)("MarkerEffectPrefab '" + value + "' carries a ZNetView and was REFUSED: attaching it would spawn a networked object that every client sees and the world keeps. Pick one listed as attachable by skcrew_prefabs.")); return null; } _effectPrefab = val; SkeletonCrewPlugin.Log.LogInfo((object)("Marker effect '" + value + "' resolved and attached to each summon.")); return _effectPrefab; } private static Color Parse(string html) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); if (!ColorUtility.TryParseHtmlString(html, ref result)) { return Color.white; } return result; } } internal static class CrewMenu { private sealed class Item { internal string Label; internal Func LabelOf; internal Func Status; internal Func Available; internal Action Invoke; internal List Children; internal bool Destructive; internal string Text { get { if (LabelOf == null) { return Label; } return LabelOf(); } } internal bool IsSubmenu { get { if (Children != null) { return Children.Count > 0; } return false; } } } private static readonly List> Stack = new List>(); private static readonly List Visible = new List(); private static int _index; private static GameObject _panel; private static Text _title; private static Text _hint; private static readonly List Rows = new List(); private static Transform _attachedTo; private static Commands.LookResult _openLook; private static float _suppressUntil; private const int RowHeight = 22; private const int PanelWidth = 260; internal static bool IsOpen { get; private set; } internal static bool ConsumesInput { get { if (!IsOpen) { return Time.time < _suppressUntil; } return true; } } private static void Consume() { _suppressUntil = Time.time + 0.25f; } internal static void Toggle() { if (IsOpen) { Close(); return; } _openLook = Commands.Look(); Stack.Clear(); Stack.Add(Root()); _index = 0; IsOpen = true; } internal static void Close() { IsOpen = false; Stack.Clear(); if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); } } internal static bool HandleInput() { if (!IsOpen) { return false; } if (Input.GetKeyDown((KeyCode)27) || Input.GetKeyDown((KeyCode)276)) { Consume(); if (Stack.Count > 1) { Stack.RemoveAt(Stack.Count - 1); _index = 0; } else { Close(); } return true; } RefreshVisible(); if (Visible.Count == 0) { return true; } if (Input.GetKeyDown((KeyCode)274)) { Consume(); _index = (_index + 1) % Visible.Count; } else if (Input.GetKeyDown((KeyCode)273)) { Consume(); _index = (_index - 1 + Visible.Count) % Visible.Count; } else if (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271) || Input.GetKeyDown((KeyCode)275)) { Consume(); Choose(Visible[Mathf.Clamp(_index, 0, Visible.Count - 1)]); } return true; } private static void Choose(Item item) { if (item.IsSubmenu) { Stack.Add(item.Children); _index = 0; return; } item.Invoke?.Invoke(); if (item.Status == null) { Close(); } } private static void RefreshVisible() { Visible.Clear(); if (Stack.Count == 0) { return; } foreach (Item item in Stack[Stack.Count - 1]) { if (item.Available == null || item.Available()) { Visible.Add(item); } } _index = ((Visible.Count != 0) ? Mathf.Clamp(_index, 0, Visible.Count - 1) : 0); } private static string TargetName() { if (_openLook.Harvest == null) { return "that"; } string prefabName = _openLook.Harvest.PrefabName; if (!string.IsNullOrEmpty(prefabName)) { return prefabName.TrimStart(new char[1] { '_' }); } return "that"; } private static string WhyNot() { if (!string.IsNullOrEmpty(_openLook.HarvestRefusal)) { return _openLook.HarvestRefusal; } return "aim at it, then F1"; } private static void RunHarvest(Commands.OrderAction action) { if (_openLook.Harvest != null) { Commands.Execute(action, _openLook); return; } string text = "Nothing to harvest - " + WhyNot(); SkeletonCrewPlugin.Log.LogInfo((object)("Order 'harvest': refused. " + WhyNot() + ".")); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false); } } private static void Heal(Func request) { string text = request(); if (text != null) { SkeletonCrewPlugin.Log.LogInfo((object)("Order 'heal': refused. " + text + ".")); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false); } } } private static List Root() { return new List { new Item { Label = "Come to me", Invoke = Commands.FollowFromMenu }, new Item { Label = "Hold position", Invoke = Commands.HoldFromMenu }, new Item { Label = "Attack what I was aiming at", Available = () => (Object)(object)_openLook.Target != (Object)null, Invoke = delegate { Commands.Execute(Commands.OrderAction.Attack, _openLook); } }, new Item { Label = "Heal me", Available = () => Healer.Available(), Invoke = delegate { Heal(() => (!Healer.OrderMe(out var why)) ? why : null); } }, new Item { Label = "Heal whoever needs it most", Available = () => Healer.Available(), Invoke = delegate { Heal(() => (!Healer.OrderWorst(out var why)) ? why : null); } }, new Item { LabelOf = () => (_openLook.Harvest == null) ? ("Harvest one - " + WhyNot()) : ("Harvest one: " + TargetName()), Available = () => SkeletonCrewPlugin.HarvestEnabled.Value, Invoke = delegate { RunHarvest(Commands.OrderAction.Harvest); } }, new Item { LabelOf = () => (_openLook.Harvest == null) ? ("Harvest area - " + WhyNot()) : ("Harvest every " + TargetName() + " within " + $"{SkeletonCrewPlugin.HarvestAreaRadius.Value:F0}m"), Available = () => SkeletonCrewPlugin.HarvestEnabled.Value, Invoke = delegate { RunHarvest(Commands.OrderAction.HarvestArea); } }, new Item { Label = "Orders apply to", Status = () => CrewSelection.Describe(), Children = SelectionItems() }, new Item { Label = "Stance", Status = () => Stance.Current.ToString(), Children = StanceItems() }, new Item { Label = "Next raise", Status = () => (!SummonGroup.IsArmed) ? SkeletonCrewPlugin.SummonWeaponPreference.Value.ToString() : $"{SummonGroup.ArmedName} ({SummonGroup.Remaining} to go)", Children = NextRaiseItems() }, new Item { Label = "Harvesting", Status = () => (!SkeletonCrewPlugin.HarvestEnabled.Value) ? "off" : "on", Children = HarvestItems() }, new Item { LabelOf = () => $"Dismiss all ({SummonRegistry.Owned.Count})", Available = () => SkeletonCrewPlugin.AllowDismiss.Value, Destructive = true, Invoke = Commands.DismissAllFromMenu } }; } private static List SelectionItems() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) List list = new List { new Item { Label = "Everyone", Status = () => (!CrewSelection.IsAll) ? string.Empty : "*", Invoke = CrewSelection.SelectAll } }; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null)) { ZDOID id = item.Id; string hoverName = item.Character.GetHoverName(); list.Add(new Item { Label = hoverName, Status = () => (CrewSelection.IsAll || !(CrewSelection.Selected == id)) ? string.Empty : "*", Invoke = delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) CrewSelection.Select(id); } }); } } return list; } private static List StanceItems() { List list = new List(); CrewStance[] array = (CrewStance[])Enum.GetValues(typeof(CrewStance)); foreach (CrewStance crewStance in array) { CrewStance captured = crewStance; list.Add(new Item { Label = captured.ToString(), Status = () => (SkeletonCrewPlugin.StanceSetting.Value != captured) ? string.Empty : "*", Invoke = delegate { SkeletonCrewPlugin.StanceSetting.Value = captured; } }); } return list; } private static List SummonTypeItems() { List list = new List(); SummonPreference[] array = (SummonPreference[])Enum.GetValues(typeof(SummonPreference)); foreach (SummonPreference summonPreference in array) { if ((summonPreference != SummonPreference.Adaptive || SkeletonCrewPlugin.EnableAdaptive.Value) && Progression.IsUnlocked(summonPreference)) { SummonPreference captured = summonPreference; list.Add(new Item { Label = $"{captured} (one)", Status = () => (SummonGroup.IsArmed || SkeletonCrewPlugin.SummonWeaponPreference.Value != captured) ? string.Empty : "*", Invoke = delegate { SummonGroup.Disarm(); SkeletonCrewPlugin.SummonWeaponPreference.Value = captured; } }); } } return list; } private static List NextRaiseItems() { List list = new List(); foreach (KeyValuePair> item in SummonGroup.All()) { string name = item.Key; string detail = SummonGroup.Describe(item.Value); int size = item.Value.Count; list.Add(new Item { Label = name + " (" + detail + ")", Status = () => (!string.Equals(SummonGroup.ArmedName, name, StringComparison.OrdinalIgnoreCase)) ? $"{size} summons" : "*", Invoke = delegate { if (SummonGroup.Arm(name) && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, "Next raise: " + name + " - " + detail, 0, (Sprite)null, false); } } }); } list.AddRange(SummonTypeItems()); return list; } private static List HarvestItems() { return new List { new Item { Label = "Harvesting", Status = () => (!SkeletonCrewPlugin.HarvestEnabled.Value) ? "off" : "on", Invoke = delegate { SkeletonCrewPlugin.HarvestEnabled.Value = !SkeletonCrewPlugin.HarvestEnabled.Value; } }, new Item { Label = "Pick berries and mushrooms", Available = () => SkeletonCrewPlugin.HarvestEnabled.Value, Status = () => (!SkeletonCrewPlugin.HarvestPickables.Value) ? "off" : "on", Invoke = delegate { SkeletonCrewPlugin.HarvestPickables.Value = !SkeletonCrewPlugin.HarvestPickables.Value; } }, new Item { Label = "Mine world structures", Available = () => SkeletonCrewPlugin.HarvestEnabled.Value, Status = () => (!SkeletonCrewPlugin.HarvestStructures.Value) ? "off" : "on", Invoke = delegate { SkeletonCrewPlugin.HarvestStructures.Value = !SkeletonCrewPlugin.HarvestStructures.Value; } } }; } internal static void Tick() { if (!IsOpen) { if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); } return; } Transform val = UiRoot.Current(); if (!((Object)(object)val == (Object)null)) { if ((Object)(object)_panel == (Object)null) { Build(val); } if ((Object)(object)_attachedTo != (Object)(object)val) { _panel.transform.SetParent(val, false); _attachedTo = val; } _panel.SetActive(true); RefreshVisible(); Draw(); } } private static void Build(Transform parent) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_007b: 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_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) Rows.Clear(); _panel = new GameObject("SkeletonCrewMenu", new Type[3] { typeof(RectTransform), typeof(CanvasGroup), typeof(Image) }); _panel.transform.SetParent(parent, false); _attachedTo = parent; ((Graphic)_panel.GetComponent()).color = new Color(0f, 0f, 0f, 0.75f); CanvasGroup component = _panel.GetComponent(); component.interactable = false; component.blocksRaycasts = false; RectTransform component2 = _panel.GetComponent(); component2.anchorMin = new Vector2(0.5f, 0.5f); component2.anchorMax = new Vector2(0.5f, 0.5f); component2.pivot = new Vector2(0.5f, 0.5f); component2.anchoredPosition = Vector2.zero; component2.sizeDelta = new Vector2(260f, 240f); _title = UiRoot.MakeText(_panel.transform, 15, (FontStyle)1); Place(((Component)_title).GetComponent(), 8f); _hint = UiRoot.MakeText(_panel.transform, 11, (FontStyle)0); ((Graphic)_hint).color = new Color(0.75f, 0.75f, 0.75f); _hint.text = "↑↓ move ↵ pick Esc back"; } private static void Place(RectTransform rect, float y) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0f, 1f); rect.anchorMax = new Vector2(1f, 1f); rect.pivot = new Vector2(0f, 1f); rect.anchoredPosition = new Vector2(10f, 0f - y); rect.sizeDelta = new Vector2(-20f, 22f); } private static void Draw() { //IL_01de: Unknown result type (might be due to invalid IL or missing references) _title.text = ((Stack.Count > 1) ? "CREW ‹" : "CREW"); while (Rows.Count < Visible.Count) { Text val = UiRoot.MakeText(_panel.transform, 13, (FontStyle)0); Place(((Component)val).GetComponent(), 32f + (float)(Rows.Count * 22)); Rows.Add(val); } for (int i = 0; i < Rows.Count; i++) { if (i >= Visible.Count) { ((Component)Rows[i]).gameObject.SetActive(false); continue; } ((Component)Rows[i]).gameObject.SetActive(true); Item item = Visible[i]; bool flag = i == _index; string text = ((item.Status == null) ? string.Empty : item.Status()); string text2 = (item.IsSubmenu ? " ▸" : string.Empty); string text3 = ((!item.Destructive) ? (flag ? "#FFD24A" : "#FFFFFF") : (flag ? "#FF6B5A" : "#C4443A")); string text4 = (flag ? "›" : " "); Rows[i].text = "" + text4 + " " + item.Text + text2 + " " + text + ""; } float num = 44f + (float)(Visible.Count * 22); _panel.GetComponent().sizeDelta = new Vector2(260f, num); Place(((Component)_hint).GetComponent(), num - 20f); } } internal static class UiRoot { private static Transform _cached; internal static Transform Current() { if ((Object)(object)_cached != (Object)null) { return _cached; } GameObject val = GameObject.Find("CustomGUIFront") ?? GameObject.Find("_GameMain/LoadingGUI/CustomGUIFront"); _cached = (((Object)(object)val != (Object)null) ? val.transform : (((Object)(object)Hud.instance == (Object)null) ? null : ((Component)Hud.instance).transform)); return _cached; } internal static Text MakeText(Transform parent, int size, FontStyle style) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Text", new Type[3] { typeof(RectTransform), typeof(Text), typeof(Outline) }); val.transform.SetParent(parent, false); Text component = val.GetComponent(); component.font = Font.CreateDynamicFontFromOSFont("Arial", size); component.fontSize = size; component.fontStyle = style; ((Graphic)component).color = Color.white; component.alignment = (TextAnchor)3; component.horizontalOverflow = (HorizontalWrapMode)1; component.verticalOverflow = (VerticalWrapMode)1; component.supportRichText = true; ((Shadow)val.GetComponent()).effectColor = new Color(0f, 0f, 0f, 0.9f); return component; } } } namespace SkeletonCrew.Patches { [HarmonyPatch(typeof(MonsterAI), "UpdateTarget")] internal static class MonsterAiUpdateTargetPatch { private static bool Prefix(MonsterAI __instance, ref bool canHearTarget, ref bool canSeeTarget, out Character __state) { __state = __instance.m_targetCreature; if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value) { return true; } if (SummonRegistry.IsSuppressingCombat(__instance)) { canHearTarget = false; canSeeTarget = false; return false; } if (!SummonRegistry.HasOrderedTarget(__instance)) { if (SummonRegistry.HoldsCrewFocus(__instance)) { canHearTarget = true; canSeeTarget = true; } return true; } canHearTarget = true; canSeeTarget = true; return false; } private static void Postfix(MonsterAI __instance, ref bool canHearTarget, ref bool canSeeTarget, Character __state) { if (SkeletonCrewPlugin.Enabled != null && SkeletonCrewPlugin.Enabled.Value) { if ((Object)(object)__state != (Object)null && (Object)(object)__instance.m_targetCreature == (Object)null) { SummonRegistry.NoteVanillaDrop(__instance, canSeeTarget ? "vanilla dropped it" : "vanilla lost sight of it"); } if (Stance.SuppressesVanillaAcquisition(__instance)) { SummonRegistry.ClearTarget(__instance, "back-line rule"); canHearTarget = false; canSeeTarget = false; } } } } [HarmonyPatch(typeof(Tameable), "UpdateSavedFollowTarget")] internal static class TameableFollowPatch { private static bool Prefix(Tameable __instance) { if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value) { return true; } return !SummonRegistry.IsFollowSuppressed(__instance); } } [HarmonyPatch(typeof(MonsterAI), "UpdateAI")] internal static class MonsterAiDriveOrderPatch { private static bool Prefix(MonsterAI __instance, float dt, ref bool __result) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || !SkeletonCrewPlugin.DriveOrderedMovement.Value) { return true; } if (!SummonRegistry.TryGetOrderedDestination(__instance, out var destination)) { return true; } float num = SummonRegistry.ArriveDistance(__instance); if (Vector3.Distance(((Component)__instance).transform.position, destination) <= num) { SummonRegistry.NotifyArrived(__instance); return true; } if (SummonRegistry.IsSuppressingCombat(__instance)) { SummonRegistry.ClearTarget(__instance, "steering"); } Vector3 val = LavaGuard.Steer(((BaseAI)__instance).m_character, ((Component)__instance).transform.position, destination); ((BaseAI)__instance).MoveTo(dt, val, num, SkeletonCrewPlugin.RunOnOrders.Value); __result = true; return false; } } [HarmonyPatch(typeof(BaseAI), "CanUseAttack")] internal static class BaseAiCanUseAttackPatch { private static void Postfix(BaseAI __instance, ref bool __result) { if (!__result && SkeletonCrewPlugin.Enabled != null && SkeletonCrewPlugin.Enabled.Value && SkeletonCrewPlugin.AttackInWater.Value) { MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if (val != null && (Object)(object)((BaseAI)val).m_character != (Object)null && ((BaseAI)val).m_character.IsSwimming() && SummonRegistry.IsOwnedSummon(((BaseAI)val).m_character)) { __result = true; } } } } [HarmonyPatch(typeof(BaseAI), "MoveTo")] internal static class BaseAiMoveToPatch { private static void Prefix(BaseAI __instance, ref bool run) { if (SkeletonCrewPlugin.Enabled != null && SkeletonCrewPlugin.Enabled.Value && SkeletonCrewPlugin.RunOnOrders.Value && SummonRegistry.ShouldRunToOrder(__instance)) { run = true; } } } [HarmonyPatch(typeof(Tameable), "UnsummonMaxInstances")] internal static class TameableCullWeakestPatch { private static readonly Dictionary Dismissed = new Dictionary(); private const float DismissedMemory = 10f; internal static bool Dismiss(Tameable t) { ZNetView val = (((Object)(object)t?.m_character == (Object)null) ? null : t.m_character.m_nview); if ((Object)(object)val == (Object)null || !val.IsValid()) { return false; } Remember(t); val.InvokeRPC("RPC_UnSummon", Array.Empty()); return true; } internal static bool IsDismissed(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (Dismissed.TryGetValue(id, out var value)) { return Time.time - value < 10f; } return false; } private static void Remember(Tameable t) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) ZNetView val = (((Object)(object)t?.m_character == (Object)null) ? null : t.m_character.m_nview); if ((Object)(object)val == (Object)null || !val.IsValid()) { return; } List list = new List(); foreach (KeyValuePair item in Dismissed) { if (Time.time - item.Value > 10f) { list.Add(item.Key); } } foreach (ZDOID item2 in list) { Dismissed.Remove(item2); } Dismissed[val.GetZDO().m_uid] = Time.time; } private static bool Prefix(Tameable __instance, ref int maxInstances) { //IL_015f: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value) { return true; } Character character = __instance.m_character; MonsterAI monsterAI = __instance.m_monsterAI; if ((Object)(object)character == (Object)null || (Object)(object)monsterAI == (Object)null || (Object)(object)__instance.m_nview == (Object)null || !__instance.m_nview.IsOwner()) { return true; } GameObject followTarget = monsterAI.GetFollowTarget(); object obj; if (!((Object)(object)followTarget == (Object)null)) { Player component = followTarget.GetComponent(); obj = ((component != null) ? component.GetPlayerName() : null); } else { obj = null; } string text = (string)obj; if (string.IsNullOrEmpty(text)) { return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || text != localPlayer.GetPlayerName()) { return true; } maxInstances = SummonRegistry.CapFor(maxInstances); SummonRegistry.NoteCrewCapacity(maxInstances); if (!SkeletonCrewPlugin.ReplaceWeakestSummon.Value) { return true; } List list = new List(); foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsDead() || allCharacter.m_name != character.m_name) { continue; } ZNetView component2 = ((Component)allCharacter).GetComponent(); ZDO val = (((Object)(object)component2 == (Object)null || !component2.IsValid()) ? null : component2.GetZDO()); if (val != null && !(val.GetString(ZDOVars.s_follow, string.Empty) != text) && (!Dismissed.TryGetValue(val.m_uid, out var value) || !(Time.time - value < 10f))) { Tameable component3 = ((Component)allCharacter).GetComponent(); if ((Object)(object)component3 != (Object)null && (Object)(object)((Component)allCharacter).GetComponent() != (Object)null) { list.Add(component3); } } } int num = list.Count - maxInstances; if (num <= 0) { return false; } list.Sort((Tameable a, Tameable b) => a.m_character.GetHealth().CompareTo(b.m_character.GetHealth())); for (int num2 = 0; num2 < num && num2 < list.Count; num2++) { SkeletonCrewPlugin.Log.LogInfo((object)($"Summon cap {maxInstances} reached ({list.Count} counted): dismissing the weakest " + "('" + list[num2].m_character.GetHoverName() + "', " + $"{list[num2].m_character.GetHealth():F0} hp).")); Remember(list[num2]); list[num2].UnSummon(); } if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, "$hud_maxsummonsreached", 0, (Sprite)null); } return false; } } [HarmonyPatch(typeof(Tameable), "RPC_Command")] internal static class TameableCommandPatch { private static void Postfix(Tameable __instance, ZDOID characterID) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.Enabled != null && SkeletonCrewPlugin.Enabled.Value) { SummonRegistry.PreserveOwnerAfterCommand(__instance, characterID); } } } [HarmonyPatch(typeof(Tameable), "UpdateSummon")] internal static class TameableUnsummonPatch { private static float _lastSuppressLog; private static bool Prefix(Tameable __instance) { if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value) { return true; } bool flag = TeleportGuard.Protects(__instance); if (!flag && !SummonRegistry.HasStandingOrder(__instance)) { return true; } if (Time.time - _lastSuppressLog > 10f) { _lastSuppressLog = Time.time; SkeletonCrewPlugin.Log.LogInfo((object)(flag ? "Teleport guard suppressed a vanilla unsummon (distance/logout check)." : "Standing order suppressed a vanilla unsummon (distance check).")); } return false; } } [HarmonyPatch(typeof(Tameable), "GetHoverText")] internal static class TameableHoverTextPatch { private static void Postfix(Tameable __instance, ref string __result) { if (SkeletonCrewPlugin.Enabled != null && SkeletonCrewPlugin.Enabled.Value && SkeletonCrewPlugin.AllowDismiss.Value && SummonRegistry.IsOwnedSummon(__instance.m_character)) { string text = ((ZInput.IsNonClassicFunctionality() && ZInput.IsGamepadActive()) ? "$KEY_AltKeys" : "$KEY_AltPlace"); __result += Localization.instance.Localize("\n[" + text + " + $KEY_Use] Dismiss"); } } } [HarmonyPatch(typeof(Tameable), "Interact")] internal static class TameableInteractPatch { private static bool Prefix(Tameable __instance, Humanoid user, bool hold, bool alt, ref bool __result) { if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || !SkeletonCrewPlugin.AllowDismiss.Value) { return true; } if ((Object)(object)user != (Object)(object)Player.m_localPlayer || !SummonRegistry.IsOwnedSummon(__instance.m_character)) { return true; } bool flag = alt || Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307); if (!flag || hold) { if (flag && hold) { SkeletonCrewPlugin.Log.LogInfo((object)("Dismiss not triggered on '" + __instance.m_character.GetHoverName() + "': hold Alt and TAP use - a held key is a different gesture and is left to vanilla.")); } return true; } ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return true; } SkeletonCrewPlugin.Log.LogInfo((object)("Dismissed '" + __instance.m_character.GetHoverName() + "'.")); TameableCullWeakestPatch.Dismiss(__instance); __result = true; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonDown")] internal static class ZInputMenuSuppressPatch { private static bool Prefix(string name, ref bool __result) { if (!CrewMenu.ConsumesInput || !Owned(name)) { return true; } __result = false; return false; } private static bool Owned(string name) { switch (name) { case "Chat": case "JoyChat": case "Menu": case "JoyMenu": case "Use": case "JoyUse": return true; default: return false; } } } [HarmonyPatch(typeof(Pathfinding), "SetupAgents")] internal static class PathfindingSetupAgentsPatch { internal const AgentType SummonAgent = (AgentType)13; private static void Postfix(Pathfinding __instance) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.UseCustomAgent != null && SkeletonCrewPlugin.UseCustomAgent.Value) { AgentSettings val = __instance.GetSettings((AgentType)5) ?? __instance.GetSettings((AgentType)1); AgentSettings settings = __instance.GetSettings((AgentType)13); if (val == null || settings == null) { SkeletonCrewPlugin.Log.LogWarning((object)"Could not read agent settings; summons will use vanilla pathing."); return; } settings.m_canWalk = val.m_canWalk; settings.m_canSwim = val.m_canSwim; settings.m_avoidWater = val.m_avoidWater; settings.m_swimDepth = val.m_swimDepth; settings.m_areaMask = val.m_areaMask; NavMeshBuildSettings build = val.m_build; ((NavMeshBuildSettings)(ref build)).agentTypeID = ((NavMeshBuildSettings)(ref settings.m_build)).agentTypeID; ((NavMeshBuildSettings)(ref build)).agentClimb = SkeletonCrewPlugin.SummonAgentClimb.Value; settings.m_build = build; SkeletonCrewPlugin.Log.LogInfo((object)($"Summon navmesh agent ready: type={(object)(AgentType)13} id={((NavMeshBuildSettings)(ref build)).agentTypeID} " + $"climb={((NavMeshBuildSettings)(ref build)).agentClimb} (vanilla humanoid {((NavMeshBuildSettings)(ref val.m_build)).agentClimb}) " + $"radius={((NavMeshBuildSettings)(ref build)).agentRadius} height={((NavMeshBuildSettings)(ref build)).agentHeight} slope={((NavMeshBuildSettings)(ref build)).agentSlope}")); } } } [HarmonyPatch(typeof(Character), "Damage")] internal static class CharacterDamagePatch { private static void Postfix(Character __instance, HitData hit) { if (SkeletonCrewPlugin.Enabled != null && SkeletonCrewPlugin.Enabled.Value) { Hazard.Note(__instance, hit); Pressure.Note(__instance, hit); } } private static void Prefix(Character __instance, HitData hit) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || hit == null) { return; } _ = hit.m_damage; if (false) { return; } float num = BloodMagicFactor(); if (!(num <= 0f)) { if (SummonRegistry.IsOwnedSummon(__instance)) { Defend(hit, num, SummonRole.ResistScale((Humanoid)(object)((__instance is Humanoid) ? __instance : null)), __instance); } if (hit.HaveAttacker() && SummonRegistry.IsOwnedSummon(hit.GetAttacker())) { Character attacker = hit.GetAttacker(); Empower(hit, num, SummonRole.DamageScale((Humanoid)(object)((attacker is Humanoid) ? attacker : null)), hit.GetAttacker()); } } } private static void Defend(HitData hit, float skill, float classScale, Character summon) { float num = Mathf.Min(0.95f, Clamp(SkeletonCrewPlugin.ElementalResistAtMaxSkill, skill, summon) * classScale); if (num > 0f) { float num2 = 1f - num; hit.m_damage.m_fire *= num2; hit.m_damage.m_frost *= num2; hit.m_damage.m_lightning *= num2; hit.m_damage.m_poison *= num2; hit.m_damage.m_spirit *= num2; } float num3 = Mathf.Min(0.95f, Clamp(SkeletonCrewPlugin.PhysicalResistAtMaxSkill, skill, summon) * classScale); if (num3 > 0f) { float num4 = 1f - num3; hit.m_damage.m_blunt *= num4; hit.m_damage.m_slash *= num4; hit.m_damage.m_pierce *= num4; } } private static void Empower(HitData hit, float skill, float classScale, Character summon) { float num = ((SkeletonCrewPlugin.DamageBonusAtMaxSkill == null) ? 0f : (SkeletonCrewPlugin.DamageBonusAtMaxSkill.Value * skill)); float num2 = (1f + num) * Progression.CrewMultiplier(summon) * classScale; if (!(num2 <= 1f)) { hit.m_damage.m_damage *= num2; hit.m_damage.m_blunt *= num2; hit.m_damage.m_slash *= num2; hit.m_damage.m_pierce *= num2; hit.m_damage.m_fire *= num2; hit.m_damage.m_frost *= num2; hit.m_damage.m_lightning *= num2; hit.m_damage.m_poison *= num2; hit.m_damage.m_spirit *= num2; } } private static float BloodMagicFactor() { Player localPlayer = Player.m_localPlayer; Skills val = ((localPlayer != null) ? ((Character)localPlayer).GetSkills() : null); if (!((Object)(object)val == (Object)null)) { return val.GetSkillFactor((SkillType)10); } return 0f; } private static float Clamp(ConfigEntry ceiling, float skill, Character summon) { if (ceiling == null) { return 0f; } return Mathf.Clamp(ceiling.Value * skill * Progression.CrewMultiplier(summon), 0f, 0.95f); } } [HarmonyPatch(typeof(SE_Shield), "SetLevel")] internal static class ShieldAbsorbScalingPatch { private static int _lastReportedBracket = -1; private static float _nextWhoLog; private static void Postfix(SE_Shield __instance, float skillLevel) { //IL_0141: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || SkeletonCrewPlugin.ShieldAbsorbBonusAtMaxSkill == null) { return; } float value = SkeletonCrewPlugin.ShieldAbsorbBonusAtMaxSkill.Value; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Object)(object)((StatusEffect)__instance).m_character != (Object)(object)localPlayer && !SummonRegistry.IsOwnedSummon(((StatusEffect)__instance).m_character))) { return; } float ttl = ((StatusEffect)__instance).m_ttl; Lengthen(__instance); if (SkeletonCrewPlugin.VerboseLogging.Value && Time.time >= _nextWhoLog) { _nextWhoLog = Time.time + 5f; ManualLogSource log = SkeletonCrewPlugin.Log; string[] obj = new string[6] { "SE_Shield on '", null, null, null, null, null }; Character character = ((StatusEffect)__instance).m_character; obj[1] = ((character != null) ? character.GetHoverName() : null) ?? ""; obj[2] = "' "; obj[3] = $"(absorb {__instance.m_absorbDamage:0.#}, lasts {((StatusEffect)__instance).m_ttl:0.#}s"; obj[4] = (Mathf.Approximately(ttl, ((StatusEffect)__instance).m_ttl) ? string.Empty : $", was {ttl:0.#}s"); obj[5] = $", skill {skillLevel:0.#})."; log.LogInfo((object)string.Concat(obj)); } Skills skills = ((Character)localPlayer).GetSkills(); if ((Object)(object)skills == (Object)null) { return; } float skillFactor = skills.GetSkillFactor(SkillFor(__instance)); if (!(skillFactor <= 0f) && !(value <= 0f)) { float num = __instance.m_absorbDamage + __instance.m_absorbDamagePerSkillLevel * skillLevel; if (Game.m_worldLevel > 0) { num += __instance.m_absorbDamageWorldLevel * (float)Game.m_worldLevel; } __instance.m_totalAbsorbDamage = num * (1f + value * skillFactor); Report(num, __instance.m_totalAbsorbDamage, skillFactor); } } private static void Lengthen(SE_Shield shield) { float num = ((SkeletonCrewPlugin.HealerWardSeconds == null) ? 0f : SkeletonCrewPlugin.HealerWardSeconds.Value); if (num > 0f && ((StatusEffect)shield).m_ttl < num) { ((StatusEffect)shield).m_ttl = num; } } private static SkillType SkillFor(SE_Shield shield) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) if ((int)shield.m_levelUpSkillOnBreak != 0) { return shield.m_levelUpSkillOnBreak; } return (SkillType)10; } private static void Report(float before, float after, float factor) { int num = Mathf.FloorToInt(factor * 10f); if (num != _lastReportedBracket) { _lastReportedBracket = num; SkeletonCrewPlugin.Log.LogInfo((object)($"Shield absorb: {before:F0} -> {after:F0} " + $"(skill {factor * 100f:F0}, x{SkeletonCrewPlugin.ShieldAbsorbBonusAtMaxSkill.Value:F2} at 100).")); } } } internal static class SummonCost { private static readonly Dictionary SummonPrefabs = new Dictionary(); private static readonly HashSet Reported = new HashSet(); internal static bool Applies(Attack attack, float cost, float multiplier) { if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value) { return false; } if (Mathf.Approximately(multiplier, 1f) || cost <= 0f) { return false; } if ((Object)(object)attack.m_character == (Object)null || (Object)(object)attack.m_character != (Object)(object)Player.m_localPlayer) { return false; } return IsSummonAttack(attack); } private static bool IsSummonAttack(Attack attack) { if (!Spawns(attack.m_attackProjectile)) { return Spawns(attack.m_spawnOnTrigger); } return true; } internal static bool IsOurSummonCast(Attack attack) { if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || (Object)(object)attack?.m_character == (Object)null || (Object)(object)attack.m_character != (Object)(object)Player.m_localPlayer || !IsSummonAttack(attack)) { return false; } NoteCapacityFromStaff(attack); return true; } private static void NoteCapacityFromStaff(Attack attack) { if (attack.m_weapon != null && SkeletonCrewPlugin.SummonCapBonus != null) { SpawnAbility val = AbilityOn(attack.m_attackProjectile) ?? AbilityOn(attack.m_spawnOnTrigger); if ((Object)(object)val != (Object)null && val.m_setMaxInstancesFromWeaponLevel && attack.m_weapon.m_quality > 0) { SummonRegistry.NoteCrewCapacity(SummonRegistry.CapFor(attack.m_weapon.m_quality)); } } } private static SpawnAbility AbilityOn(GameObject prefab) { if (!((Object)(object)prefab == (Object)null)) { return prefab.GetComponentInChildren(true); } return null; } internal static bool Spawns(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return false; } if (SummonPrefabs.TryGetValue(prefab, out var value)) { return value; } bool flag = (Object)(object)prefab.GetComponentInChildren(true) != (Object)null; SummonPrefabs[prefab] = flag; return flag; } internal static void Report(Attack attack, string currency, float before, float after, float mult) { string text = attack.m_weapon?.m_shared?.m_name; if (!string.IsNullOrEmpty(text) && Reported.Add(text + "/" + currency)) { SkeletonCrewPlugin.Log.LogInfo((object)($"Summon {currency} cost for '{text}': {before:F1} -> {after:F1} " + $"(x{mult:F2}, after vanilla's skill discount).")); } } } [HarmonyPatch(typeof(Attack), "GetAttackHealth")] internal static class AttackHealthCostPatch { private static void Postfix(Attack __instance, ref float __result) { if (SkeletonCrewPlugin.SummonHealthCostMultiplier != null) { if (SummonCost.IsOurSummonCast(__instance)) { SummonGroup.NoteSingleCost(0f, __result); } float num = SkeletonCrewPlugin.SummonHealthCostMultiplier.Value * SummonGroup.CostFactor(); if (SummonCost.Applies(__instance, __result, num)) { float before = __result; __result *= num; SummonCost.Report(__instance, "health", before, __result, num); } } } } [HarmonyPatch(typeof(Attack), "GetAttackEitr")] internal static class AttackEitrCostPatch { private static void Postfix(Attack __instance, ref float __result) { if (SkeletonCrewPlugin.SummonEitrCostMultiplier != null) { if (SummonCost.IsOurSummonCast(__instance)) { SummonGroup.NoteSingleCost(__result, 0f); } float num = SkeletonCrewPlugin.SummonEitrCostMultiplier.Value * SummonGroup.CostFactor(); if (SummonCost.Applies(__instance, __result, num)) { float before = __result; __result *= num; SummonCost.Report(__instance, "Eitr", before, __result, num); } } } } [HarmonyPatch(typeof(SpawnAbility), "Spawn")] internal static class SpawnAbilityGroupPatch { private static void Prefix(SpawnAbility __instance) { if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || !SummonGroup.IsArmed || (Object)(object)__instance.m_owner == (Object)null || (Object)(object)__instance.m_owner != (Object)(object)Player.m_localPlayer) { return; } if (__instance.m_setMaxInstancesFromWeaponLevel && __instance.m_weapon != null) { SummonRegistry.NoteCrewCapacity(SummonRegistry.CapFor(__instance.m_weapon.m_quality)); } int num = SummonGroup.Size(); if (num <= 0) { __instance.m_minToSpawn = 0; __instance.m_maxToSpawn = 1; if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, SummonGroup.ArmedName + " is already up", 0, (Sprite)null, false); } return; } SkeletonCrewPlugin.Log.LogInfo((object)("Group '" + SummonGroup.ArmedName + "': " + SummonGroup.Explain())); SummonGroup.BeginCast(num); if (num > 1) { __instance.m_minToSpawn = num; __instance.m_maxToSpawn = num + 1; SkeletonCrewPlugin.Log.LogInfo((object)($"Raising the '{SummonGroup.ArmedName}' group: {num} of " + $"{SummonGroup.Remaining} summon(s) from one cast.")); if (num < SummonGroup.Remaining && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, $"Raised {num} of {SummonGroup.Remaining} - {SummonGroup.ShortfallReason()}", 0, (Sprite)null, false); } } } } internal enum SummonPreference { Melee, Archer, Defender, Healer, FireMage, IceMage, Adaptive } [HarmonyPatch(typeof(Humanoid), "GiveDefaultItems")] internal static class HumanoidGiveDefaultItemsPatch { private static void Prefix(Humanoid __instance, out GameObject[] __state) { __state = null; if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || !LooksLikeSummon(__instance)) { return; } SummonPreference summonPreference = SummonClass.Wanted(__instance); if (SkeletonCrewPlugin.VerboseLogging.Value) { List list = new List(); if (__instance.m_randomWeapon != null) { GameObject[] randomWeapon = __instance.m_randomWeapon; foreach (GameObject val in randomWeapon) { list.Add(((Object)(object)val == (Object)null) ? "" : (((Object)val).name + (IsRanged(val) ? " [ranged]" : " [melee]"))); } } SkeletonCrewPlugin.Log.LogInfo((object)string.Format("LOADOUT '{0}': want={1} pool=[{2}]", ((Object)__instance).name, summonPreference, string.Join(", ", list.ToArray()))); } if (SummonClass.ItemsFor(summonPreference).Length != 0) { __state = __instance.m_randomWeapon; __instance.m_randomWeapon = (GameObject[])(object)new GameObject[0]; } else { if (summonPreference == SummonPreference.Adaptive || __instance.m_randomWeapon == null || __instance.m_randomWeapon.Length < 2) { return; } List list2 = new List(); GameObject[] randomWeapon = __instance.m_randomWeapon; foreach (GameObject val2 in randomWeapon) { if (!((Object)(object)val2 == (Object)null) && IsRanged(val2) == (summonPreference == SummonPreference.Archer)) { list2.Add(val2); } } if (list2.Count == 0) { SkeletonCrewPlugin.Log.LogWarning((object)(string.Format("Summon preference '{0}' ignored: '{1}' has no {2} ", summonPreference, ((Object)__instance).name, (summonPreference == SummonPreference.Archer) ? "ranged" : "melee") + "weapon in its pool, so vanilla's roll stands.")); return; } __state = __instance.m_randomWeapon; __instance.m_randomWeapon = list2.ToArray(); } } private static void Postfix(Humanoid __instance, GameObject[] __state) { if (__state != null) { __instance.m_randomWeapon = __state; } if (SkeletonCrewPlugin.Enabled != null && SkeletonCrewPlugin.Enabled.Value && LooksLikeSummon(__instance)) { SummonPreference summonPreference = SummonClass.Wanted(__instance); if (summonPreference == SummonPreference.Adaptive) { GiveBothWeapons(__instance); } else { GiveClassItems(__instance, summonPreference); } SummonClass.Remember(__instance, summonPreference); float num = Devotion.Score(Player.m_localPlayer); float num2 = Devotion.FactorFor(num); Devotion.Stamp(__instance, num2); if (SkeletonCrewPlugin.DevotionScaling.Value && (Object)(object)Player.m_localPlayer != (Object)null) { SkeletonCrewPlugin.Log.LogInfo((object)($"Raised as {summonPreference} at devotion {num2:0.00} " + $"(eitr gear {Player.m_localPlayer.GetEquipmentEitrRegenModifier():0.00}, " + $"score {num:0.00}).")); } SummonGroup.Done(__instance); } } private static void GiveClassItems(Humanoid h, SummonPreference pref) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Invalid comparison between Unknown and I4 string[] array = SummonClass.ItemsFor(pref); if (array.Length == 0 || (Object)(object)ObjectDB.instance == (Object)null) { return; } Inventory inventory = h.GetInventory(); foreach (ItemData item in new List(inventory.GetAllItems())) { if (item == null) { continue; } if (!WeaponChoice.IsWeapon(item)) { SharedData shared = item.m_shared; if (shared == null || (int)shared.m_itemType != 5) { continue; } } h.UnequipItem(item, false); inventory.RemoveItem(item); } string animation = GivenAnimation(pref); float aiRange = GivenRange(pref); GameObject val = GivenAttackDonor(pref); ItemData val2 = null; string[] array2 = array; foreach (string text in array2) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text); if ((Object)(object)itemPrefab == (Object)null) { SkeletonCrewPlugin.Log.LogWarning((object)$"{pref}: no item prefab named '{text}'. Run skcrew_weapons to find the right one."); continue; } h.GiveDefaultItem(itemPrefab); foreach (ItemData allItem in h.GetInventory().GetAllItems()) { if (allItem != null && !((Object)(object)allItem.m_dropPrefab != (Object)(object)itemPrefab)) { if ((Object)(object)val != (Object)null && WeaponChoice.IsWeapon(allItem)) { WeaponRebind.Graft(allItem, val); } WeaponRebind.MakeCreatureUsable(allItem, aiRange); WeaponRebind.To(allItem, animation, aiRange); if (val2 == null && WeaponChoice.IsWeapon(allItem)) { val2 = allItem; } break; } } } if (val2 != null) { h.EquipItem(val2, false); } } private static GameObject GivenAttackDonor(SummonPreference pref) { string text = SkeletonCrewPlugin.ClassAttackFrom(pref)?.Value; if (string.IsNullOrEmpty(text) || (Object)(object)ObjectDB.instance == (Object)null) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text.Trim()); if ((Object)(object)itemPrefab == (Object)null) { SkeletonCrewPlugin.Log.LogWarning((object)($"{pref}: no item prefab named '{text}' to take an attack from. " + "Run skcrew_weapons to find the right one.")); } return itemPrefab; } private static string GivenAnimation(SummonPreference pref) { return SkeletonCrewPlugin.ClassAnimation(pref)?.Value ?? string.Empty; } private static float GivenRange(SummonPreference pref) { return SkeletonCrewPlugin.ClassRange(pref)?.Value ?? 0f; } private static void GiveBothWeapons(Humanoid h) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Invalid comparison between Unknown and I4 //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 if (h.m_randomWeapon == null || h.m_randomWeapon.Length < 2) { return; } bool flag = false; bool flag2 = false; foreach (ItemData allItem in h.GetInventory().GetAllItems()) { if (allItem?.m_shared != null && ((int)allItem.m_shared.m_itemType == 3 || (int)allItem.m_shared.m_itemType == 14 || (int)allItem.m_shared.m_itemType == 4)) { if (IsRanged(allItem.m_shared)) { flag = true; } else { flag2 = true; } } } if (!(flag && flag2)) { GameObject val = FindWeapon(h.m_randomWeapon, !flag); if (!((Object)(object)val == (Object)null)) { h.GiveDefaultItem(val); LogLoadout(h); } } } private static GameObject FindWeapon(GameObject[] pool, bool wantRanged) { foreach (GameObject val in pool) { if ((Object)(object)val != (Object)null && IsRanged(val) == wantRanged) { return val; } } return null; } private static void LogLoadout(Humanoid h) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (ItemData allItem in h.GetInventory().GetAllItems()) { if (allItem?.m_shared != null) { list.Add($"{allItem.m_shared.m_name} x{allItem.m_stack} ({allItem.m_shared.m_itemType})"); } } SkeletonCrewPlugin.Log.LogInfo((object)("Adaptive loadout for '" + ((Object)h).name + "': " + string.Join(", ", list.ToArray()))); } private static bool IsRanged(SharedData shared) { return WeaponChoice.IsRanged(shared); } private static bool IsRanged(GameObject weaponPrefab) { return WeaponChoice.IsRanged(weaponPrefab.GetComponent()?.m_itemData?.m_shared); } private static bool LooksLikeSummon(Humanoid h) { return SummonRegistry.IsSummonPrefab((Character)(object)h); } } [HarmonyPatch(typeof(Humanoid), "EquipBestWeapon")] internal static class KeepSingleWeaponEquippedPatch { private static bool Prefix(Humanoid __instance) { if (SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || (Object)(object)__instance == (Object)null || !IsSummonPrefab(__instance)) { return true; } ItemData val = null; foreach (ItemData allItem in __instance.GetInventory().GetAllItems()) { if (allItem != null && WeaponChoice.IsWeapon(allItem)) { if (val != null) { return true; } val = allItem; } } if (val == null) { return true; } if (__instance.GetCurrentWeapon() != val) { __instance.EquipItem(val, false); } return false; } private static bool IsSummonPrefab(Humanoid h) { return SummonRegistry.IsSummonPrefab((Character)(object)h); } } [HarmonyPatch(typeof(Player), "TeleportTo")] internal static class TeleportPatch { private static void Prefix(Player __instance, Vector3 pos) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || SkeletonCrewPlugin.Enabled == null || !SkeletonCrewPlugin.Enabled.Value || !SkeletonCrewPlugin.TeleportSummons.Value) { return; } int num = 0; int value = SkeletonCrewPlugin.MaxTeleportSummons.Value; TeleportGuard.Begin(SkeletonCrewPlugin.TeleportGuardSeconds.Value); foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (num >= value) { break; } if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead() && !SummonRegistry.IsStationed(item.Id)) { TeleportGuard.Protect(item.Id); Recall.BringTo(item, pos, __instance, "teleport", 0f); SummonRegistry.GrantRecallGrace(item.Id); SummonRegistry.ClearMove(item.Id); num++; } } if (num > 0) { SkeletonCrewPlugin.Log.LogInfo((object)$"Teleport: brought {num} summon(s) through with you."); } } } } namespace SkeletonCrew.Jobs { internal sealed class HarvestJob { private float _nextSwing; private bool _done; private int _lastAreaCount = int.MinValue; private float _lastProgressAt; internal HarvestTarget Target { get; private set; } internal bool IsFinished { get { if (!_done && Target != null) { return Target.IsGone; } return true; } } internal static float WorkRange => SkeletonCrewPlugin.HarvestReach.Value; internal HarvestJob(HarvestTarget target) { Target = target; } private void Complete() { _done = true; } private static bool EnsureMeleeEquipped(Character worker) { Humanoid val = (Humanoid)(object)((worker is Humanoid) ? worker : null); if (val != null) { return WeaponChoice.EquipMelee(val); } return true; } private bool HasStalled() { int liveAreaCount = Target.LiveAreaCount; if (liveAreaCount < 0) { return false; } if (liveAreaCount != _lastAreaCount) { _lastAreaCount = liveAreaCount; _lastProgressAt = Time.time; return false; } if (Time.time - _lastProgressAt < SkeletonCrewPlugin.HarvestStallSeconds.Value) { return false; } SkeletonCrewPlugin.Log.LogInfo((object)($"Harvest: giving up on '{Target.Name}' - {liveAreaCount} area(s) left but no progress in " + $"{SkeletonCrewPlugin.HarvestStallSeconds.Value:F0}s, likely out of reach.")); Complete(); return true; } internal void Tick(Character worker) { //IL_001e: 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_0061: 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_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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) if (IsFinished || (Object)(object)worker == (Object)null) { return; } Vector3 val = Target.SurfacePoint(((Component)worker).transform.position); if (Target.HasNoWorkableSurface) { SkeletonCrewPlugin.Log.LogInfo((object)("Harvest: nothing left to work on '" + Target.Name + "'.")); Complete(); return; } Vector3 val2 = val - ((Component)worker).transform.position; Vector2 val3 = new Vector2(val2.x, val2.z); if (((Vector2)(ref val3)).magnitude > WorkRange || Mathf.Abs(val2.y) > SkeletonCrewPlugin.HarvestReachVertical.Value || Time.time < _nextSwing || HasStalled() || (Target.Kind != HarvestKind.Pick && !EnsureMeleeEquipped(worker))) { return; } _nextSwing = Time.time + SkeletonCrewPlugin.HarvestSwingSeconds.Value; if (Target.Kind == HarvestKind.Pick) { SkeletonCrewPlugin.Log.LogInfo((object)(Target.Pick() ? ("Picked '" + Target.Name + "'.") : ("Could not pick '" + Target.Name + "'."))); Complete(); return; } int num = ToolTier.BestFor(Target.Kind); if (num < Target.MinToolTier) { SkeletonCrewPlugin.Log.LogInfo((object)($"Harvest refused '{Target.Name}': needs tool tier {Target.MinToolTier}, " + $"your best {Target.Kind} tool is tier {num}.")); Complete(); return; } Target.Damage(BuildHit(worker, num)); Transform transform = ((Component)worker).transform; Vector3 val4 = Vector3.ProjectOnPlane(Target.Position - ((Component)worker).transform.position, Vector3.up); transform.rotation = Quaternion.LookRotation(((Vector3)(ref val4)).normalized); Humanoid val5 = (Humanoid)(object)((worker is Humanoid) ? worker : null); if (val5 != null) { ((Character)val5).StartAttack((Character)null, false); } } private HitData BuildHit(Character worker, int tier) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //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_0068: Expected O, but got Unknown Vector3 val = Target.SurfacePoint(((Component)worker).transform.position); HitData val2 = new HitData { m_toolTier = (short)tier, m_point = val }; Vector3 val3 = val - ((Component)worker).transform.position; val2.m_dir = ((Vector3)(ref val3)).normalized; val2.m_attacker = worker.GetZDOID(); val2.m_hitCollider = Target.Surface; HitData val4 = val2; float value = SkeletonCrewPlugin.HarvestDamage.Value; if (Target.Kind == HarvestKind.Chop) { val4.m_damage.m_chop = value; } else { val4.m_damage.m_pickaxe = value; } return val4; } } internal static class HarvestScan { private static readonly Collider[] Buffer = (Collider[])(object)new Collider[512]; private static readonly HashSet Seen = new HashSet(); private static float _lastFullWarning; private const float LogRemainsRadius = 4f; internal static List Gather(Vector3 centre, float radius, HarvestKind kind, GameObject exclude = null) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) List list = new List(); int num = Physics.OverlapSphereNonAlloc(centre, radius, Buffer); if (num >= Buffer.Length && Time.time - _lastFullWarning > 30f) { _lastFullWarning = Time.time; SkeletonCrewPlugin.Log.LogWarning((object)($"harvest scan full ({Buffer.Length} colliders within {radius:F0}m) - " + "some targets may have been missed. Raise the buffer if this repeats.")); } Seen.Clear(); for (int i = 0; i < num; i++) { HarvestTarget harvestTarget = HarvestTarget.Resolve(Buffer[i]); if (harvestTarget != null && !harvestTarget.IsGone && harvestTarget.Kind == kind && (!((Object)(object)exclude != (Object)null) || harvestTarget.Root != exclude) && Seen.Add(harvestTarget.Root)) { list.Add(harvestTarget); } } return list; } internal static HarvestTarget NextNear(HarvestTarget finished, Vector3 anchor, float radius, Vector3 from, Dictionary claimed) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (finished == null) { return null; } List list = Gather(anchor, radius, finished.Kind, finished.Root); HarvestTarget result = null; float num = float.MaxValue; foreach (HarvestTarget item in list) { if (Taken(claimed, item.Root) < item.WorkerCapacity && (finished.Kind != HarvestKind.Pick || finished.PrefabName.Length <= 0 || !(item.PrefabName != finished.PrefabName))) { Vector3 val = item.Position - from; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; result = item; } } } return result; } internal static int Taken(Dictionary claimed, GameObject root) { if (claimed == null || !((Object)(object)root != (Object)null) || !claimed.TryGetValue(root, out var value)) { return 0; } return value; } internal static HarvestTarget LogFor(HarvestTarget finished, Dictionary claimed) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) if (finished == null || (!finished.IsStandingTree && !finished.IsLog)) { return null; } if (!SkeletonCrewPlugin.HarvestTrees.Value || finished.LastPosition == Vector3.zero) { return null; } float radius = (finished.IsStandingTree ? SkeletonCrewPlugin.HarvestLogSearchRadius.Value : 4f); HarvestTarget result = null; float num = float.MaxValue; foreach (HarvestTarget item in Gather(finished.LastPosition, radius, HarvestKind.Chop, finished.Root)) { if (item.IsLog && Taken(claimed, item.Root) < item.WorkerCapacity) { Vector3 val = item.Position - finished.LastPosition; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; result = item; } } } return result; } } internal enum HarvestKind { None, Chop, Pickaxe, Pick } internal sealed class HarvestTarget { private MineRock5 _rock; private TreeLog _log; private TreeBase _tree; private Destructible _destructible; private Pickable _pickable; private WearNTear _wear; internal const float StandoffFraction = 0.4f; internal HarvestKind Kind { get; private set; } internal GameObject Root { get; private set; } internal int MinToolTier { get; private set; } internal Collider Surface { get; private set; } internal Vector3 LastPosition { get; private set; } internal bool IsGone => (Object)(object)Root == (Object)null; internal bool IsStandingTree => (Object)(object)_tree != (Object)null; internal bool IsLog => (Object)(object)_log != (Object)null; internal bool HasNoWorkableSurface { get { if ((Object)(object)_rock != (Object)null) { return (Object)(object)Surface == (Object)null; } return false; } } internal int WorkerCapacity { get { int liveAreaCount = LiveAreaCount; if (liveAreaCount <= 0) { return 1; } return Mathf.Max(1, liveAreaCount / Mathf.Max(1, SkeletonCrewPlugin.HarvestAreasPerWorker.Value)); } } internal int LiveAreaCount { get { if ((Object)(object)_rock == (Object)null || _rock.m_hitAreas == null) { return -1; } int num = 0; foreach (HitArea hitArea in _rock.m_hitAreas) { if (hitArea != null && hitArea.m_health > 0f) { num++; } } return num; } } internal Vector3 Position { get { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Root != (Object)null) { LastPosition = Root.transform.position; } return LastPosition; } } internal string Name { get { if (!((Object)(object)Root == (Object)null)) { return ((Object)Root).name; } return ""; } } internal string PrefabName { get { if ((Object)(object)Root == (Object)null) { return string.Empty; } string name = ((Object)Root).name; int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num >= 0) { return name.Substring(0, num); } return name; } } internal static string LastRefusal { get; private set; } private bool IsNetworked { get { if ((Object)(object)Root == (Object)null) { return false; } ZNetView componentInParent = Root.GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return componentInParent.IsValid(); } return false; } } internal Vector3 SurfacePoint(Vector3 from) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0024: Unknown result type (might be due to invalid IL or missing references) RefreshSurface(from); if (!((Object)(object)Surface == (Object)null)) { Bounds bounds = Surface.bounds; return ((Bounds)(ref bounds)).ClosestPoint(from); } return Position; } internal Vector3 WorkPosition(Vector3 from) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) Vector3 val = SurfacePoint(from); Vector3 val2 = from - val; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f) { return val; } return val + ((Vector3)(ref val2)).normalized * (SkeletonCrewPlugin.HarvestReach.Value * 0.4f); } private void RefreshSurface(Vector3 from) { //IL_000f: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_rock != (Object)null) { RefreshRockArea(from); } else { if ((Object)(object)Surface != (Object)null && Surface.enabled && ((Component)Surface).gameObject.activeInHierarchy) { return; } Surface = null; if ((Object)(object)Root == (Object)null) { return; } float num = float.MaxValue; Collider[] componentsInChildren = Root.GetComponentsInChildren(); foreach (Collider val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && val.enabled && !val.isTrigger && ((Component)val).gameObject.activeInHierarchy) { Bounds bounds = val.bounds; Vector3 val2 = ((Bounds)(ref bounds)).ClosestPoint(from) - from; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; Surface = val; } } } } } private void RefreshRockArea(Vector3 from) { //IL_0068: 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_0071: 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) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) Surface = null; if ((Object)(object)_rock == (Object)null || _rock.m_hitAreas == null) { return; } float num = float.MaxValue; foreach (HitArea hitArea in _rock.m_hitAreas) { if (hitArea != null && !(hitArea.m_health <= 0f) && !((Object)(object)hitArea.m_collider == (Object)null)) { Bounds bounds = hitArea.m_collider.bounds; Vector3 val = ((Bounds)(ref bounds)).ClosestPoint(from) - from; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; Surface = hitArea.m_collider; } } } } internal HarvestTarget Copy() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) return new HarvestTarget { Root = Root, Kind = Kind, MinToolTier = MinToolTier, Surface = Surface, LastPosition = LastPosition, _rock = _rock, _log = _log, _tree = _tree, _destructible = _destructible, _pickable = _pickable, _wear = _wear }; } internal static HarvestTarget Resolve(Collider collider) { LastRefusal = null; if ((Object)(object)collider == (Object)null) { return null; } if (!SkeletonCrewPlugin.HarvestEnabled.Value) { LastRefusal = "harvesting is switched off"; return null; } WearNTear componentInParent = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && IsPlayerBuilt(componentInParent)) { LastRefusal = "that is player-built"; return null; } HarvestTarget harvestTarget = ResolveKind(collider, componentInParent); if (harvestTarget != null && harvestTarget.Kind != HarvestKind.Chop && IsInsidePlayerBase(collider)) { LastRefusal = "inside a player base (Harvest.ProtectPlayerBase)"; return null; } return harvestTarget; } private static HarvestTarget ResolveKind(Collider collider, WearNTear wear) { //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) Pickable componentInParent = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { if (!SkeletonCrewPlugin.HarvestPickables.Value || !componentInParent.CanBePicked()) { return null; } return new HarvestTarget { Root = ((Component)componentInParent).gameObject, _pickable = componentInParent, Surface = collider, Kind = HarvestKind.Pick }; } MineRock5 componentInParent2 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { if (!SkeletonCrewPlugin.HarvestRock.Value) { return null; } return new HarvestTarget { Root = ((Component)componentInParent2).gameObject, _rock = componentInParent2, Surface = collider, Kind = HarvestKind.Pickaxe, MinToolTier = componentInParent2.m_minToolTier }; } TreeLog componentInParent3 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null) { if (!SkeletonCrewPlugin.HarvestTrees.Value) { LastRefusal = "wood harvesting is off (Harvest.Trees)"; return null; } return new HarvestTarget { Root = ((Component)componentInParent3).gameObject, _log = componentInParent3, Surface = collider, Kind = HarvestKind.Chop, MinToolTier = componentInParent3.m_minToolTier }; } TreeBase componentInParent4 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent4 != (Object)null) { if (!SkeletonCrewPlugin.HarvestTrees.Value) { return null; } return new HarvestTarget { Root = ((Component)componentInParent4).gameObject, _tree = componentInParent4, Surface = collider, Kind = HarvestKind.Chop, MinToolTier = componentInParent4.m_minToolTier }; } Destructible componentInParent5 = ((Component)collider).GetComponentInParent(); if ((Object)(object)componentInParent5 != (Object)null && SkeletonCrewPlugin.HarvestDestructibles.Value) { return new HarvestTarget { Root = ((Component)componentInParent5).gameObject, _destructible = componentInParent5, Surface = collider, Kind = PickKind(componentInParent5.m_damages), MinToolTier = componentInParent5.m_minToolTier }; } if ((Object)(object)wear != (Object)null && SkeletonCrewPlugin.HarvestStructures.Value) { return new HarvestTarget { Root = ((Component)wear).gameObject, _wear = wear, Surface = collider, Kind = PickKind(wear.m_damages), MinToolTier = wear.m_minToolTier }; } return null; } private static bool IsInsidePlayerBase(Collider collider) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.ProtectPlayerBase.Value) { return false; } Bounds bounds = collider.bounds; return (Object)(object)EffectArea.IsPointInsideArea(((Bounds)(ref bounds)).center, (Type)4, 0f) != (Object)null; } private static bool IsPlayerBuilt(WearNTear wear) { Piece val = ((Component)wear).GetComponent() ?? ((Component)wear).GetComponentInParent(); if ((Object)(object)val != (Object)null) { return val.IsPlacedByPlayer(); } return false; } private static HarvestKind PickKind(DamageModifiers damages) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: 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_000e: Unknown result type (might be due to invalid IL or missing references) if (!Accepts(damages.m_chop)) { if (!Accepts(damages.m_pickaxe)) { return HarvestKind.None; } return HarvestKind.Pickaxe; } return HarvestKind.Chop; } private static bool Accepts(DamageModifier mod) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)mod != 3) { return (int)mod != 4; } return false; } internal bool Pick() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)_pickable == (Object)null || (Object)(object)localPlayer == (Object)null || !_pickable.CanBePicked()) { return false; } return _pickable.Interact((Humanoid)(object)localPlayer, false, false); } internal void Damage(HitData hit) { if (IsNetworked) { if ((Object)(object)_rock != (Object)null) { _rock.Damage(hit); } else if ((Object)(object)_log != (Object)null) { _log.Damage(hit); } else if ((Object)(object)_tree != (Object)null) { _tree.Damage(hit); } else if ((Object)(object)_destructible != (Object)null) { _destructible.Damage(hit); } else if ((Object)(object)_wear != (Object)null) { _wear.Damage(hit); } } } } internal static class ToolTier { internal static int BestFor(HarvestKind kind) { Player localPlayer = Player.m_localPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val == null) { return 0; } int num = 0; foreach (ItemData allItem in val.GetAllItems()) { SharedData val2 = allItem?.m_shared; if (val2 != null && ((kind == HarvestKind.Chop) ? (val2.m_damages.m_chop > 0f) : (val2.m_damages.m_pickaxe > 0f)) && val2.m_toolTier > num) { num = val2.m_toolTier; } } return num; } } } namespace SkeletonCrew.Diagnostics { internal static class CrewTrace { private static float _next; internal static void Tick() { if (!SkeletonCrewPlugin.DeepTrace.Value || Time.time < _next) { return; } _next = Time.time + Mathf.Max(1f, SkeletonCrewPlugin.DeepTraceInterval.Value); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || SummonRegistry.Owned.Count == 0) { return; } SkeletonCrewPlugin.Log.LogInfo((object)($"TRACE crew={SummonRegistry.Owned.Count} stance={Stance.Current} " + $"focus='{Name(CrewFocus.Current)}' pulling={CrewFocus.Pulling} " + $"ownerHp={((Character)localPlayer).GetHealth():0}/{((Character)localPlayer).GetMaxHealth():0} " + $"ownerPress={Pressure.Incoming((Character)(object)localPlayer):0.#} " + $"eitrGear={localPlayer.GetEquipmentEitrRegenModifier():0.00} " + $"devotionNow={Devotion.FactorFor(Devotion.Score(localPlayer)):0.00}")); foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead()) { SkeletonCrewPlugin.Log.LogInfo((object)(" " + Describe(item, localPlayer))); } } } private static string Describe(SummonRegistry.Summon s, Player player) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) Character character = s.Character; MonsterAI aI = s.AI; Character val = (((Object)(object)aI == (Object)null) ? null : ((BaseAI)aI).GetTargetCreature()); return $"'{character.GetHoverName()}' {SummonRegistry.RoleOf(s.Id)}/{Class(character)} " + $"intent={SummonRegistry.IntentFor(s.Id)} " + "dest=" + Where(SummonRegistry.IntentFor(s.Id)) + " target='" + Name(val) + "'" + Gap(character, val) + " " + $"hp={character.GetHealth():0}/{character.GetMaxHealth():0} " + $"fromOwner={Vector3.Distance(((Component)character).transform.position, ((Component)player).transform.position):0}m " + $"press={Pressure.Incoming(character):0.#} " + "lava=" + (character.InLava() ? "IN" : (character.AboveOrInLava() ? "above" : "no")) + " hazard=" + (Hazard.Hurting(character) ? "HURTING" : "no") + " warded=" + Warded(character) + " follow=" + (((Object)(object)aI != (Object)null && (Object)(object)aI.GetFollowTarget() != (Object)null) ? "yes" : "no") + " alerted=" + (((Object)(object)aI != (Object)null && ((BaseAI)aI).IsAlerted()) ? "yes" : "no") + " weapon=" + Weapon(character) + " " + $"devotion={Devotion.Factor(character):0.00} " + "churn=" + SummonRegistry.Churn(s.Id); } private static string Gap(Character c, Character target) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null)) { return $"@{Vector3.Distance(((Component)c).transform.position, ((Component)target).transform.position):0}m"; } return string.Empty; } private static string Where(Intent intent) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (intent.Destination.HasValue) { return $"{intent.Destination.Value.x:0},{intent.Destination.Value.z:0}"; } return "-"; } private static string Class(Character c) { return SummonClass.Of((Humanoid)(object)((c is Humanoid) ? c : null))?.ToString() ?? "unarmed"; } private static string Warded(Character c) { SEMan sEMan = c.GetSEMan(); if (sEMan == null) { return "?"; } if (!sEMan.HaveStatusEffect(SEMan.s_statusEffectBurning)) { if (sEMan.GetStatusEffects().Count <= 0) { return "no"; } return "yes"; } return "burning"; } private static string Weapon(Character c) { Character obj = ((c is Humanoid) ? c : null); ItemData val = ((obj != null) ? ((Humanoid)obj).GetCurrentWeapon() : null); if (!((Object)(object)val?.m_dropPrefab == (Object)null)) { return ((Object)val.m_dropPrefab).name; } return "NONE"; } private static string Name(Character c) { if (!((Object)(object)c == (Object)null)) { return c.GetHoverName(); } return ""; } } internal static class SummonRecon { [HarmonyPatch(typeof(Humanoid), "EquipBestWeapon")] internal static class EquipBestWeaponPinPatch { private static bool Prefix(Humanoid __instance) { //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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (Pinned == ZDOID.None || (Object)(object)__instance == (Object)null) { return true; } ZNetView nview = ((Character)__instance).m_nview; ZDO val = (((Object)(object)nview == (Object)null || !nview.IsValid()) ? null : nview.GetZDO()); if (val != null) { return val.m_uid != Pinned; } return true; } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class TerminalInitPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; public static ConsoleEvent <>9__1_1; public static ConsoleEvent <>9__1_2; public static ConsoleEvent <>9__1_3; internal void b__1_0(ConsoleEventArgs args) { Run(args.Context); } internal void b__1_1(ConsoleEventArgs args) { ListPrefabs(args.Context, (args.Args.Length > 1) ? args.Args[1] : null); } internal void b__1_2(ConsoleEventArgs args) { ListWeapons(args.Context, (args.Args.Length > 1) ? args.Args[1] : null); } internal void b__1_3(ConsoleEventArgs args) { GiveWeapon(args.Context, (args.Args.Length > 1) ? args.Args[1] : null, (args.Args.Length > 2) ? args.Args[2] : null); } } private static bool _registered; private static void Postfix() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_00e8: 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_00df: Expected O, but got Unknown if (_registered) { return; } _registered = true; object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { Run(args.Context); }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("skcrew_recon", "SkeletonCrew: list every item that summons a creature, and whether the crew manages it.", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__1_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { ListPrefabs(args.Context, (args.Args.Length > 1) ? args.Args[1] : null); }; <>c.<>9__1_1 = val2; obj2 = (object)val2; } new ConsoleCommand("skcrew_prefabs", "SkeletonCrew: list prefabs matching a filter, marking which are safe to attach as a marker effect.", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__1_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { ListWeapons(args.Context, (args.Args.Length > 1) ? args.Args[1] : null); }; <>c.<>9__1_2 = val3; obj3 = (object)val3; } new ConsoleCommand("skcrew_weapons", "SkeletonCrew: list weapons matching a filter, marking which a CREATURE could actually use.", (ConsoleEvent)obj3, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj4 = <>c.<>9__1_3; if (obj4 == null) { ConsoleEvent val4 = delegate(ConsoleEventArgs args) { GiveWeapon(args.Context, (args.Args.Length > 1) ? args.Args[1] : null, (args.Args.Length > 2) ? args.Args[2] : null); }; <>c.<>9__1_3 = val4; obj4 = (object)val4; } new ConsoleCommand("skcrew_giveweapon", "SkeletonCrew: hand a weapon to the selected summon, optionally on a different attack animation.", (ConsoleEvent)obj4, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } internal const string Command = "skcrew_recon"; internal const string PrefabCommand = "skcrew_prefabs"; internal const string WeaponCommand = "skcrew_weapons"; internal const string GiveCommand = "skcrew_giveweapon"; internal static ZDOID Pinned { get; private set; } = ZDOID.None; private static void ListPrefabs(Terminal terminal, string filter) { if ((Object)(object)ZNetScene.instance == (Object)null) { Reply(terminal, "ZNetScene is not loaded yet - run this in a world."); return; } if (string.IsNullOrEmpty(filter)) { Reply(terminal, "Usage: skcrew_prefabs e.g. skcrew_prefabs wisp"); return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("=== prefabs matching '" + filter + "' ==="); int num = 0; foreach (GameObject nonNetViewPrefab in ZNetScene.instance.m_nonNetViewPrefabs) { if (!((Object)(object)nonNetViewPrefab == (Object)null) && ((Object)nonNetViewPrefab).name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0) { num++; stringBuilder.AppendLine(" " + ((Object)nonNetViewPrefab).name + " - attachable (no ZNetView)"); } } foreach (string prefabName in ZNetScene.instance.GetPrefabNames()) { if (prefabName != null && prefabName.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0) { GameObject prefab = ZNetScene.instance.GetPrefab(prefabName); bool flag = (Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent() != (Object)null; num++; stringBuilder.AppendLine(" " + prefabName + " - " + (flag ? "NOT attachable (has ZNetView)" : "attachable")); } } stringBuilder.AppendLine($"=== {num} match(es) ==="); SkeletonCrewPlugin.Log.LogInfo((object)stringBuilder.ToString()); Reply(terminal, $"{num} prefab(s) matching '{filter}'. Full list in LogOutput.log."); } private static void ListWeapons(Terminal terminal, string filter) { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Invalid comparison between Unknown and I4 if ((Object)(object)ObjectDB.instance == (Object)null) { Reply(terminal, "ObjectDB is not loaded yet - run this in a world."); return; } if (string.IsNullOrEmpty(filter)) { Reply(terminal, "Usage: skcrew_weapons e.g. skcrew_weapons magestaff"); return; } HashSet hashSet = SkeletonAnimations(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("=== weapons matching '" + filter + "' ==="); stringBuilder.AppendLine("(skeleton's own attack animations: " + string.Join(", ", Sorted(hashSet)) + ")"); int num = 0; foreach (GameObject item in ObjectDB.instance.m_items) { ItemDrop val = (((Object)(object)item == (Object)null) ? null : item.GetComponent()); SharedData val2 = val?.m_itemData?.m_shared; if (val2 != null && (WeaponChoice.IsWeapon(val.m_itemData) || (int)val2.m_itemType == 5) && (((Object)item).name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 || (val2.m_name != null && val2.m_name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0))) { num++; Describe(stringBuilder, item, val2, hashSet); } } stringBuilder.AppendLine($"=== {num} match(es) ==="); SkeletonCrewPlugin.Log.LogInfo((object)stringBuilder.ToString()); Reply(terminal, $"{num} weapon(s) matching '{filter}'. Full detail in LogOutput.log."); } private static void Describe(StringBuilder report, GameObject prefab, SharedData shared, HashSet skeletonAnimations) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) string name = ((Object)prefab).name; Attack attack = shared.m_attack; bool flag = attack != null && shared.m_aiAttackRange > 0f && shared.m_aiAttackInterval > 0f; bool flag2 = attack == null || attack.m_attackEitr <= 0f; string text = attack?.m_attackAnimation ?? ""; bool flag3 = !string.IsNullOrEmpty(text) && skeletonAnimations.Contains(text); report.AppendLine(); report.AppendLine($"ITEM '{name}' name='{shared.m_name}' type={shared.m_itemType} skill={shared.m_skillType}"); report.AppendLine($" ai: range={shared.m_aiAttackRange:F1} min={shared.m_aiAttackRangeMin:F1} " + $"interval={shared.m_aiAttackInterval:F1} target={shared.m_aiTargetType} " + $"prioritized={shared.m_aiPrioritized} whenWalking={shared.m_aiWhenWalking}"); report.AppendLine(" attack: " + ((attack == null) ? "" : ((object)Unsafe.As(ref attack.m_attackType)/*cast due to .constrained prefix*/).ToString()) + " anim='" + text + "' " + $"eitr={attack?.m_attackEitr ?? 0f:F1} " + $"health={attack?.m_attackHealth ?? 0f:F1}"); if ((Object)(object)attack?.m_attackProjectile != (Object)null) { report.AppendLine(" fires: " + ((Object)attack.m_attackProjectile).name); } if ((Object)(object)attack?.m_spawnOnTrigger != (Object)null) { report.AppendLine(" spawns: " + ((Object)attack.m_spawnOnTrigger).name); } bool flag4 = name.StartsWith("skeleton_", StringComparison.OrdinalIgnoreCase); string text2 = ((!flag) ? "NOT creature-usable - no AI attack range/interval, so EquipBestWeapon can never pick it" : ((!flag2) ? "NOT usable by a summon - costs Eitr, which creatures do not have" : (flag3 ? "USABLE - the animation is one the summon already plays" : ("WILL NOT ATTACK - '" + text + "' is not in the summon's own animation set" + (flag4 ? " (skeleton-native is not enough: proven by skeleton_hildir_firenova)" : string.Empty))))); report.AppendLine(" -> " + text2); bool flag5 = (Object)(object)prefab.transform.Find("attach") != (Object)null; report.AppendLine(" -> " + (flag5 ? "DRAWABLE - has an 'attach' child" : "NOT DRAWABLE by the game - no 'attach' child; SkeletonCrew attaches a mesh child itself")); if (!flag5) { List list = new List(); for (int i = 0; i < prefab.transform.childCount; i++) { list.Add(((Object)prefab.transform.GetChild(i)).name); } report.AppendLine(" children: " + ((list.Count == 0) ? "" : string.Join(", ", list.ToArray()))); } } private static HashSet SkeletonAnimations() { HashSet hashSet = new HashSet(); ZNetScene instance = ZNetScene.instance; GameObject obj = ((instance != null) ? instance.GetPrefab("Skeleton_Friendly") : null); GameObject[] array = ((obj == null) ? null : obj.GetComponent()?.m_randomWeapon); if (array == null) { return hashSet; } GameObject[] array2 = array; foreach (GameObject obj2 in array2) { Attack val = ((obj2 == null) ? null : obj2.GetComponent()?.m_itemData?.m_shared?.m_attack); if (val != null && !string.IsNullOrEmpty(val.m_attackAnimation)) { hashSet.Add(val.m_attackAnimation); } } return hashSet; } private static List Sorted(HashSet set) { List list = new List(set); list.Sort(); return list; } private static void RebindAnimation(ItemData item, string animation) { WeaponRebind.To(item, animation); } internal static bool IsPinned(ZDOID id) { //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_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (Pinned != ZDOID.None) { return Pinned == id; } return false; } private static void GiveWeapon(Terminal terminal, string prefabName, string animation) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(prefabName)) { Reply(terminal, "Usage: skcrew_giveweapon [animation] e.g. skcrew_giveweapon charred_magestaff_fire attack | skcrew_giveweapon clear to release the pinned summon"); return; } if (prefabName.Equals("clear", StringComparison.OrdinalIgnoreCase)) { Pinned = ZDOID.None; Reply(terminal, "Released - the weapon policy applies to the whole crew again."); return; } if ((Object)(object)ObjectDB.instance == (Object)null) { Reply(terminal, "ObjectDB is not loaded yet - run this in a world."); return; } if (CrewSelection.IsAll) { Reply(terminal, "Select ONE summon first (the order-target key, F3 by default) - this hands the weapon to that one."); return; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); if ((Object)(object)itemPrefab == (Object)null) { Reply(terminal, "No item prefab named '" + prefabName + "'. Try skcrew_weapons to find it."); return; } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (item.Id != CrewSelection.Selected) { continue; } Character character = item.Character; Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val == null) { continue; } val.GiveDefaultItem(itemPrefab); ItemData val2 = null; foreach (ItemData allItem in val.GetInventory().GetAllItems()) { if (allItem != null && (Object)(object)allItem.m_dropPrefab == (Object)(object)itemPrefab) { val2 = allItem; break; } } if (val2 != null) { if (!string.IsNullOrEmpty(animation)) { RebindAnimation(val2, animation); } val.EquipItem(val2, false); Pinned = item.Id; } List list = new List(); foreach (ItemData allItem2 in val.GetInventory().GetAllItems()) { if (allItem2?.m_shared != null) { list.Add(allItem2.m_shared.m_name); } } ItemData currentWeapon = val.GetCurrentWeapon(); SkeletonCrewPlugin.Log.LogInfo((object)("Gave '" + prefabName + "' to '" + item.Character.GetHoverName() + "'. Holding: " + (currentWeapon?.m_shared?.m_name ?? "") + ". Kit: " + string.Join(", ", list.ToArray()))); Reply(terminal, "Gave '" + prefabName + "' to " + item.Character.GetHoverName() + ". Put it in a fight and watch whether it casts."); return; } Reply(terminal, "The selected summon is no longer in the crew."); } private static void Run(Terminal terminal) { if ((Object)(object)ObjectDB.instance == (Object)null) { Reply(terminal, "ObjectDB is not loaded yet - run this in a world, not the main menu."); return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("=== SkeletonCrew summon recon ==="); int num = 0; foreach (GameObject item in ObjectDB.instance.m_items) { SharedData val = (((Object)(object)item == (Object)null) ? null : item.GetComponent())?.m_itemData?.m_shared; if (val != null && (DescribeAttack(stringBuilder, ((Object)item).name, val, val.m_attack, "primary") | DescribeAttack(stringBuilder, ((Object)item).name, val, val.m_secondaryAttack, "secondary"))) { num++; } } List list = new List(); foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects) { SE_Spawn val2 = (SE_Spawn)(object)((statusEffect is SE_Spawn) ? statusEffect : null); if (val2 != null && (Object)(object)val2.m_prefab != (Object)null) { list.Add(((Object)statusEffect).name + " -> " + ((Object)val2.m_prefab).name); } } stringBuilder.AppendLine(); stringBuilder.AppendLine((list.Count == 0) ? "SE_Spawn status effects: none." : ("SE_Spawn status effects (NOT handled by this mod): " + string.Join(", ", list.ToArray()))); stringBuilder.AppendLine($"=== {num} summoning item(s) found ==="); SkeletonCrewPlugin.Log.LogInfo((object)stringBuilder.ToString()); Reply(terminal, $"Summon recon: {num} summoning item(s). Full detail in LogOutput.log."); } private static bool DescribeAttack(StringBuilder report, string itemName, SharedData shared, Attack attack, string which) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (attack == null) { return false; } List list = new List(); Collect(attack.m_attackProjectile, list); Collect(attack.m_spawnOnTrigger, list); if (list.Count == 0) { return false; } report.AppendLine(); report.AppendLine($"ITEM '{shared.m_name}' ({itemName}) [{which}] skill={shared.m_skillType} " + $"eitr={attack.m_attackEitr:F1} health={attack.m_attackHealth:F1}+{attack.m_attackHealthPercentage:F1}%"); foreach (SpawnAbility item in list) { report.AppendLine($" cap: maxSpawned={item.m_maxSpawned} fromWeaponLevel={item.m_setMaxInstancesFromWeaponLevel} " + $"commandOnSpawn={item.m_commandOnSpawn} perCast={item.m_minToSpawn}-{item.m_maxToSpawn}"); if (item.m_spawnPrefab != null) { GameObject[] spawnPrefab = item.m_spawnPrefab; foreach (GameObject creature in spawnPrefab) { DescribeCreature(report, creature); } } } return true; } private static void DescribeCreature(StringBuilder report, GameObject creature) { if ((Object)(object)creature == (Object)null) { report.AppendLine(" spawns: "); return; } Character component = creature.GetComponent(); if ((Object)(object)component == (Object)null) { report.AppendLine(" spawns: " + ((Object)creature).name + " - not a Character (an effect, not a mob)"); return; } Tameable component2 = creature.GetComponent(); Humanoid component3 = creature.GetComponent(); report.AppendLine($" spawns: {((Object)creature).name} name='{component.m_name}' hp={component.m_health:F0} " + $"tameable={(Object)(object)component2 != (Object)null} startsTamed={(Object)(object)component2 != (Object)null && component2.m_startsTamed} " + $"unsummon={(((Object)(object)component2 == (Object)null) ? 0f : component2.m_unsummonDistance):F0} " + $"commandable={(Object)(object)component2 != (Object)null && component2.m_commandable} humanoid={(Object)(object)component3 != (Object)null}"); if ((Object)(object)component3 != (Object)null && component3.m_randomWeapon != null && component3.m_randomWeapon.Length != 0) { List list = new List(); GameObject[] randomWeapon = component3.m_randomWeapon; foreach (GameObject val in randomWeapon) { if (!((Object)(object)val == (Object)null)) { bool flag = WeaponChoice.IsRanged(val.GetComponent()?.m_itemData?.m_shared); list.Add(((Object)val).name + (flag ? " [ranged]" : " [melee]")); } } report.AppendLine(" weapons: " + string.Join(", ", list.ToArray())); } else if ((Object)(object)component3 != (Object)null) { report.AppendLine(" weapons: none (fixed loadout - the melee/ranged preference cannot apply)"); } bool num = (Object)(object)component2 != (Object)null && component2.m_startsTamed; string signal; bool flag2 = SummonRegistry.IsSummon(component, component2, out signal); string text = ((!num) ? "no - not tamed on spawn, so Tick() rejects it before the summon test" : (flag2 ? ("yes - " + signal + " (and s_follow must name you, which vanilla writes on spawn)") : ("no - " + signal))); report.AppendLine(" MANAGED BY SKELETONCREW: " + text); } private static void Collect(GameObject prefab, List into) { if (!((Object)(object)prefab == (Object)null) && SummonCost.Spawns(prefab)) { SpawnAbility[] componentsInChildren = prefab.GetComponentsInChildren(true); foreach (SpawnAbility item in componentsInChildren) { into.Add(item); } } } private static void Reply(Terminal terminal, string message) { SkeletonCrewPlugin.Log.LogInfo((object)message); if ((Object)(object)terminal != (Object)null) { terminal.AddString(message); } } } } namespace SkeletonCrew.Ai { internal static class CrewFocus { private static string _lastReport; private static Character _pullTarget; private static float _pullSince; internal static Character Current { get; private set; } internal static bool Pulling { get; private set; } private static bool IsPull(Player player) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.PullTactics.Value) { return false; } if ((Object)(object)ThreatScan.AttackerOf((Character)(object)player) != (Object)null) { return false; } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if ((Object)(object)item.Character != (Object)null && !item.Character.IsDead() && (Object)(object)ThreatScan.AttackerOf(item.Character) != (Object)null) { return false; } } float value = SkeletonCrewPlugin.PullReceiveDistance.Value; Vector3 val = ((Component)Current).transform.position - ((Component)player).transform.position; return ((Vector3)(ref val)).sqrMagnitude > value * value; } private static Character Puller() { Character result = null; float num = 0f; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead() && SummonClass.Of((Humanoid)/*isinst with value type is only supported in some contexts*/) != SummonPreference.Healer) { Character character = item.Character; float num2 = WeaponChoice.ReachOf((Humanoid)(object)((character is Humanoid) ? character : null)); if (num2 > num) { num = num2; result = item.Character; } } } return result; } internal static void Tick() { //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !SkeletonCrewPlugin.FocusFire.Value) { Current = null; return; } Current = Pick(localPlayer); if ((Object)(object)Current == (Object)null) { Pulling = false; return; } bool flag = IsPull(localPlayer); if (!flag || (Object)(object)Current != (Object)(object)_pullTarget) { _pullTarget = (flag ? Current : null); _pullSince = (flag ? Time.time : 0f); } if (flag && _pullSince > 0f && Time.time - _pullSince > SkeletonCrewPlugin.PullTimeoutSeconds.Value) { flag = false; } Pulling = flag; int num = 0; int num2 = 0; string text = null; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if ((Object)(object)item.AI == (Object)null || (Object)(object)item.Character == (Object)null || item.Character.IsDead()) { continue; } if (SummonRegistry.HasOrderedTarget(item.AI)) { num2++; text = text ?? "own attack order"; continue; } Character character = item.Character; if (SummonClass.Of((Humanoid)(object)((character is Humanoid) ? character : null)) == SummonPreference.Healer) { continue; } Character targetCreature = ((BaseAI)item.AI).GetTargetCreature(); Character val = ThreatScan.AttackerOf(item.Character); if ((Object)(object)val != (Object)null && !val.IsDead()) { if ((Object)(object)targetCreature != (Object)(object)val) { SummonRegistry.Retarget(item.AI, val); num++; } continue; } if (SummonRegistry.IsStationed(item.Id, out var post)) { if (Vector3.Distance(((Component)Current).transform.position, post) > SkeletonCrewPlugin.GuardRadius.Value) { num2++; text = text ?? "guarding a post out of range"; continue; } } else if (SummonRegistry.HasStandingOrder(item.Id)) { num2++; text = text ?? SummonRegistry.IntentFor(item.Id).ToString(); continue; } if (Pulling && SkeletonCrewPlugin.RoleFormation.Value && (Object)(object)item.Character != (Object)(object)Puller()) { num2++; text = text ?? "holding while the puller brings it in"; continue; } if (!item.Character.InLava() && LavaGuard.Between(((Component)item.Character).transform.position, ((Component)Current).transform.position)) { num2++; text = text ?? "lava in the way"; continue; } if ((Object)(object)targetCreature != (Object)(object)Current) { num++; } SummonRegistry.Retarget(item.AI, Current); } if (SkeletonCrewPlugin.VerboseLogging.Value && (num != 0 || num2 != 0)) { string text2 = $"{num}/{num2}/{text}/{Current.GetHoverName()}"; if (text2 != _lastReport) { _lastReport = text2; string arg = ((num2 > 0) ? $" ({num2} held back: {text})" : string.Empty); SkeletonCrewPlugin.Log.LogInfo((object)$"Crew focus: {num} joined on '{Current.GetHoverName()}'{arg}."); } } } private static Character Pick(Player player) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) Vector3 val; if ((Object)(object)Current != (Object)null && !Current.IsDead()) { val = ((Component)Current).transform.position - ((Component)player).transform.position; if (((Vector3)(ref val)).sqrMagnitude <= Stance.EscortRadius() * Stance.EscortRadius() * 4f) { return Current; } } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead()) { Character val2 = ThreatScan.AttackerOf(item.Character); if ((Object)(object)val2 != (Object)null && !val2.IsDead()) { return val2; } } } Character val3 = ThreatScan.AttackerOf((Character)(object)player); if ((Object)(object)val3 != (Object)null && !val3.IsDead()) { return val3; } Character val4 = ThreatScan.BestThreatTo(((Component)player).transform.position, Stance.EscortRadius(), Stance.OnlyAnswersAggression); if ((Object)(object)val4 != (Object)null) { return val4; } float radius = Stance.EscortRadius(); Character result = null; float num = float.MaxValue; foreach (SummonRegistry.Summon item2 in SummonRegistry.Owned) { if ((Object)(object)item2.Character == (Object)null || item2.Character.IsDead()) { continue; } Character val5 = ThreatScan.Nearest(((Component)item2.Character).transform.position, radius); if (!((Object)(object)val5 == (Object)null)) { val = ((Component)val5).transform.position - ((Component)item2.Character).transform.position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; result = val5; } } } return result; } } internal static class Devotion { private const string Key = "SkeletonCrew.Devotion"; internal static float Score(Player player) { if ((Object)(object)player == (Object)null) { return 0f; } float num = Mathf.Max(0.01f, SkeletonCrewPlugin.DevotionFullGear.Value); float num2 = Mathf.Clamp01(player.GetEquipmentEitrRegenModifier() / num); float num3 = Mathf.Max(1f, SkeletonCrewPlugin.DevotionFullArsenal.Value); float num4 = Mathf.Clamp01((float)MagicItems(player) / num3); return Mathf.Clamp01(num2 * 0.65f + num4 * 0.35f); } internal static float FactorFor(float score) { float num = Mathf.Clamp01(SkeletonCrewPlugin.DevotionFloor.Value); return num + (1f - num) * Mathf.Clamp01(score); } private static int MagicItems(Player player) { //IL_0048: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Invalid comparison between Unknown and I4 //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Invalid comparison between Unknown and I4 Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return 0; } int num = 0; foreach (ItemData allItem in inventory.GetAllItems()) { SkillType valueOrDefault = (allItem?.m_shared?.m_skillType).GetValueOrDefault(); if ((int)valueOrDefault == 9 || (int)valueOrDefault == 10) { num++; } } return num; } internal static void Stamp(Humanoid summon, float factor) { ZNetView val = (((Object)(object)summon == (Object)null) ? null : ((Component)summon).GetComponent()); if ((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()) { val.GetZDO().Set("SkeletonCrew.Devotion", factor); } } internal static float Factor(Character summon) { if (!SkeletonCrewPlugin.DevotionScaling.Value || (Object)(object)summon == (Object)null) { return 1f; } ZNetView component = ((Component)summon).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return 1f; } float num = component.GetZDO().GetFloat("SkeletonCrew.Devotion", 0f); if (!(num <= 0f)) { return num; } return 1f; } internal static void Ratchet(Humanoid summon, Player player) { if (SkeletonCrewPlugin.DevotionScaling.Value && !((Object)(object)summon == (Object)null) && !((Object)(object)player == (Object)null)) { float num = FactorFor(Score(player)); if (num > Factor((Character)(object)summon) + 0.001f) { Stamp(summon, num); } } } } internal static class EquipmentVisuals { private static readonly HashSet Undrawable = new HashSet(); internal static void Tick() { foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { Character character = item.Character; Reconcile((Humanoid)(object)((character is Humanoid) ? character : null)); } } internal static void Reconcile(Humanoid h) { if ((Object)(object)h == (Object)null) { return; } VisEquipment visEquipment = h.m_visEquipment; ZNetView nview = ((Character)h).m_nview; if ((Object)(object)visEquipment == (Object)null || (Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner()) { return; } ZDO zDO = nview.GetZDO(); if (zDO != null) { if ((!Published(zDO, ZDOVars.s_rightItem, h.m_rightItem) || !Published(zDO, ZDOVars.s_leftItem, h.m_leftItem)) && !Unpublishable(h.m_rightItem) && !Unpublishable(h.m_leftItem)) { visEquipment.m_rightItem = null; visEquipment.m_leftItem = null; h.SetupEquipment(); } Draw(visEquipment); } } private static void Draw(VisEquipment vis) { GameObject val = Build(vis.m_currentRightItemHash, vis.m_rightItemInstance, vis.m_rightHand); if ((Object)(object)val != (Object)null) { vis.m_rightItemInstance = val; } GameObject val2 = Build(vis.m_currentLeftItemHash, vis.m_leftItemInstance, vis.m_leftHand); if ((Object)(object)val2 != (Object)null) { vis.m_leftItemInstance = val2; } } private static GameObject Build(int hash, GameObject existing, Transform joint) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0125: 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) if (hash == 0 || (Object)(object)existing != (Object)null || (Object)(object)joint == (Object)null || Undrawable.Contains(hash) || (Object)(object)ObjectDB.instance == (Object)null) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(hash); if ((Object)(object)itemPrefab == (Object)null) { Undrawable.Add(hash); return null; } Transform val = Copyable(itemPrefab.transform); GameObject val2 = (((Object)(object)val != (Object)null) ? Object.Instantiate(((Component)val).gameObject, joint) : CloneWhole(itemPrefab, joint)); if ((Object)(object)val2 == (Object)null) { Undrawable.Add(hash); SkeletonCrewPlugin.Log.LogInfo((object)("No model to attach for '" + ((Object)itemPrefab).name + "' - nothing to copy. Children: " + Children(itemPrefab))); return null; } val2.SetActive(true); val2.transform.localPosition = Vector3.zero; val2.transform.localRotation = Quaternion.identity; Transform val3 = itemPrefab.transform.Find("equipoffset"); if ((Object)(object)val3 != (Object)null) { Transform transform = val2.transform; transform.localPosition += val3.position; Transform transform2 = val2.transform; transform2.localRotation *= val3.rotation; } VisEquipment.CleanupInstance(val2); Rigidbody[] componentsInChildren = val2.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.Destroy((Object)(object)componentsInChildren[i]); } SkeletonCrewPlugin.Log.LogInfo((object)("Attached '" + ((Object)itemPrefab).name + "' by hand (" + (((Object)(object)val == (Object)null) ? "whole prefab, stripped" : ("'" + ((Object)val).name + "' child")) + ") - the item has no 'attach' child, so the game drew nothing.")); return val2; } private static GameObject CloneWhole(GameObject prefab, Transform joint) { if ((Object)(object)prefab.GetComponentInChildren(true) == (Object)null && (Object)(object)prefab.GetComponentInChildren(true) == (Object)null) { return null; } bool activeSelf = prefab.activeSelf; prefab.SetActive(false); GameObject val; try { val = Object.Instantiate(prefab, joint); } finally { prefab.SetActive(activeSelf); } Strip(val); Strip(val); Strip(val); Strip(val); return val; } private static void Strip(GameObject go) where T : Component { T[] componentsInChildren = go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren[i]); } } private static string Children(GameObject prefab) { if (prefab.transform.childCount == 0) { return ""; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < prefab.transform.childCount; i++) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(((Object)prefab.transform.GetChild(i)).name); } return stringBuilder.ToString(); } private static Transform Copyable(Transform prefab) { for (int i = 0; i < prefab.childCount; i++) { Transform child = prefab.GetChild(i); if (!((Object)(object)((Component)child).GetComponentInChildren(true) != (Object)null) && !((Object)(object)((Component)child).GetComponentInChildren(true) != (Object)null) && ((Object)(object)((Component)child).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)child).GetComponentInChildren(true) != (Object)null)) { return child; } } return null; } private static bool Unpublishable(ItemData item) { if (item != null) { return (Object)(object)item.m_dropPrefab == (Object)null; } return false; } private static bool Published(ZDO zdo, int key, ItemData item) { return zdo.GetInt(key, 0) == Hash(item); } private static int Hash(ItemData item) { string text = (((Object)(object)item?.m_dropPrefab == (Object)null) ? null : ((Object)item.m_dropPrefab).name); if (!string.IsNullOrEmpty(text)) { return StringExtensionMethods.GetStableHashCode(text); } return 0; } internal static string Describe(Humanoid h) { if ((Object)(object)h == (Object)null) { return "not a Humanoid"; } VisEquipment visEquipment = h.m_visEquipment; if ((Object)(object)visEquipment == (Object)null) { return "no VisEquipment component - nothing can ever be drawn"; } ZNetView nview = ((Character)h).m_nview; ZDO val = (((Object)(object)nview == (Object)null || !nview.IsValid()) ? null : nview.GetZDO()); StringBuilder stringBuilder = new StringBuilder(); string value = (((Object)(object)h.m_rightItem?.m_dropPrefab == (Object)null) ? "" : ((Object)h.m_rightItem.m_dropPrefab).name); stringBuilder.Append("left=").Append(((Object)(object)h.m_leftItem?.m_dropPrefab == (Object)null) ? "" : ((Object)h.m_leftItem.m_dropPrefab).name).Append(' '); stringBuilder.Append("holds=").Append(value); stringBuilder.Append(" wants=").Append(Hash(h.m_rightItem)); stringBuilder.Append(" zdo=").Append((val == null) ? "" : val.GetInt(ZDOVars.s_rightItem, 0).ToString()); stringBuilder.Append(" owner=").Append((Object)(object)nview != (Object)null && nview.IsValid() && nview.IsOwner()); stringBuilder.Append(" cache='").Append(visEquipment.m_rightItem ?? "").Append('\''); stringBuilder.Append(" drawnHash=").Append(visEquipment.m_currentRightItemHash); stringBuilder.Append(" model=").Append((Object)(object)visEquipment.m_rightItemInstance != (Object)null); stringBuilder.Append(" leftModel=").Append((Object)(object)visEquipment.m_leftItemInstance != (Object)null); stringBuilder.Append(" hand=").Append((Object)(object)visEquipment.m_rightHand != (Object)null); return stringBuilder.ToString(); } } internal static class Hazard { private static readonly HashSet Environmental = new HashSet { (HitType)5, (HitType)20, (HitType)21, (HitType)6, (HitType)7, (HitType)9, (HitType)4, (HitType)10 }; private static readonly Dictionary HurtUntil = new Dictionary(); internal static void Note(Character c, HitData hit) { //IL_0012: 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) if (!((Object)(object)c == (Object)null) && hit != null && Environmental.Contains(hit.m_hitType)) { ZNetView component = ((Component)c).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { HurtUntil[component.GetZDO().m_uid] = Time.time + SkeletonCrewPlugin.HazardMemorySeconds.Value; } } } internal static bool Hurting(Character c) { //IL_0036: 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_0083: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c == (Object)null || c.IsDead()) { return false; } if (c.m_lavaHeatLevel >= 1f && c.m_lavaProximity > c.m_minLavaMaskThreshold) { return true; } if (InFire(((Component)c).transform.position) && Burning(c)) { return true; } ZNetView component = ((Component)c).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid() || !InFire(((Component)c).transform.position)) { return false; } if (HurtUntil.TryGetValue(component.GetZDO().m_uid, out var value)) { return Time.time < value; } return false; } private static bool Burning(Character c) { SEMan sEMan = c.GetSEMan(); if (sEMan != null) { return sEMan.HaveStatusEffect(SEMan.s_statusEffectBurning); } return false; } private static bool InFire(Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return (Object)(object)EffectArea.IsPointInsideArea(point, (Type)10, 0f) != (Object)null; } internal static void Forget() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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) if (HurtUntil.Count == 0) { return; } float time = Time.time; List list = null; foreach (KeyValuePair item in HurtUntil) { if (time > item.Value + 30f) { (list ?? (list = new List())).Add(item.Key); } } if (list == null) { return; } foreach (ZDOID item2 in list) { HurtUntil.Remove(item2); } } } internal static class Healer { private static bool _reportedHealer; private static float _nextHealLog; private static float _nextSkipLog; private static float _nextBarrier; private static float _nextCast; private static float _nextEmergency; private static Character _ordered; private static float _orderedUntil; private static float _criticalAt = -1f; private static Vector3 _criticalFrom; private static Character _criticalWas; internal static void Tick() { if (!SkeletonCrewPlugin.HealerHealsPlayers.Value || SkeletonCrewPlugin.HealerCastHealAmount.Value <= 0f) { return; } bool flag = false; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead() && IsHealer(item)) { flag = true; if ((!Dying(item) || !Cast(item)) && !Barrier(item)) { Cast(item); } } } if (flag != _reportedHealer && (flag || SummonRegistry.Owned.Count > 0)) { _reportedHealer = flag; SkeletonCrewPlugin.Log.LogInfo((object)(flag ? "Healer on duty: a summon is carrying the healer's staff." : ("No healer in the crew - nothing carries '" + SkeletonCrewPlugin.ClassItems(SummonPreference.Healer)?.Value + "'. Crew: " + Carrying()))); } } private static bool Dying(SummonRegistry.Summon s) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) Character character = s.Character; Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val != null) { return (Object)(object)Critical(((Component)s.Character).transform.position, Reach(val)) != (Object)null; } return false; } private static string Carrying() { StringBuilder stringBuilder = new StringBuilder(); foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { Character character = item.Character; Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val != null) { if (stringBuilder.Length > 0) { stringBuilder.Append("; "); } stringBuilder.Append(item.Character.GetHoverName()).Append('='); ItemData currentWeapon = val.GetCurrentWeapon(); stringBuilder.Append(((Object)(object)currentWeapon?.m_dropPrefab == (Object)null) ? "" : ((Object)currentWeapon.m_dropPrefab).name); } } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return ""; } internal static bool Order(Character patient, out string why) { why = null; if ((Object)(object)patient == (Object)null || patient.IsDead()) { why = "Nothing there to heal"; return false; } if (!AnyHealer()) { why = "No healer in the crew"; return false; } if (patient.GetHealth() >= patient.GetMaxHealth() - 0.01f) { why = patient.GetHoverName() + " is unhurt"; return false; } _ordered = patient; _orderedUntil = Time.time + Mathf.Max(5f, SkeletonCrewPlugin.HealerCastInterval.Value * 2f); return true; } internal static bool OrderWorst(out string why) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) why = null; if (!AnyHealer()) { why = "No healer in the crew"; return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } float num = Mathf.Max(SkeletonCrewPlugin.HealerHealRadius.Value, SkeletonCrewPlugin.EscortRadius.Value); Character val = MostHurt(((Component)localPlayer).transform.position, num, 1f); if ((Object)(object)val == (Object)null) { why = $"Nobody within {num:0}m is hurt - {Candidates(((Component)localPlayer).transform.position, num)}"; return false; } return Order(val, out why); } private static string Candidates(Vector3 from, float radius) { //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) StringBuilder stringBuilder = new StringBuilder(); float num = radius * radius; int num2 = 0; foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsDead() || (!allCharacter.IsPlayer() && !allCharacter.IsTamed())) { continue; } float num3 = Vector3.Distance(((Component)allCharacter).transform.position, from); if (!(num3 * num3 > num) && num2 < 3) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(allCharacter.GetHoverName()).Append(' ').Append((allCharacter.GetHealth() / Mathf.Max(1f, allCharacter.GetMaxHealth()) * 100f).ToString("0")) .Append("% at ") .Append(num3.ToString("0")) .Append('m'); num2++; } } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "nothing friendly in range"; } internal static bool Available() { return AnyHealer(); } internal static bool OrderMe(out string why) { why = null; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } return Order((Character)(object)localPlayer, out why); } private static bool AnyHealer() { foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if ((Object)(object)item.Character != (Object)null && !item.Character.IsDead() && IsHealer(item)) { return true; } } return false; } private static bool InReach(Character c, Vector3 from, float range) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c != (Object)null && !c.IsDead() && c.GetHealth() < c.GetMaxHealth() - 0.01f) { Vector3 val = ((Component)c).transform.position - from; return ((Vector3)(ref val)).sqrMagnitude <= range * range; } return false; } internal static float Reach(Humanoid h) { float value = SkeletonCrewPlugin.HealerHealRadius.Value; return Mathf.Max(((h == null) ? ((float?)null) : h.GetCurrentWeapon()?.m_shared?.m_aiAttackRange).GetValueOrDefault(), value); } private static bool Cast(SummonRegistry.Summon s) { //IL_0042: 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) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: 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_0196: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.HealerCastsStaff.Value && !((Object)(object)s.AI == (Object)null)) { Character character = s.Character; Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val != null) { bool flag = Time.time >= _nextEmergency && (Object)(object)Critical(((Component)s.Character).transform.position, Reach(val)) != (Object)null; if (Time.time < _nextCast && !flag) { return false; } if (val.GetCurrentWeapon()?.m_shared == null) { return false; } if ((Object)(object)_ordered != (Object)null && (_ordered.IsDead() || Time.time > _orderedUntil)) { _ordered = null; } float num = Reach(val); Character val2 = (InReach(_ordered, ((Component)s.Character).transform.position, num) ? _ordered : (Critical(((Component)s.Character).transform.position, num) ?? MostHurt(((Component)s.Character).transform.position, num))); if ((Object)(object)val2 == (Object)null) { Explain(((Component)s.Character).transform.position, num); return false; } if (!s.AI.DoAttack(val2, true)) { return false; } if ((Object)(object)val2 == (Object)(object)_ordered) { _ordered = null; } _nextCast = Time.time + Mathf.Max(1f, SkeletonCrewPlugin.HealerCastInterval.Value); if (flag) { _nextEmergency = Time.time + Mathf.Max(0.5f, SkeletonCrewPlugin.HealerEmergencyInterval.Value); } Mend(((Component)val2).transform.position, val2); if (Time.time >= _nextHealLog) { _nextHealLog = Time.time + 30f; SkeletonCrewPlugin.Log.LogInfo((object)("Healer cast its staff at '" + val2.GetHoverName() + "' " + $"({val2.GetHealth():0.#} / {val2.GetMaxHealth():0}).")); } return true; } } return false; } private static void Mend(Vector3 from, Character focus) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (SkeletonCrewPlugin.HealerCastHealAmount.Value <= 0f) { return; } float value = SkeletonCrewPlugin.HealerCastHealRadius.Value; float radiusSq = value * value; int num = 0; foreach (Character allCharacter in Character.GetAllCharacters()) { num += (HealIfNear(allCharacter, focus, from, radiusSq) ? 1 : 0); } if (num > 0 && SkeletonCrewPlugin.VerboseLogging.Value) { SkeletonCrewPlugin.Log.LogInfo((object)$"Healer's cast mended {num}, {Amount(focus):0.#} on '{focus.GetHoverName()}'."); } } private static bool HealIfNear(Character c, Character focus, Vector3 from, float radiusSq) { //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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!IsPatient(c) || c.GetHealth() >= c.GetMaxHealth() - 0.01f) { return false; } if ((Object)(object)c != (Object)(object)focus) { Vector3 val = ((Component)c).transform.position - from; if (((Vector3)(ref val)).sqrMagnitude > radiusSq) { return false; } } return Apply(c, Amount(c)); } private static float Amount(Character c) { float value = SkeletonCrewPlugin.HealerCastHealAmount.Value; if ((Object)(object)c == (Object)null) { return value; } return Mathf.Max(value, c.GetMaxHealth() * SkeletonCrewPlugin.HealerCastHealPercent.Value); } private static bool IsPatient(Character c) { if ((Object)(object)c != (Object)null && !c.IsDead()) { if (!c.IsPlayer()) { return c.IsTamed(); } return true; } return false; } private static bool Apply(Character c, float amount) { ZNetView component = ((Component)c).GetComponent(); bool num = (Object)(object)component != (Object)null && component.IsValid() && component.IsOwner(); float health = c.GetHealth(); c.Heal(amount, true); if (num) { return c.GetHealth() > health + 0.01f; } return true; } internal static bool ShouldClose(SummonRegistry.Summon s, Player player, out Vector3 destination) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) destination = Vector3.zero; if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || (Object)(object)s.Character == (Object)null || !SkeletonCrewPlugin.HealerHealsPlayers.Value || !IsHealer(s)) { return false; } Character character = s.Character; float num = Reach((Humanoid)(object)((character is Humanoid) ? character : null)); if ((Object)(object)_ordered != (Object)null && !_ordered.IsDead() && Time.time < _orderedUntil && !InReach(_ordered, ((Component)s.Character).transform.position, num)) { destination = ((Component)_ordered).transform.position; return true; } if (((Character)player).GetHealth() / Mathf.Max(1f, ((Character)player).GetMaxHealth()) > SkeletonCrewPlugin.HealerRescueHealth.Value) { return false; } Vector3 val = ((Component)player).transform.position - ((Component)s.Character).transform.position; if (((Vector3)(ref val)).sqrMagnitude <= num * num) { return false; } destination = ((Component)player).transform.position; return true; } private static bool Barrier(SummonRegistry.Summon s) { if (!(Time.time < _nextBarrier)) { Character character = s.Character; Humanoid h = (Humanoid)(object)((character is Humanoid) ? character : null); if (h != null && !((Character)h).InAttack()) { if (!Ward.Approaching(out var distance)) { return false; } ItemData currentWeapon = h.GetCurrentWeapon(); if (!WeaponRebind.HasAlternate(currentWeapon)) { return false; } if (!Ward.Needed(currentWeapon, out var missing)) { return false; } if (s.Character.GetHealthPercentage() < SkeletonCrewPlugin.HealerCriticalHealth.Value) { return false; } Character at = Ward.Patient(s.Character) ?? s.Character; if (!WeaponRebind.WithAlternate(currentWeapon, () => ((Character)h).StartAttack(at, false))) { _nextBarrier = Time.time + 2f; return false; } _nextBarrier = Time.time + Mathf.Max(1f, SkeletonCrewPlugin.HealerWardInterval.Value); string report; int num = Ward.Cover(s.Character, currentWeapon, out report); SkeletonCrewPlugin.Log.LogInfo((object)((num > 0) ? $"Healer warded {report} (nearest hostile {distance:0}m, {missing} needed one)." : ("Healer raised its barrier (" + s.Character.GetHoverName() + ") - " + (report ?? "nobody else in range needed one") + "."))); return true; } } return false; } private static Character Critical(Vector3 from, float radius) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (Time.time - _criticalAt < 0.1f) { Vector3 val = _criticalFrom - from; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { return _criticalWas; } } _criticalAt = Time.time; _criticalFrom = from; _criticalWas = Worst(from, radius); return _criticalWas; } private static Character Worst(Vector3 from, float radius) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) float value = SkeletonCrewPlugin.HealerCriticalHealth.Value; float num = radius * radius; Character result = null; float num2 = value; foreach (Character allCharacter in Character.GetAllCharacters()) { if (!IsPatient(allCharacter)) { continue; } Vector3 val = ((Component)allCharacter).transform.position - from; if (!(((Vector3)(ref val)).sqrMagnitude > num)) { float num3 = allCharacter.GetHealth() / Mathf.Max(1f, allCharacter.GetMaxHealth()); if (num3 < num2) { num2 = num3; result = allCharacter; } } } return result; } private static Character MostHurt(Vector3 from, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return MostHurt(from, radius, SkeletonCrewPlugin.HealerHealThreshold.Value); } private static Character MostHurt(Vector3 from, float radius, float threshold) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) Character worst = null; float worstFraction = 0f; float radiusSq = radius * radius; foreach (Player allPlayer in Player.GetAllPlayers()) { Consider((Character)(object)allPlayer, from, radiusSq, threshold, ref worst, ref worstFraction); } foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter != (Object)null && !allCharacter.IsPlayer() && allCharacter.IsTamed()) { Consider(allCharacter, from, radiusSq, threshold, ref worst, ref worstFraction); } } return worst; } private static void Consider(Character c, Vector3 from, float radiusSq, float threshold, ref Character worst, ref float worstFraction) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c == (Object)null || c.IsDead()) { return; } float num = c.GetHealth() / Mathf.Max(1f, c.GetMaxHealth()); if (num > threshold) { return; } Vector3 val = ((Component)c).transform.position - from; if (!(((Vector3)(ref val)).sqrMagnitude > radiusSq)) { float num2 = (1f - num) * (1f + Pressure.Incoming(c)); if (!(num2 <= worstFraction)) { worst = c; worstFraction = num2; } } } private static void Explain(Vector3 from, float range) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !(Time.time < _nextSkipLog)) { _nextSkipLog = Time.time + 30f; float num = Vector3.Distance(((Component)localPlayer).transform.position, from); string text = ((((Character)localPlayer).GetHealth() >= ((Character)localPlayer).GetMaxHealth() - 0.01f) ? "at full health" : ((num > range) ? $"{num:0.#}m away, reach is {range:0.#}m" : $"above the {SkeletonCrewPlugin.HealerHealThreshold.Value:P0} threshold")); SkeletonCrewPlugin.Log.LogInfo((object)("Healer had nobody to mend - '" + localPlayer.GetPlayerName() + "' " + text + " " + $"({((Character)localPlayer).GetHealth():0.#} / {((Character)localPlayer).GetMaxHealth():0}).")); } } private static bool IsHealer(SummonRegistry.Summon s) { Character character = s.Character; return SummonClass.Of((Humanoid)(object)((character is Humanoid) ? character : null)) == SummonPreference.Healer; } } internal enum Activity { Idle, Following, Regrouping, MovingTo, HoldingAt, Attacking, Working } internal enum IntentSource { Rescue, PlayerOrder, Work, Threat, Default } internal readonly struct Intent { internal Activity Activity { get; } internal IntentSource Source { get; } internal Vector3? Destination { get; } internal Character Target { get; } internal static Intent Follow => new Intent(Activity.Following, IntentSource.Default); internal bool IsCommanded { get { if (Source != IntentSource.PlayerOrder) { return Source == IntentSource.Rescue; } return true; } } internal Intent(Activity activity, IntentSource source, Vector3? destination = null, Character target = null) { Activity = activity; Source = source; Destination = destination; Target = target; } public override string ToString() { return $"{Activity}({Source})"; } } internal static class LavaGuard { private static readonly float[] DodgeAngles = new float[4] { 35f, -35f, 70f, -70f }; private const float ArrivedSq = 4f; private static float _nextTrappedLog; private static readonly List PathBuffer = new List(); internal static bool ShouldEscape(Character summon, Player owner) { if (!SkeletonCrewPlugin.AvoidLava.Value || (Object)(object)summon == (Object)null || summon.IsDead()) { return false; } if ((Object)(object)owner != (Object)null && Hazard.Hurting((Character)(object)owner)) { return false; } return Hazard.Hurting(summon); } private static bool InFire(Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return (Object)(object)EffectArea.IsPointInsideArea(point, (Type)10, 0f) != (Object)null; } internal static bool TryEscape(Character summon, Player owner, ref Vector3 latched, ref float until, out Vector3 destination) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_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_0048: Unknown result type (might be due to invalid IL or missing references) destination = latched; if (Time.time < until) { if (!IsLava(latched) && !InFire(latched)) { Vector3 val = ((Component)summon).transform.position - latched; if (!(((Vector3)(ref val)).sqrMagnitude <= 4f)) { return true; } } until = 0f; return false; } if (!ShouldEscape(summon, owner)) { return false; } if (!TryFindSafeGround(((Component)summon).transform.position, out var safe)) { if (Time.time >= _nextTrappedLog) { _nextTrappedLog = Time.time + 5f; SkeletonCrewPlugin.Log.LogWarning((object)("'" + summon.GetHoverName() + "' is burning and cannot find safe ground within 24m.")); } return false; } latched = safe; destination = safe; until = Time.time + SkeletonCrewPlugin.LavaEscapeSeconds.Value; return true; } internal static bool TryFindSafeGround(Vector3 from, out Vector3 safe) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) safe = from; Vector3 val = default(Vector3); float y = default(float); for (float num = 2f; num <= 24f; num += 2f) { for (int i = 0; i < 16; i++) { float num2 = (float)i * ((float)Math.PI / 8f); ((Vector3)(ref val))..ctor(from.x + Mathf.Cos(num2) * num, from.y, from.z + Mathf.Sin(num2) * num); if (!IsLava(val) && !InFire(val)) { if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.FindFloor(val + Vector3.up * 4f, ref y)) { val.y = y; } safe = val; return true; } } } return false; } internal static bool Between(Vector3 from, Vector3 to) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0074: Unknown result type (might be due to invalid IL or missing references) float value = SkeletonCrewPlugin.LavaCrossingTolerance.Value; if (!SkeletonCrewPlugin.AvoidLava.Value || (Object)(object)ZoneSystem.instance == (Object)null || value <= 0f) { return false; } Vector3 val = to - from; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 2f) { return false; } val /= magnitude; float num = 0f; for (float num2 = 2f; num2 < magnitude; num2 += 2f) { if (IsLava(from + val * num2)) { num += 2f; if (num >= value) { return true; } } else { num = 0f; } } return false; } internal static bool TryRoute(Vector3 from, Vector3 to, out Vector3 waypoint) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0099: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: 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_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) waypoint = to; if (!Between(from, to)) { return true; } Vector3 val = to - from; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 2f) { return false; } val /= magnitude; Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(0f - val.z, 0f, val.x); Vector3 val3 = from + val * (magnitude * 0.5f); float num = Mathf.Min(60f, Mathf.Max(20f, magnitude)); float y = default(float); for (float num2 = 10f; num2 <= num; num2 += 10f) { for (int num3 = 1; num3 >= -1; num3 -= 2) { Vector3 val4 = val3 + val2 * (num2 * (float)num3); if (!IsLava(val4) && !InFire(val4) && !Between(from, val4) && !Between(val4, to) && !(Vector3.Distance(from, val4) + Vector3.Distance(val4, to) > magnitude * SkeletonCrewPlugin.RouteDetourFactor.Value)) { if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.FindFloor(val4 + Vector3.up * 4f, ref y)) { val4.y = y; } waypoint = val4; return true; } } } return false; } internal static bool Crosses(Vector3 from, Vector3 to) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.AvoidLava.Value || (Object)(object)ZoneSystem.instance == (Object)null) { return false; } Vector3 val = to - from; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 1f) { return false; } val /= magnitude; for (float num = 1f; num < magnitude; num += 1f) { if (IsLava(from + val * num)) { return true; } } return false; } internal static Vector3 StopShortOf(Vector3 from, Vector3 to) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) Vector3 val = to - from; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 1f || (Object)(object)ZoneSystem.instance == (Object)null) { return from; } val /= magnitude; Vector3 val2 = from; float num = 0f; for (float num2 = 1f; num2 < magnitude; num2 += 1f) { Vector3 val3 = from + val * num2; if (IsLava(val3)) { break; } val2 = val3; num = num2; } if (!(num > 2f)) { return from; } return val2 - val * 2f; } internal static bool PathIsClear(Vector3 from, Vector3 to, AgentType agent, out Vector3 lastSafe) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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_005b: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) lastSafe = from; if (!SkeletonCrewPlugin.AvoidLava.Value || (Object)(object)Pathfinding.instance == (Object)null) { return true; } PathBuffer.Clear(); if (!Pathfinding.instance.GetPath(from, to, PathBuffer, agent, true, true, false) || PathBuffer.Count == 0) { return !Crosses(from, to); } Vector3 val = from; foreach (Vector3 item in PathBuffer) { Vector3 val2 = item - val; float magnitude = ((Vector3)(ref val2)).magnitude; if (magnitude > 0.01f) { Vector3 val3 = val2 / magnitude; for (float num = 0f; num <= magnitude; num += 2f) { if (IsLava(val + val3 * num)) { return false; } } } val = item; lastSafe = item; } return true; } internal static bool Safe(Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (!IsLava(point)) { return !InFire(point); } return false; } internal static bool IsLava(Vector3 point) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance != (Object)null) { return instance.IsLava(point, false); } return false; } internal static Vector3 Steer(Character summon, Vector3 from, Vector3 to) { //IL_0018: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.AvoidLava.Value || !SkeletonCrewPlugin.LavaDodge.Value) { return to; } if ((Object)(object)summon != (Object)null && summon.InLava()) { return to; } Vector3 val = to - from; val.y = 0f; float value = SkeletonCrewPlugin.LavaLookahead.Value; if (((Vector3)(ref val)).sqrMagnitude < 0.01f || value <= 0f) { return to; } ((Vector3)(ref val)).Normalize(); if (!IsLava(from + val * value)) { return to; } float[] dodgeAngles = DodgeAngles; foreach (float num in dodgeAngles) { Vector3 val2 = Quaternion.Euler(0f, num, 0f) * val; Vector3 val3 = from + val2 * value; if (!IsLava(val3)) { return val3; } } return from; } } internal static class Pressure { private readonly struct Blow { internal readonly float At; internal readonly float Amount; internal Blow(float at, float amount) { At = at; Amount = amount; } } private static readonly Dictionary> Recent = new Dictionary>(); internal static void Note(Character c, HitData hit) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_0074: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c == (Object)null || hit == null || c.IsDead() || (!c.IsPlayer() && !c.IsTamed())) { return; } float totalDamage = hit.GetTotalDamage(); if (totalDamage <= 0f) { return; } ZNetView component = ((Component)c).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { ZDOID uid = component.GetZDO().m_uid; if (!Recent.TryGetValue(uid, out var value)) { value = new List(); Recent[uid] = value; } value.Add(new Blow(Time.time, totalDamage)); } } internal static float Incoming(Character c) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c == (Object)null) { return 0f; } ZNetView component = ((Component)c).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid() || !Recent.TryGetValue(component.GetZDO().m_uid, out var value)) { return 0f; } float num = Mathf.Max(0.5f, SkeletonCrewPlugin.PressureWindowSeconds.Value); float num2 = Time.time - num; float num3 = 0f; for (int num4 = value.Count - 1; num4 >= 0; num4--) { if (value[num4].At < num2) { value.RemoveAt(num4); } else { num3 += value[num4].Amount; } } return num3 / num; } internal static bool Under(Character c) { return Incoming(c) > 0f; } internal static void Forget() { //IL_0068: 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_00a7: Unknown result type (might be due to invalid IL or missing references) if (Recent.Count == 0) { return; } float num = Time.time - 30f; List list = null; foreach (KeyValuePair> item in Recent) { List value = item.Value; if (value.Count == 0 || value[value.Count - 1].At < num) { (list ?? (list = new List())).Add(item.Key); } } if (list == null) { return; } foreach (ZDOID item2 in list) { Recent.Remove(item2); } } } internal static class SummonClass { private readonly struct Memoised { internal readonly float At; internal readonly SummonPreference? Pref; internal Memoised(float at, SummonPreference? pref) { At = at; Pref = pref; } } private static readonly Dictionary ByPrefab = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ByClass = new Dictionary(); internal static readonly SummonPreference[] All = (SummonPreference[])Enum.GetValues(typeof(SummonPreference)); private static bool _built; private const string ClassKey = "SkeletonCrew.Class"; private static float _lastNoticeAt = -1f; private const float MemoSeconds = 0.5f; private static readonly Dictionary Memo = new Dictionary(); internal static void Remember(Humanoid h, SummonPreference pref) { ZNetView val = (((Object)(object)h == (Object)null) ? null : ((Component)h).GetComponent()); if ((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()) { val.GetZDO().Set("SkeletonCrew.Class", (int)(pref + 1)); } } internal static void EnsureTagged(Humanoid h) { if (!((Object)(object)h == (Object)null) && !Stored(h).HasValue) { SummonPreference? summonPreference = Of(h); if (summonPreference.HasValue) { Remember(h, summonPreference.Value); } } } internal static SummonPreference? Stored(Humanoid h) { ZNetView val = (((Object)(object)h == (Object)null) ? null : ((Component)h).GetComponent()); if ((Object)(object)val == (Object)null || !val.IsValid()) { return null; } int num = val.GetZDO().GetInt("SkeletonCrew.Class", 0); if (num > 0) { return (SummonPreference)(num - 1); } return null; } internal static SummonPreference Wanted(Humanoid h) { SummonPreference? summonPreference = Stored(h); if (summonPreference.HasValue) { return summonPreference.Value; } SummonPreference summonPreference2 = SummonGroup.For(h); int num = SkeletonCrewPlugin.ClassLimit(summonPreference2)?.Value ?? 0; if (num <= 0 || CountInCrew(summonPreference2) < num) { return summonPreference2; } if (!Mathf.Approximately(_lastNoticeAt, Time.time)) { _lastNoticeAt = Time.time; string text = $"{summonPreference2} limit reached ({num}) - raised a warrior instead."; SkeletonCrewPlugin.Log.LogInfo((object)text); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false); } } return SummonPreference.Melee; } internal static int CountInCrew(SummonPreference pref) { int num = 0; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { Character character = item.Character; Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val != null && Of(val) == pref) { num++; } } return num; } internal static string[] ItemsFor(SummonPreference pref) { Build(); if (!ByClass.TryGetValue(pref, out var value)) { return new string[0]; } return value; } internal static bool Carries(Humanoid h, SummonPreference pref) { string[] array = ItemsFor(pref); if (array.Length == 0 || (Object)(object)h == (Object)null) { return false; } foreach (ItemData allItem in h.GetInventory().GetAllItems()) { object obj; if (allItem == null) { obj = null; } else { GameObject dropPrefab = allItem.m_dropPrefab; obj = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); } string text = (string)obj; if (string.IsNullOrEmpty(text)) { continue; } string[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (string.Equals(array2[i], text, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } internal static SummonPreference? Of(Humanoid h) { Build(); if ((Object)(object)h == (Object)null) { return null; } if (Memo.TryGetValue(h, out var value) && Time.time - value.At < 0.5f) { return value.Pref; } SummonPreference? summonPreference = null; int num = 0; SummonPreference[] all = All; foreach (SummonPreference summonPreference2 in all) { string[] array = ItemsFor(summonPreference2); if (array.Length != 0 && array.Length > num && CarriesAll(h, array)) { summonPreference = summonPreference2; num = array.Length; } } Memoise(h, summonPreference); return summonPreference; } private static void Memoise(Humanoid h, SummonPreference? pref) { if (Memo.Count > 64) { Memo.Clear(); } Memo[h] = new Memoised(Time.time, pref); } private static bool CarriesAll(Humanoid h, string[] items) { foreach (string b in items) { bool flag = false; foreach (ItemData allItem in h.GetInventory().GetAllItems()) { object a; if (allItem == null) { a = null; } else { GameObject dropPrefab = allItem.m_dropPrefab; a = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); } if (string.Equals((string?)a, b, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { return false; } } return true; } private static void Build() { if (_built) { return; } _built = true; ByPrefab.Clear(); ByClass.Clear(); SummonPreference[] all = All; foreach (SummonPreference summonPreference in all) { ConfigEntry val = SkeletonCrewPlugin.ClassItems(summonPreference); if (val == null) { continue; } val.SettingChanged -= OnChanged; val.SettingChanged += OnChanged; List list = new List(); string[] array = (val.Value ?? string.Empty).Split(new char[1] { ',' }); for (int j = 0; j < array.Length; j++) { string text = array[j].Trim(); if (text.Length > 0) { list.Add(text); } } ByClass[summonPreference] = list.ToArray(); foreach (string item in list) { if (!ByPrefab.ContainsKey(item)) { ByPrefab[item] = summonPreference; } } } } private static void OnChanged(object sender, EventArgs args) { _built = false; } } internal static class SummonGroup { private static readonly Dictionary Letters = new Dictionary { { 'd', SummonPreference.Defender }, { 'a', SummonPreference.Melee }, { 'm', SummonPreference.Melee }, { 'r', SummonPreference.Archer }, { 'b', SummonPreference.Archer }, { 'f', SummonPreference.FireMage }, { 'i', SummonPreference.IceMage }, { 'h', SummonPreference.Healer } }; private static readonly List Composition = new List(); private static readonly List CastQueue = new List(); private static readonly Dictionary Decided = new Dictionary(); private static float _singleEitr; private static float _singleHealth; private static int _planned; private static float _plannedAt = -1f; private const float PlanLife = 3f; internal static string ArmedName { get; private set; } internal static bool IsArmed => Composition.Count > 0; internal static int Remaining => Missing().Count; internal static string ShortfallReason() { if (SummonRegistry.CrewCapacity() - LiveCount() + Surplus().Count < Missing().Count) { return "no room for more"; } float num = _singleEitr * CostFactor(Missing().Count) * SkeletonCrewPlugin.SummonEitrCostMultiplier.Value; float num2 = (((Object)(object)Player.m_localPlayer == (Object)null) ? 0f : Player.m_localPlayer.GetEitr()); return $"needs {num:0} Eitr, you have {num2:0}"; } internal static List Missing() { List list = new List(); if (Composition.Count == 0) { return list; } Dictionary dictionary = Census(); foreach (SummonPreference item in Composition) { dictionary.TryGetValue(item, out var value); if (value > 0) { dictionary[item] = value - 1; } else { list.Add(item); } } return list; } internal static List Surplus() { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (Composition.Count == 0) { return list; } Dictionary dictionary = new Dictionary(); foreach (SummonPreference item in Composition) { dictionary.TryGetValue(item, out var value); dictionary[item] = value + 1; } foreach (SummonRegistry.Summon item2 in SummonRegistry.Owned) { if (!((Object)(object)item2.Character == (Object)null) && !item2.Character.IsDead() && !((Object)(object)item2.Tameable == (Object)null) && !TameableCullWeakestPatch.IsDismissed(item2.Id)) { Character character = item2.Character; SummonPreference valueOrDefault = SummonClass.Of((Humanoid)(object)((character is Humanoid) ? character : null)).GetValueOrDefault(); dictionary.TryGetValue(valueOrDefault, out var value2); if (value2 > 0) { dictionary[valueOrDefault] = value2 - 1; } else { list.Add(item2); } } } return list; } private static int LiveCount() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) int num = 0; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if ((Object)(object)item.Character != (Object)null && !item.Character.IsDead() && !TameableCullWeakestPatch.IsDismissed(item.Id)) { num++; } } return num; } private static Dictionary Census() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(); foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead() && !TameableCullWeakestPatch.IsDismissed(item.Id)) { Character character = item.Character; SummonPreference valueOrDefault = SummonClass.Of((Humanoid)(object)((character is Humanoid) ? character : null)).GetValueOrDefault(); dictionary.TryGetValue(valueOrDefault, out var value); dictionary[valueOrDefault] = value + 1; } } return dictionary; } internal static void NoteSingleCost(float eitr, float health) { if (eitr > 0f) { _singleEitr = eitr; } if (health > 0f) { _singleHealth = health; } _planned = Compute(); _plannedAt = Time.time; } internal static int Size() { if (Composition.Count == 0) { return 1; } if (_plannedAt >= 0f && Time.time - _plannedAt < 3f) { return _planned; } return Compute(); } internal static string Explain() { return $"want={Composition.Count} missing={Missing().Count} cap={SummonRegistry.CrewCapacity()} " + $"live={LiveCount()} surplus={Surplus().Count} " + $"eitr={(((Object)(object)Player.m_localPlayer == (Object)null) ? 0f : Player.m_localPlayer.GetEitr()):0} " + $"perSummonEitr={_singleEitr:0.#} -> raise {_planned}"; } private static int Compute() { if (Composition.Count == 0) { return 1; } int count = Missing().Count; if (count == 0) { return 0; } int num = SummonRegistry.CrewCapacity() - LiveCount(); int num2 = Mathf.Max(0, num) + Mathf.Min(Surplus().Count, Mathf.Max(0, count - num)); return Mathf.Clamp(count, 0, Mathf.Max(0, num2)); } internal static float CostFactor(int size) { if (size <= 0) { return 0f; } if (size == 1) { return 1f; } float num = SkeletonCrewPlugin.GroupCostPerExtra?.Value ?? 0f; float num2 = 1f + (float)(size - 1) * Mathf.Max(0f, num); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || _singleEitr <= 0f || Mathf.Approximately(num2, 1f)) { return num2; } float num3 = _singleEitr * SkeletonCrewPlugin.SummonEitrCostMultiplier.Value; return Mathf.Clamp((num3 <= 0f) ? num2 : (localPlayer.GetEitr() / num3), 1f, num2); } internal static float CostFactor() { return CostFactor(Size()); } internal static bool Arm(string name) { Disarm(); if (string.IsNullOrEmpty(name)) { return true; } foreach (KeyValuePair> item in All()) { if (string.Equals(item.Key, name, StringComparison.OrdinalIgnoreCase)) { Composition.AddRange(item.Value); ArmedName = item.Key; return true; } } return false; } internal static void Disarm() { Composition.Clear(); CastQueue.Clear(); Decided.Clear(); ArmedName = null; } internal static void BeginCast(int count) { CastQueue.Clear(); Decided.Clear(); _plannedAt = -1f; List list = Missing(); int num = Mathf.Max(0, SummonRegistry.CrewCapacity() - LiveCount()); int num2 = Mathf.Max(0, count - num); foreach (SummonRegistry.Summon item in Surplus()) { if (num2 <= 0) { break; } if (TameableCullWeakestPatch.Dismiss(item.Tameable)) { num2--; SkeletonCrewPlugin.Log.LogInfo((object)("Group '" + ArmedName + "': dismissed '" + item.Character.GetHoverName() + "' to make room - the composition has no place for it.")); } } for (int i = 0; i < count && i < list.Count; i++) { CastQueue.Add(list[i]); } } internal static SummonPreference For(Humanoid h) { if ((Object)(object)h != (Object)null && Decided.TryGetValue(h, out var value)) { return value; } SummonPreference summonPreference; if (CastQueue.Count > 0) { summonPreference = CastQueue[0]; CastQueue.RemoveAt(0); } else { summonPreference = SkeletonCrewPlugin.SummonWeaponPreference.Value; } if ((Object)(object)h != (Object)null) { Decided[h] = summonPreference; } return summonPreference; } internal static void Done(Humanoid h) { if ((Object)(object)h != (Object)null) { Decided.Remove(h); } } internal static List>> All() { List>> list = new List>>(); string text = SkeletonCrewPlugin.SummonPresets?.Value; if (string.IsNullOrEmpty(text)) { return list; } string[] array = text.Split(new char[1] { '|' }); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { '=' }); if (array2.Length == 2) { string text2 = array2[0].Trim(); List list2 = Parse(array2[1]); if (text2.Length > 0 && list2.Count > 0) { list.Add(new KeyValuePair>(text2, list2)); } } } return list; } private static List Parse(string composition) { List list = new List(); string[] array = composition.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } int result = 1; int j; for (j = 0; j < text.Length && char.IsDigit(text[j]); j++) { } if (j > 0 && !int.TryParse(text.Substring(0, j), out result)) { result = 1; } if (j >= text.Length || !Letters.TryGetValue(char.ToLowerInvariant(text[j]), out var value)) { SkeletonCrewPlugin.Log.LogWarning((object)("Summon preset: '" + text + "' names no class. Use d defender, a attacker, r ranged, f fire mage, i ice mage, h healer.")); continue; } for (int k = 0; k < Mathf.Clamp(result, 1, 12); k++) { list.Add(value); } } return list; } internal static string Describe(List classes) { List> list = new List>(); foreach (SummonPreference pref in classes) { int num = list.FindIndex((KeyValuePair c) => c.Key == pref); if (num < 0) { list.Add(new KeyValuePair(pref, 1)); } else { list[num] = new KeyValuePair(pref, list[num].Value + 1); } } StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair item in list) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(item.Value).Append(' ').Append(item.Key); } return stringBuilder.ToString(); } } internal enum Role { Front, Line, Mid, Back } internal static class SummonRole { internal static Role Of(Humanoid h) { SummonPreference? summonPreference = SummonClass.Of(h); if (summonPreference.HasValue) { switch (summonPreference.Value) { case SummonPreference.Defender: return Role.Front; case SummonPreference.Melee: return Role.Line; case SummonPreference.Archer: return Role.Mid; case SummonPreference.Healer: case SummonPreference.FireMage: case SummonPreference.IceMage: return Role.Back; } } if ((Object)(object)h != (Object)null && CarriesShield(h)) { return Role.Front; } if (!WeaponChoice.IsRanged((h == null) ? null : h.GetCurrentWeapon()?.m_shared)) { return Role.Line; } return Role.Mid; } internal static float HealthScale(Humanoid h) { return Stat(h, SkeletonCrewPlugin.ClassHealthScale); } internal static float DamageScale(Humanoid h) { return Stat(h, SkeletonCrewPlugin.ClassDamageScale); } internal static float ResistScale(Humanoid h) { return Stat(h, SkeletonCrewPlugin.ClassResistScale); } private static float Stat(Humanoid h, Func> lookup) { SummonPreference? summonPreference = SummonClass.Of(h); if (!summonPreference.HasValue) { return 1f; } ConfigEntry val = lookup(summonPreference.Value); if (val != null) { return Mathf.Max(0.05f, val.Value); } return 1f; } internal static bool Initiates(Role role) { return role != Role.Back; } internal static float RadiusScale(Role role) { return role switch { Role.Front => SkeletonCrewPlugin.FrontLineRadiusScale.Value, Role.Mid => 1.2f, Role.Back => SkeletonCrewPlugin.BackLineRadiusScale.Value, _ => 1f, }; } internal static float RadiusScale(Humanoid h, Role role) { if (SummonClass.Of(h) != SummonPreference.Healer) { return RadiusScale(role); } return SkeletonCrewPlugin.HealerFormationScale.Value; } internal static float Bearing(Humanoid h, Role role) { if (SummonClass.Of(h) != SummonPreference.Healer) { return Bearing(role); } return 2.4f; } internal static float Bearing(Role role) { return role switch { Role.Front => 0f, Role.Line => 1.05f, Role.Mid => 2.1f, _ => 3.14159f, }; } private static bool CarriesShield(Humanoid h) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Invalid comparison between Unknown and I4 foreach (ItemData allItem in h.GetInventory().GetAllItems()) { if (allItem != null && (int)(allItem.m_shared?.m_itemType).GetValueOrDefault() == 5) { return true; } } return false; } } internal static class ThreatScan { private readonly struct Threat { internal readonly Character Character; internal readonly Vector3 Position; internal readonly bool IsAggressor; internal readonly bool HuntsOwner; internal readonly Character Target; internal Threat(Character character, Vector3 position, bool isAggressor, bool huntsOwner, Character target) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Character = character; Position = position; IsAggressor = isAggressor; HuntsOwner = huntsOwner; Target = target; } } private static readonly List Threats = new List(); private static float _lastRefresh = -999f; private const float MaxAge = 0.4f; internal static void Refresh() { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) Threats.Clear(); _lastRefresh = Time.time; Player localPlayer = Player.m_localPlayer; foreach (Character allCharacter in Character.GetAllCharacters()) { if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.IsDead() && !allCharacter.IsPlayer() && !allCharacter.IsTamed()) { MonsterAI component = ((Component)allCharacter).GetComponent(); Character val = ((component != null) ? ((BaseAI)component).GetTargetCreature() : null); bool isAggressor = (Object)(object)val != (Object)null && (val.IsPlayer() || val.IsTamed()); bool huntsOwner = (Object)(object)localPlayer != (Object)null && (Object)(object)val == (Object)(object)localPlayer; Threats.Add(new Threat(allCharacter, ((Component)allCharacter).transform.position, isAggressor, huntsOwner, val)); } } } private static void EnsureFresh() { if (Time.time - _lastRefresh > 0.4f) { Refresh(); } } internal static bool AggressorNear(Vector3 point, float radius) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) EnsureFresh(); float num = radius * radius; foreach (Threat threat in Threats) { if (threat.IsAggressor && FlatSq(threat.Position, point) <= num && Alive(threat)) { return true; } } return false; } internal static Character AttackerOf(Character victim) { if ((Object)(object)victim == (Object)null) { return null; } EnsureFresh(); foreach (Threat threat in Threats) { if ((Object)(object)threat.Target == (Object)(object)victim && Alive(threat)) { return threat.Character; } } return null; } private static float FlatSq(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = a.x - b.x; float num2 = a.z - b.z; return num * num + num2 * num2; } internal static Character Hunter(Vector3 point, float radius) { //IL_0021: 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) EnsureFresh(); float num = radius * radius; Character result = null; float num2 = num; foreach (Threat threat in Threats) { float num3 = FlatSq(threat.Position, point); if (threat.HuntsOwner && num3 <= num2 && Alive(threat)) { num2 = num3; result = threat.Character; } } return result; } internal static bool Aggressing(Character hostile) { if ((Object)(object)hostile == (Object)null) { return false; } EnsureFresh(); foreach (Threat threat in Threats) { if ((Object)(object)threat.Character == (Object)(object)hostile) { return threat.IsAggressor; } } return false; } internal static Character Nearest(Vector3 point, float radius) { //IL_0021: 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) EnsureFresh(); Character result = null; float num = radius * radius; foreach (Threat threat in Threats) { float num2 = FlatSq(threat.Position, point); if (num2 <= num && Alive(threat)) { num = num2; result = threat.Character; } } return result; } internal static Character BestThreatTo(Vector3 point, float radius, bool aggressorsOnly) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) EnsureFresh(); Character val = null; bool flag = false; float num = float.MaxValue; float num2 = radius * radius; foreach (Threat threat in Threats) { if (!aggressorsOnly || threat.IsAggressor) { float num3 = FlatSq(threat.Position, point); if (!(num3 > num2) && Alive(threat) && ((Object)(object)val == (Object)null || (threat.HuntsOwner && !flag) || (threat.HuntsOwner == flag && num3 < num))) { val = threat.Character; flag = threat.HuntsOwner; num = num3; } } } return val; } private static bool Alive(Threat t) { if ((Object)(object)t.Character != (Object)null) { return !t.Character.IsDead(); } return false; } } internal static class Ward { private static float _nextTtlLog; internal static Character Patient(Character healer) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; Character val = null; Character val2 = null; float num = 0f; foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (!((Object)(object)item.Character == (Object)null) && !item.Character.IsDead() && !((Object)(object)item.Character == (Object)(object)healer)) { bool flag = SummonRegistry.RoleOf(item.Id) == Role.Front; float num2 = Pressure.Incoming(item.Character); if (flag && num2 > 0f) { return item.Character; } if (flag && (Object)(object)val == (Object)null) { val = item.Character; } if (num2 > num) { num = num2; val2 = item.Character; } } } if ((Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead() && Pressure.Incoming((Character)(object)localPlayer) > 0f) { return (Character)(object)localPlayer; } if ((Object)(object)val2 != (Object)null) { return val2; } return val; } internal static int Cover(Character healer, ItemData staff, out string report) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) report = null; StatusEffect val = staff?.m_shared?.m_consumeStatusEffect; if ((Object)(object)val == (Object)null) { return 0; } float value = SkeletonCrewPlugin.HealerWardRadius.Value; float num = value * value; int hash = val.NameHash(); int covered; Character val2 = BestCentre(hash, num, out covered); if ((Object)(object)val2 == (Object)null) { return 0; } int num2 = 0; int num3 = 0; foreach (Character item in Friendlies()) { Vector3 val3 = ((Component)item).transform.position - ((Component)val2).transform.position; if (!(((Vector3)(ref val3)).sqrMagnitude > num)) { if (Warded(item, hash)) { num3++; } else if (Apply(item, staff)) { num2++; } } } report = $"{num2} of {covered}" + ((num3 > 0) ? $" ({num3} already up)" : string.Empty) + ", centred on '" + val2.GetHoverName() + "'"; return num2; } internal static bool Approaching(out float distance) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) float value = SkeletonCrewPlugin.WardApproachRadius.Value; distance = float.MaxValue; foreach (Character item in Friendlies()) { Character val = ThreatScan.Nearest(((Component)item).transform.position, value); if (!((Object)(object)val == (Object)null)) { float num = Vector3.Distance(((Component)val).transform.position, ((Component)item).transform.position); if (num < distance) { distance = num; } } } if (distance < float.MaxValue) { return true; } Player localPlayer = Player.m_localPlayer; Character val2 = (((Object)(object)localPlayer == (Object)null) ? null : ThreatScan.Hunter(((Component)localPlayer).transform.position, value)); if ((Object)(object)val2 == (Object)null) { return false; } distance = Vector3.Distance(((Component)val2).transform.position, ((Component)localPlayer).transform.position); return true; } internal static bool Needed(ItemData staff, out int missing) { missing = 0; StatusEffect val = staff?.m_shared?.m_consumeStatusEffect; if ((Object)(object)val == (Object)null) { return false; } float value = SkeletonCrewPlugin.HealerWardRadius.Value; if ((Object)(object)BestCentre(val.NameHash(), value * value, out missing) != (Object)null) { return missing > 0; } return false; } private static Character BestCentre(int hash, float radiusSq, out int covered) { //IL_0050: 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) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) Character result = null; int num = 0; float num2 = -1f; covered = 0; foreach (Character item in Friendlies()) { int num3 = 0; foreach (Character item2 in Friendlies()) { if (!Warded(item2, hash)) { Vector3 val = ((Component)item2).transform.position - ((Component)item).transform.position; if (((Vector3)(ref val)).sqrMagnitude <= radiusSq) { num3++; } } } if (num3 != 0) { float num4 = Pressure.Incoming(item); if (num3 > num || (num3 == num && num4 > num2)) { result = item; num = num3; num2 = num4; } } } covered = num; return result; } private static void Lengthen(SEMan seman, int hash) { float value = SkeletonCrewPlugin.HealerWardSeconds.Value; if (value <= 0f) { return; } StatusEffect statusEffect = seman.GetStatusEffect(hash); if (!((Object)(object)statusEffect == (Object)null)) { statusEffect.m_ttl = value; if (SkeletonCrewPlugin.VerboseLogging.Value && Time.time >= _nextTtlLog) { _nextTtlLog = Time.time + 10f; ManualLogSource log = SkeletonCrewPlugin.Log; Character character = seman.m_character; log.LogInfo((object)("Ward on '" + (((character != null) ? character.GetHoverName() : null) ?? "") + "' set to " + $"{statusEffect.m_ttl:0}s (was {statusEffect.GetRemaningTime():0}s remaining).")); } } } private static IEnumerable Friendlies() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead()) { yield return (Character)(object)localPlayer; } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if ((Object)(object)item.Character != (Object)null && !item.Character.IsDead()) { yield return item.Character; } } } private static bool Warded(Character c, int hash) { SEMan val = (((Object)(object)c == (Object)null) ? null : c.GetSEMan()); if (val != null) { return val.HaveStatusEffect(hash); } return false; } internal static bool Apply(Character target, ItemData staff) { StatusEffect val = staff?.m_shared?.m_consumeStatusEffect; SEMan val2 = (((Object)(object)target == (Object)null) ? null : target.GetSEMan()); if ((Object)(object)val == (Object)null || val2 == null) { return false; } int num = val.NameHash(); if (val2.HaveStatusEffect(num)) { Lengthen(val2, num); return false; } val2.AddStatusEffect(val, true, staff.m_quality, 0f); Lengthen(val2, num); return val2.HaveStatusEffect(num); } } internal static class WeaponChoice { internal static void Tick() { if (!SkeletonCrewPlugin.SmartWeaponChoice.Value) { return; } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { Apply(item); } } private static void Apply(SummonRegistry.Summon s) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) Character character = s.Character; Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val == null || (Object)(object)s.AI == (Object)null || s.Character.IsDead() || SummonRecon.IsPinned(s.Id)) { return; } if (SummonRegistry.IntentFor(s.Id).Activity == Activity.Working) { EquipMelee(val); return; } ItemData val2 = SoleWeapon(val); if (val2 != null) { if (val.GetCurrentWeapon() != val2) { val.EquipItem(val2, false); } } else { if (FindWeapon(val, ranged: true) == null || FindWeapon(val, ranged: false) == null || s.Character.InAttack()) { return; } Character targetCreature = ((BaseAI)s.AI).GetTargetCreature(); if ((Object)(object)targetCreature == (Object)null) { return; } bool flag = WantsRanged(s, val, targetCreature); ItemData currentWeapon = val.GetCurrentWeapon(); bool flag2 = currentWeapon != null && IsRanged(currentWeapon); if (currentWeapon != null && flag2 == flag) { ApplyKiting(s, flag); } else if (SummonRegistry.MaySwapWeapon(s.Id)) { ItemData val3 = FindWeapon(val, flag); if (val3 != null) { val.EquipItem(val3, false); SummonRegistry.NoteWeaponSwap(s.Id); ApplyKiting(s, flag); } } } } private static bool WantsRanged(SummonRegistry.Summon s, Humanoid h, Character target) { //IL_0021: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) ItemData val = FindWeapon(h, ranged: true); if (val == null || !Attack.HaveAmmo(h, val)) { return false; } Vector3 position = ((Component)s.Character).transform.position; float num = Vector3.Distance(position, ((Component)target).transform.position); if (num <= SkeletonCrewPlugin.MeleeSwitchDistance.Value) { return false; } if (!((BaseAI)s.AI).CanSeeTarget(target)) { return false; } if (FriendlyInLineOfFire(s, position, target)) { return false; } if (num < SkeletonCrewPlugin.RangedSwitchDistance.Value) { ItemData currentWeapon = h.GetCurrentWeapon(); if (currentWeapon != null) { return IsRanged(currentWeapon); } return false; } return true; } private static bool FriendlyInLineOfFire(SummonRegistry.Summon s, Vector3 from, Character target) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)target).transform.position - from; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.01f) { return true; } Vector3 aim = val / magnitude; float cosLimit = Mathf.Cos(SkeletonCrewPlugin.FriendlyFireConeDegrees.Value * ((float)Math.PI / 180f)); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && InCone(from, aim, magnitude, cosLimit, ((Component)localPlayer).transform.position)) { return true; } foreach (SummonRegistry.Summon item in SummonRegistry.Owned) { if (item != s && !((Object)(object)item.Character == (Object)null) && !item.Character.IsDead() && InCone(from, aim, magnitude, cosLimit, ((Component)item.Character).transform.position)) { return true; } } return false; } private static bool InCone(Vector3 from, Vector3 aim, float range, float cosLimit, Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_0026: Unknown result type (might be due to invalid IL or missing references) Vector3 val = point - from; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.01f || magnitude > range) { return false; } return Vector3.Dot(val / magnitude, aim) >= cosLimit; } private static void ApplyKiting(SummonRegistry.Summon s, bool ranged) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (!SkeletonCrewPlugin.ArchersKeepDistance.Value) { return; } if (FindWeapon((Humanoid)s.Character, ranged: false) != null) { SummonRegistry.ApplyCircling(s, ranged: false); return; } Character val = (((Object)(object)s.AI == (Object)null) ? null : ((BaseAI)s.AI).GetTargetCreature()); int num; if ((Object)(object)val != (Object)null) { Vector3 val2 = ((Component)val).transform.position - ((Component)s.Character).transform.position; num = ((((Vector3)(ref val2)).sqrMagnitude < SkeletonCrewPlugin.KiteDistance.Value * SkeletonCrewPlugin.KiteDistance.Value) ? 1 : 0); } else { num = 0; } bool flag = (byte)num != 0; SummonRegistry.ApplyCircling(s, ranged && flag); } internal static float ReachOf(Humanoid h) { float valueOrDefault = ((h == null) ? ((float?)null) : h.GetCurrentWeapon()?.m_shared?.m_aiAttackRange).GetValueOrDefault(); return Mathf.Max(2f, valueOrDefault * 0.9f); } internal static bool EquipMelee(Humanoid h) { ItemData currentWeapon = h.GetCurrentWeapon(); if (currentWeapon != null && !IsRanged(currentWeapon)) { return true; } ItemData val = FindWeapon(h, ranged: false); if (val == null) { if (currentWeapon != null) { h.UnequipItem(currentWeapon, false); } return true; } h.EquipItem(val, false); ItemData currentWeapon2 = h.GetCurrentWeapon(); if (currentWeapon2 != null) { return !IsRanged(currentWeapon2); } return false; } private static ItemData FindWeapon(Humanoid h, bool ranged) { Inventory inventory = h.GetInventory(); if (inventory == null) { return null; } foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem?.m_shared != null && IsWeapon(allItem) && IsRanged(allItem) == ranged) { return allItem; } } return null; } internal static bool IsRanged(ItemData item) { return IsRanged(item?.m_shared); } internal static bool IsRanged(SharedData shared) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Invalid comparison between Unknown and I4 //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Invalid comparison between Unknown and I4 if (shared == null) { return false; } if ((int)shared.m_itemType == 4 || (int)shared.m_skillType == 8 || (int)shared.m_skillType == 14 || !string.IsNullOrEmpty(shared.m_ammoType)) { return true; } if (shared.m_attack != null) { if ((int)shared.m_attack.m_attackType != 2) { return (int)shared.m_attack.m_attackType == 5; } return true; } return false; } internal static ItemData SoleWeapon(Humanoid h) { ItemData val = null; foreach (ItemData allItem in h.GetInventory().GetAllItems()) { if (allItem != null && IsWeapon(allItem)) { if (val != null) { return null; } val = allItem; } } return val; } internal static bool IsWeapon(ItemData item) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 ItemType itemType = item.m_shared.m_itemType; if ((int)itemType != 3 && (int)itemType != 14 && (int)itemType != 22) { return (int)itemType == 4; } return true; } } internal static class WeaponRebind { private static readonly FieldInfo[] SharedFields = typeof(SharedData).GetFields(BindingFlags.Instance | BindingFlags.Public); private static readonly Dictionary Alternates = new Dictionary(); private static readonly HashSet Ours = new HashSet(); internal static bool HasAlternate(ItemData item) { if (item?.m_shared != null) { return Alternates.ContainsKey(item.m_shared); } return false; } internal static bool WithAlternate(ItemData item, Func cast) { if (item?.m_shared == null || !Alternates.TryGetValue(item.m_shared, out var value) || value == null) { return false; } Attack attack = item.m_shared.m_attack; item.m_shared.m_attack = value; try { return cast(); } finally { item.m_shared.m_attack = attack; } } internal static SharedData Privatise(ItemData item) { SharedData val = item?.m_shared; if (val == null || Ours.Contains(val)) { return val; } item.m_shared = Copy(val); return item.m_shared; } private static SharedData Copy(SharedData original) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown SharedData val = new SharedData(); FieldInfo[] sharedFields = SharedFields; foreach (FieldInfo fieldInfo in sharedFields) { fieldInfo.SetValue(val, fieldInfo.GetValue(original)); } if (original.m_attack != null) { val.m_attack = original.m_attack.Clone(); } if (original.m_secondaryAttack != null) { val.m_secondaryAttack = original.m_secondaryAttack.Clone(); } Ours.Add(val); return val; } internal static void MakeCreatureUsable(ItemData item, float aiRange) { SharedData val = Privatise(item); if (val == null) { return; } if (val.m_attack != null) { val.m_attack.m_attackEitr = 0f; val.m_attack.m_attackHealth = 0f; val.m_attack.m_attackStamina = 0f; val.m_attack.m_attackHealthPercentage = 0f; } if (val.m_secondaryAttack != null) { val.m_secondaryAttack.m_attackEitr = 0f; val.m_secondaryAttack.m_attackHealth = 0f; val.m_secondaryAttack.m_attackStamina = 0f; val.m_secondaryAttack.m_attackHealthPercentage = 0f; } if (val.m_aiAttackRange <= 0f) { val.m_aiAttackRange = ((aiRange > 0f) ? aiRange : 12f); } if (val.m_aiAttackInterval <= 0f) { val.m_aiAttackInterval = 2f; } val.m_aiAttackRangeMin = 0f; val.m_aiWhenWalking = true; if (val.m_secondaryAttack != null) { if (!Alternates.ContainsKey(val)) { Alternates[val] = val.m_secondaryAttack; } val.m_secondaryAttack = null; } } internal static bool Graft(ItemData item, GameObject donorPrefab) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) ItemData val = (((Object)(object)donorPrefab == (Object)null) ? null : donorPrefab.GetComponent()?.m_itemData); if (val?.m_shared?.m_attack == null) { return false; } SharedData val2 = Privatise(item); if (val2 == null) { return false; } Alternates[val2] = val2.m_attack; val2.m_attack = val.m_shared.m_attack.Clone(); val2.m_secondaryAttack = null; val2.m_aiTargetType = val.m_shared.m_aiTargetType; val2.m_aiPrioritized = val.m_shared.m_aiPrioritized; val2.m_aiAttackRange = val.m_shared.m_aiAttackRange; val2.m_aiAttackRangeMin = val.m_shared.m_aiAttackRangeMin; val2.m_aiAttackInterval = val.m_shared.m_aiAttackInterval; ManualLogSource log = SkeletonCrewPlugin.Log; string[] obj = new string[11] { "Grafted '", val.m_shared.m_name, "' (", ((Object)donorPrefab).name, ") onto '", val2.m_name, "': ", $"{val2.m_attack.m_attackType}, target={val2.m_aiTargetType}, ", $"range={val2.m_aiAttackRange:F1}, fires ", null, null }; GameObject attackProjectile = val2.m_attack.m_attackProjectile; obj[9] = ((attackProjectile != null) ? ((Object)attackProjectile).name : null) ?? ""; obj[10] = ". Its own attack is held back as a second ability, out of the AI's reach."; log.LogInfo((object)string.Concat(obj)); return true; } internal static bool To(ItemData item, string animation, float aiRange = 0f) { SharedData val = item?.m_shared; if (val == null || (string.IsNullOrEmpty(animation) && aiRange <= 0f)) { return false; } float aiAttackRange = val.m_aiAttackRange; string name = val.m_name; SharedData val2 = Privatise(item); if (aiRange > 0f) { val2.m_aiAttackRange = aiRange; } if (!string.IsNullOrEmpty(animation)) { if (val2.m_attack != null) { val2.m_attack.m_attackAnimation = animation; } if (val2.m_secondaryAttack != null) { val2.m_secondaryAttack.m_attackAnimation = animation; } if (Alternates.TryGetValue(val2, out var value) && value != null) { value.m_attackAnimation = animation; } } if (Alternates.TryGetValue(val2, out var value2) && value2 != null) { value2.m_attackEitr = 0f; value2.m_attackHealth = 0f; value2.m_attackStamina = 0f; value2.m_attackHealthPercentage = 0f; } bool flag = !Mathf.Approximately(aiAttackRange, val2.m_aiAttackRange); if (flag || !string.IsNullOrEmpty(animation)) { SkeletonCrewPlugin.Log.LogInfo((object)("Rebound '" + name + "' on this item only: animation '" + (string.IsNullOrEmpty(animation) ? "" : animation) + "'" + (flag ? $", aiRange {aiAttackRange:F1} -> {val2.m_aiAttackRange:F1}" : string.Empty) + " (attack type " + (((object)Unsafe.As(ref val2.m_attack?.m_attackType)/*cast due to .constrained prefix*/).ToString() ?? "") + ").")); } return true; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { internal IgnoresAccessChecksToAttribute(string assemblyName) { } } }