Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ScalpelsSkellyStaff v1.0.0
BepInEx/plugins/ScalpelsSkellyStaff/ScalpelsSkellyStaff.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace ScalpelsSkellyStaff; internal static class Native { internal static readonly int OwnerKey = StringExtensionMethods.GetStableHashCode("au.tyvalheim.scalpelsskellystaff.summoner"); internal static readonly int TypeKey = StringExtensionMethods.GetStableHashCode("au.tyvalheim.scalpelsskellystaff.type"); internal static readonly FieldInfo Seed = Required(typeof(Humanoid), "m_seed"); private static readonly FieldInfo maxAir = Required(typeof(Character), "m_maxAirAltitude"); private static readonly FieldInfo groundTouch = Required(typeof(Character), "m_lastGroundTouch"); private static readonly FieldInfo groundCollider = Required(typeof(Character), "m_lastGroundCollider"); private static readonly FieldInfo groundBody = Required(typeof(Character), "m_lastGroundBody"); private static readonly FieldInfo creatureTarget = Required(typeof(MonsterAI), "m_targetCreature"); private static readonly FieldInfo staticTarget = Required(typeof(MonsterAI), "m_targetStatic"); private static readonly FieldInfo path = Required(typeof(BaseAI), "m_path"); private static readonly FieldInfo pathTime = Required(typeof(BaseAI), "m_lastFindPathTime"); private static readonly Action<Character> resetGround = (Action<Character>)Delegate.CreateDelegate(typeof(Action<Character>), AccessTools.Method(typeof(Character), "ResetGroundContact", (Type[])null, (Type[])null)); private static readonly Action<Character> invalidateLiquid = (Action<Character>)Delegate.CreateDelegate(typeof(Action<Character>), AccessTools.Method(typeof(Character), "InvalidateCachedLiquidDepth", (Type[])null, (Type[])null)); internal static readonly Func<Player, bool> CanInput = (Func<Player, bool>)Delegate.CreateDelegate(typeof(Func<Player, bool>), AccessTools.Method(typeof(Player), "TakeInput", (Type[])null, (Type[])null)); private static FieldInfo Required(Type type, string name) { FieldInfo fieldInfo = AccessTools.Field(type, name); if (fieldInfo == null) { throw new MissingFieldException(type.Name, name); } return fieldInfo; } internal static void Validate() { if (Seed.FieldType != typeof(int)) { throw new InvalidOperationException("Unsupported Humanoid seed field."); } } internal static bool IsSkeleton(GameObject obj) { if ((Object)(object)obj != (Object)null) { if (!(((Object)obj).name == "Skeleton_Friendly")) { return ((Object)obj).name == "Skeleton_Friendly(Clone)"; } return true; } return false; } internal static void Move(Character character, MonsterAI ai, ZNetView view, Player player, Vector3 destination) { //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_0027: 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_003b: 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_005b: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_0096: Unknown result type (might be due to invalid IL or missing references) if (!view.IsOwner()) { throw new InvalidOperationException("Recall must own the network instance before moving it."); } ZDO zDO = view.GetZDO(); Quaternion rotation = ((Component)player).transform.rotation; zDO.SetPosition(destination); zDO.SetRotation(rotation); zDO.Set(ZDOVars.s_bodyVelHash, Vector3.zero); zDO.Set(ZDOVars.s_bodyAVelHash, Vector3.zero); ((Component)character).transform.SetPositionAndRotation(destination, rotation); Rigidbody component = ((Component)character).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { component.position = destination; component.rotation = rotation; if (!component.isKinematic) { component.linearVelocity = Vector3.zero; component.angularVelocity = Vector3.zero; } } resetGround(character); groundCollider.SetValue(character, null); groundBody.SetValue(character, null); groundTouch.SetValue(character, 1f); maxAir.SetValue(character, destination.y); invalidateLiquid(character); creatureTarget.SetValue(ai, null); staticTarget.SetValue(ai, null); if (path.GetValue(ai) is List<Vector3> list) { list.Clear(); } pathTime.SetValue(ai, -1000f); ((BaseAI)ai).ResetPatrolPoint(); ((BaseAI)ai).ResetRandomMovement(); ((BaseAI)ai).StopMoving(); ai.SetFollowTarget(((Component)player).gameObject); zDO.Set(ZDOVars.s_follow, player.GetPlayerName()); zDO.Set(OwnerKey, player.GetPlayerID()); Physics.SyncTransforms(); character.ResetCloth(); } } [BepInPlugin("au.tyvalheim.scalpelsskellystaff", "Scalpel's Skelly Staff", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Id = "au.tyvalheim.scalpelsskellystaff"; public const string Version = "1.0.0"; internal static Plugin Instance; internal static bool Ready; internal ConfigEntry<SkeletonType> Type; internal ConfigEntry<KeyboardShortcut> RecallKey; internal ConfigEntry<KeyboardShortcut> CycleKey; internal ConfigEntry<bool> SuppressSecondary; internal ConfigEntry<bool> TransferOwnership; internal ConfigEntry<bool> ExistingSummons; internal ConfigEntry<bool> AutoRecall; internal ConfigEntry<bool> Hints; internal ConfigEntry<bool> Diagnostics; internal ConfigEntry<bool> TraceSummons; internal ConfigEntry<float> RecallRange; internal ConfigEntry<float> PlacementRadius; internal ConfigEntry<float> Cooldown; internal ConfigEntry<float> AutoDistance; internal ConfigEntry<float> CheckInterval; private Harmony harmony; private float nextRecall; private float nextAuto; private float nextError; private int inputFrame = -1; private bool holding; private Player lastPlayer; private void Awake() { //IL_0043: 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_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown Instance = this; Type = ((BaseUnityPlugin)this).Config.Bind<SkeletonType>("Summoning", "SelectedType", SkeletonType.Melee, "Type for future Dead raiser summons. Shift + middle-click cycles this and saves it. Existing skeletons are unchanged."); RecallKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "Recall", new KeyboardShortcut((KeyCode)325, (KeyCode[])(object)new KeyCode[0]), "Recall while holding the Dead raiser. Mouse2 is middle mouse. None disables this shortcut."); CycleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "CycleType", new KeyboardShortcut((KeyCode)325, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Cycle Melee / Archer / Random. Takes priority over Recall when both match."); SuppressSecondary = ((BaseUnityPlugin)this).Config.Bind<bool>("Controls", "SuppressSecondaryAttack", true, "Suppress the staff's secondary attack while a configured command shortcut is held. Other weapons are unaffected."); RecallRange = Float("Recall", "MaximumDistance", 200f, 5f, 500f, "Maximum distance in metres. Only skeletons currently loaded on this client can be recalled; increasing this does not load distant areas."); PlacementRadius = Float("Recall", "PlacementRadius", 3f, 1.5f, 8f, "Preferred arrival radius in metres. Searches clear ground around the player; skips skeletons when no suitable point is found."); Cooldown = Float("Recall", "CooldownSeconds", 3f, 0.5f, 60f, "Minimum time between recall attempts. Recall has no health or Eitr cost and does not heal."); TransferOwnership = ((BaseUnityPlugin)this).Config.Bind<bool>("Recall", "AllowOwnershipTransfer", true, "Use Valheim's native ownership transfer for your loaded summons when another peer simulates them. Disable to recall only locally simulated skeletons."); ExistingSummons = ((BaseUnityPlugin)this).Config.Bind<bool>("Recall", "IncludeExistingFollowers", true, "Also recall unmarked friendly skeletons already following you, including ones summoned before installing. Original summoner cannot be recovered for these; following you is the eligibility rule."); AutoRecall = ((BaseUnityPlugin)this).Config.Bind<bool>("AutomaticRecall", "Enabled", false, "Optional distance catch-up while the staff is held. Not a stuck detector. Manual recall handles nearby stuck skeletons."); AutoDistance = Float("AutomaticRecall", "Distance", 40f, 10f, 150f, "Recall loaded eligible skeletons farther than this many metres away. Effective minimum is also at least two metres beyond PlacementRadius."); CheckInterval = Float("AutomaticRecall", "CheckIntervalSeconds", 2f, 0.5f, 10f, "Time between automatic distance checks; the recall cooldown also applies."); Hints = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "ShowEquipHint", true, "Show selected type and shortcuts when equipping the staff. Type changes and manual recall results are always shown."); Diagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseLogging", false, "Log seed selection, actual equipment checks, ownership transfers, and recall summaries to BepInEx/LogOutput.log. Errors and selection failures are always logged."); TraceSummons = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "TraceSummons", false, "Optional trace of each captured cast, spawn, selected seed and resulting equipment to investigate inconsistent type selection. Does not change selection behaviour."); try { Native.Validate(); harmony = new Harmony("au.tyvalheim.scalpelsskellystaff"); harmony.PatchAll(typeof(Plugin).Assembly); Ready = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Scalpel's Skelly Staff 1.0.0 loaded; built against Valheim 1.0.16. Client controls; loaded-summon recall only."); } catch (Exception ex) { if (harmony != null) { harmony.UnpatchSelf(); } ((BaseUnityPlugin)this).Logger.LogError((object)("Skelly Staff disabled: game hooks did not match. " + ex)); } } private ConfigEntry<float> Float(string section, string key, float value, float min, float max, string description) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<float>(section, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), new object[0])); } internal static bool IsStaff(ItemData item) { if (item != null && (Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name == "StaffSkeleton"; } return false; } internal static bool CanCommand(Player player) { if (Ready && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && IsStaff(((Humanoid)player).GetCurrentWeapon()) && Native.CanInput(player) && !InventoryGui.IsVisible() && !Console.IsVisible() && !TextInput.IsVisible() && !Menu.IsVisible()) { if (!((Object)(object)Chat.instance == (Object)null)) { return !Chat.instance.HasFocus(); } return true; } return false; } private static bool Down(KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0) { return ((KeyboardShortcut)(ref shortcut)).IsDown(); } return false; } private static bool Held(KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0) { return ((KeyboardShortcut)(ref shortcut)).IsPressed(); } return false; } internal bool ConsumeSecondary(Player player) { //IL_001b: 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) if (CanCommand(player) && SuppressSecondary.Value) { if (!Held(RecallKey.Value)) { return Held(CycleKey.Value); } return true; } return false; } internal void PollInput(Player player) { //IL_0028: 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) if (inputFrame == Time.frameCount) { return; } inputFrame = Time.frameCount; if (!CanCommand(player)) { return; } if (Down(CycleKey.Value)) { Type.Value = Rules.Next(Type.Value); ((BaseUnityPlugin)this).Config.Save(); Notify("Next summon: " + Type.Value); } else if (Down(RecallKey.Value)) { if (Time.unscaledTime < nextRecall) { Notify("Recall ready in " + Math.Ceiling(nextRecall - Time.unscaledTime) + "s."); return; } nextRecall = Time.unscaledTime + Cooldown.Value; Recall.Run(player, automatic: false); } } private void Update() { //IL_00a4: 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) if (!Ready) { return; } try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)lastPlayer != (Object)(object)localPlayer) { lastPlayer = localPlayer; holding = false; nextRecall = (nextAuto = 0f); } bool flag = (Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead() && IsStaff(((Humanoid)localPlayer).GetCurrentWeapon()); if (flag && !holding && Hints.Value) { Notify(string.Concat(Type.Value, " | Recall: ", RecallKey.Value, " | Type: ", CycleKey.Value)); } holding = flag; PollInput(localPlayer); if (AutoRecall.Value && CanCommand(localPlayer) && Time.unscaledTime >= nextAuto && Time.unscaledTime >= nextRecall) { nextAuto = Time.unscaledTime + CheckInterval.Value; if (Recall.Run(localPlayer, automatic: true) > 0) { nextRecall = Time.unscaledTime + Cooldown.Value; } } } catch (Exception ex) { if (Time.unscaledTime >= nextError) { nextError = Time.unscaledTime + 10f; ((BaseUnityPlugin)this).Logger.LogError((object)("Skelly Staff operation failed: " + ex)); } } } internal static void Notify(string text) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).Message((MessageType)1, "Skelly Staff: " + text, 0, (Sprite)null, false); } } internal static void Log(string text) { if ((Object)(object)Instance != (Object)null && Instance.Diagnostics.Value) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)text); } } internal static void Trace(string text) { if ((Object)(object)Instance != (Object)null && Instance.TraceSummons.Value) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[Summon trace] " + text)); } } internal static void Warn(string text) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)text); } } private void OnDestroy() { Ready = false; if (harmony != null) { harmony.UnpatchSelf(); } Instance = null; } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class ControlsPatch { private static void Prefix(Player __instance, ref bool __3, ref bool __4) { if (Plugin.Ready && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { try { Plugin.Instance.PollInput(__instance); } catch (Exception ex) { Plugin.Warn("Command failed: " + ex); } if (Plugin.Instance.ConsumeSecondary(__instance)) { __3 = false; __4 = false; } } } } internal static class Recall { private static readonly Collider[] overlaps = (Collider[])(object)new Collider[64]; private static readonly List<Vector3> reserved = new List<Vector3>(); private static readonly int groundMask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "piece", "terrain", "vehicle" }); private static readonly int obstacleMask = groundMask | LayerMask.GetMask(new string[4] { "character", "character_net", "character_ghost", "character_noenv" }); internal static int Run(Player player, bool automatic) { //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_016e: 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_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || ((Character)player).IsTeleporting()) { return 0; } if (((Character)player).IsAttached() || ((Character)player).IsSwimming() || !((Character)player).IsOnGround()) { if (!automatic) { Plugin.Notify("Stand on solid ground to recall skeletons."); } return 0; } Plugin instance = Plugin.Instance; float minimum = (automatic ? Math.Max(instance.AutoDistance.Value, instance.PlacementRadius.Value + 2f) : 0f); Vector3 position = ((Component)player).transform.position; int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; reserved.Clear(); List<Character> allCharacters = Character.GetAllCharacters(); for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if ((Object)(object)val == (Object)null || val.IsDead() || !Native.IsSkeleton(((Component)val).gameObject) || !val.IsTamed()) { continue; } ZNetView component = ((Component)val).GetComponent<ZNetView>(); MonsterAI component2 = ((Component)val).GetComponent<MonsterAI>(); if ((Object)(object)component == (Object)null || !component.IsValid() || (Object)(object)component2 == (Object)null) { continue; } long recordedOwner = component.GetZDO().GetLong(Native.OwnerKey, 0L); if (!Rules.BelongsTo(recordedOwner, player.GetPlayerID(), instance.ExistingSummons.Value, (Object)(object)component2.GetFollowTarget() == (Object)(object)((Component)player).gameObject)) { continue; } Vector3 val2 = ((Component)val).transform.position - position; if (!Rules.InRange(((Vector3)(ref val2)).sqrMagnitude, instance.RecallRange.Value, minimum)) { num5++; continue; } num++; if (!component.IsOwner() && !instance.TransferOwnership.Value) { num4++; continue; } if (!FindPoint(player, val, num - 1, out var destination)) { num3++; continue; } try { if (!component.IsOwner()) { component.ClaimOwnership(); Plugin.Log("Requested native ownership for " + component.GetZDO().m_uid); } if (!component.IsOwner()) { num4++; continue; } if (val.IsDead() || !Rules.BelongsTo(component.GetZDO().GetLong(Native.OwnerKey, 0L), player.GetPlayerID(), instance.ExistingSummons.Value, (Object)(object)component2.GetFollowTarget() == (Object)(object)((Component)player).gameObject)) { num6++; continue; } Native.Move(val, component2, component, player, destination); reserved.Add(destination); num2++; } catch (Exception ex) { num6++; Plugin.Warn("Recall failed for " + ((Object)val).name + ": " + ex); } } string text = "Recalled " + num2 + "/" + num + " loaded skeletons"; if (num3 > 0) { text = text + "; no clear ground: " + num3; } if (num4 > 0) { text = text + "; remote ownership: " + num4; } if (num6 > 0) { text = text + "; failed: " + num6; } if (!automatic && num5 > 0) { text = text + "; outside range: " + num5; } if (!automatic) { Plugin.Notify((num == 0 && num5 == 0) ? "No eligible loaded skeletons found. Dismissed or unloaded summons cannot be recalled." : (text + ".")); } if (!automatic || num > 0) { Plugin.Log(text + ". Player=" + player.GetPlayerID()); } return num2; } private static bool FindPoint(Player player, Character skeleton, int index, 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_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_007e: 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_009d: 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_00a4: 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_00b0: 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_00ba: 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_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0128: 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_0152: 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_0161: 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_016b: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0191: 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) //IL_0198: 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) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: 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_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020d: 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_0270: Unknown result type (might be due to invalid IL or missing references) destination = Vector3.zero; float num = Math.Max(0.3f, skeleton.GetRadius()); float num2 = Math.Max(num * 2f, skeleton.GetHeight()); float value = Plugin.Instance.PlacementRadius.Value; Vector3 position = ((Component)player).transform.position; RaycastHit val2 = default(RaycastHit); for (int i = 0; i < 40; i++) { float num3 = ((float)index * 137.5f + (float)i * 37f) * ((float)Math.PI / 180f); float num4 = value + (float)(i % 3 - 1) * 0.5f; Vector3 val = position + new Vector3(Mathf.Cos(num3) * num4, 0f, Mathf.Sin(num3) * num4); if (!Physics.Raycast(val + Vector3.up * 2.5f, Vector3.down, ref val2, 5f, groundMask, (QueryTriggerInteraction)1) || ((RaycastHit)(ref val2)).normal.y < 0.7f || Math.Abs(((RaycastHit)(ref val2)).point.y - position.y) > 2f || (Object)(object)((Component)((RaycastHit)(ref val2)).collider).GetComponentInParent<Ship>() != (Object)null || Floating.GetLiquidLevel(((RaycastHit)(ref val2)).point, 1f, (LiquidType)10) > ((RaycastHit)(ref val2)).point.y + 0.05f) { continue; } val = ((RaycastHit)(ref val2)).point + Vector3.up * 0.15f; if (ZoneSystem.IsLavaPreHeightmap(val, 0f)) { continue; } bool flag = false; foreach (Vector3 item in reserved) { Vector3 val3 = val - item; if (((Vector3)(ref val3)).sqrMagnitude < (num * 2f + 0.3f) * (num * 2f + 0.3f)) { flag = true; break; } } if (flag) { continue; } int num5 = Physics.OverlapCapsuleNonAlloc(val + Vector3.up * num, val + Vector3.up * (num2 - num), num, overlaps, obstacleMask, (QueryTriggerInteraction)1); if (num5 == overlaps.Length) { continue; } for (int j = 0; j < num5; j++) { Collider val4 = overlaps[j]; if ((Object)(object)val4 != (Object)null && (Object)(object)((Component)val4).GetComponentInParent<Character>() != (Object)(object)skeleton) { flag = true; break; } } if (!flag) { destination = val; return true; } } return false; } } public enum SkeletonType { Melee, Archer, Random } [Flags] internal enum Weapons { None = 0, Melee = 1, Bow = 2 } internal static class Rules { internal static SkeletonType Next(SkeletonType value) { return (SkeletonType)((int)(value + 1) % 3); } internal static bool Matches(SkeletonType type, Weapons weapons) { switch (type) { default: if ((weapons & Weapons.Melee) != Weapons.None) { return (weapons & Weapons.Bow) == 0; } return false; case SkeletonType.Archer: return (weapons & Weapons.Bow) != 0; case SkeletonType.Random: return true; } } internal static bool FindSeed(int initial, SkeletonType type, Func<int, Weapons> roll, int attempts, out int seed) { seed = initial; if (type == SkeletonType.Random) { return true; } for (int i = 0; i < attempts; i++) { int num = initial + i; if (num != 0 && Matches(type, roll(num))) { seed = num; return true; } } return false; } internal static bool BelongsTo(long recordedOwner, long player, bool allowExisting, bool followingPlayer) { if (player != 0) { if (recordedOwner == 0) { if (allowExisting) { return followingPlayer; } return false; } return recordedOwner == player; } return false; } internal static bool InRange(float distanceSquared, float maximum, float minimum) { if (!float.IsNaN(distanceSquared) && !float.IsInfinity(distanceSquared) && distanceSquared >= minimum * minimum) { return distanceSquared <= maximum * maximum; } return false; } } internal static class Summoning { private sealed class Cast { internal Player Player; internal SkeletonType Type; } private sealed class Table { private readonly Weapons defaults; private readonly Weapons[] shields; private readonly Weapons[] weapons; private readonly Weapons[] armor; private readonly Weapons[] sets; internal Table(Humanoid humanoid) { defaults = Union(Map(humanoid.m_defaultItems)); shields = Map(humanoid.m_randomShield); weapons = Map(humanoid.m_randomWeapon); armor = Map(humanoid.m_randomArmor); ItemSet[] randomSets = humanoid.m_randomSets; sets = ((randomSets == null) ? new Weapons[0] : new Weapons[randomSets.Length]); for (int i = 0; i < sets.Length; i++) { sets[i] = ((randomSets[i] != null) ? Union(Map(randomSets[i].m_items)) : Weapons.None); } } private static Weapons[] Map(GameObject[] prefabs) { if (prefabs == null) { return new Weapons[0]; } Weapons[] array = new Weapons[prefabs.Length]; for (int i = 0; i < array.Length; i++) { array[i] = Kind(prefabs[i]); } return array; } private static Weapons Union(Weapons[] source) { Weapons weapons = Weapons.None; foreach (Weapons weapons2 in source) { weapons |= weapons2; } return weapons; } private static Weapons Pick(Weapons[] source) { if (source.Length != 0) { return source[Random.Range(0, source.Length)]; } return Weapons.None; } internal Weapons Roll(int seed) { Random.InitState(seed); Weapons weapons = defaults; weapons |= Pick(shields); weapons |= Pick(this.weapons); weapons |= Pick(armor); return weapons | Pick(sets); } } private static readonly ConditionalWeakTable<SpawnAbility, Cast> casts = new ConditionalWeakTable<SpawnAbility, Cast>(); internal static void Capture(SpawnAbility ability, Character owner, ItemData weapon) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown casts.Remove(ability); if (Plugin.Ready && (Object)(object)owner != (Object)null && (Object)(object)owner == (Object)(object)Player.m_localPlayer && Plugin.IsStaff(weapon)) { casts.Add(ability, new Cast { Player = (Player)owner, Type = Plugin.Instance.Type.Value }); Plugin.Trace("Captured ability=" + ((Object)ability).GetInstanceID() + "; type=" + Plugin.Instance.Type.Value); } else if (Plugin.Ready && (Object)(object)owner == (Object)(object)Player.m_localPlayer && (Object)(object)owner != (Object)null) { Plugin.Trace("Uncaptured local ability=" + ((Object)ability).GetInstanceID() + "; item=" + ((weapon == null || (Object)(object)weapon.m_dropPrefab == (Object)null) ? "missing" : ((Object)weapon.m_dropPrefab).name)); } } internal static GameObject Instantiate(GameObject prefab, Vector3 position, Quaternion rotation, SpawnAbility ability) { //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_01c9: 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_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate<GameObject>(prefab, position, rotation); try { if (!Plugin.Ready || !Native.IsSkeleton(prefab)) { return val; } if (!casts.TryGetValue(ability, out var value) || (Object)(object)value.Player == (Object)null) { Plugin.Trace("Skeleton spawn without captured local cast; ability=" + ((Object)ability).GetInstanceID()); return val; } Humanoid component = val.GetComponent<Humanoid>(); ZNetView component2 = val.GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || !component2.IsValid() || !component2.IsOwner()) { Plugin.Trace("Selection skipped: missing humanoid/view or nonlocal owner; ability=" + ((Object)ability).GetInstanceID()); return val; } Plugin.Trace(string.Concat("Spawn=", component2.GetZDO().m_uid, "; ability=", ((Object)ability).GetInstanceID(), "; requested=", value.Type)); component2.GetZDO().Set(Native.OwnerKey, value.Player.GetPlayerID()); component2.GetZDO().Set(Native.TypeKey, (int)value.Type, false); if (value.Type == SkeletonType.Random) { return val; } if (component.m_randomItems != null && component.m_randomItems.Length != 0) { Plugin.Warn("Skeleton has a nonstandard random-item table; leaving native selection unchanged."); Plugin.Notify("Modified skeleton equipment table: using its normal random selection."); return val; } Table table = new Table(component); int num = (int)Native.Seed.GetValue(component); State state = Random.state; bool flag; int seed; try { flag = Rules.FindSeed(num, value.Type, table.Roll, 512, out seed); } finally { Random.state = state; } if (!flag) { Plugin.Warn(string.Concat("Could not find ", value.Type, " in the skeleton's native equipment table; original summon retained.")); Plugin.Notify("Requested equipment unavailable; summoned normally."); return val; } Native.Seed.SetValue(component, seed); component2.GetZDO().Set(ZDOVars.s_seed, seed, false); Plugin.Trace("Selected seed=" + seed + "; original=" + num + "; spawn=" + component2.GetZDO().m_uid); Plugin.Log(string.Concat("Selected ", value.Type, " for ", component2.GetZDO().m_uid, "; native seed ", num, " -> ", seed, ".")); } catch (Exception ex) { Plugin.Warn("Selection failed; the already-created summon is retained: " + ex); Plugin.Notify("Selection failed; check LogOutput.log. Your summon was retained."); } return val; } internal static Weapons Kind(ItemData item) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 if (item == null || !item.IsWeapon()) { return Weapons.None; } if ((int)item.m_shared.m_itemType != 4) { return Weapons.Melee; } return Weapons.Bow; } private static Weapons Kind(GameObject prefab) { ItemDrop val = (((Object)(object)prefab == (Object)null) ? null : prefab.GetComponent<ItemDrop>()); if (!((Object)(object)val == (Object)null)) { return Kind(val.m_itemData); } return Weapons.None; } internal static void Verify(Humanoid humanoid) { //IL_0066: 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) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Ready || !Native.IsSkeleton(((Component)humanoid).gameObject)) { return; } ZNetView component = ((Component)humanoid).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return; } if (!component.IsOwner() || component.GetZDO().GetLong(Native.OwnerKey, 0L) == 0) { Plugin.Trace(string.Concat("Start verification skipped: spawn=", component.GetZDO().m_uid, "; local-owner=", component.IsOwner(), "; marked=", component.GetZDO().GetLong(Native.OwnerKey, 0L) != 0)); return; } int num = component.GetZDO().GetInt(Native.TypeKey, 2); Weapons weapons = Weapons.None; foreach (ItemData allItem in humanoid.GetInventory().GetAllItems()) { weapons |= Kind(allItem); } Plugin.Trace(string.Concat("Equipment spawn=", component.GetZDO().m_uid, "; requested=", (SkeletonType)num, "; actual=", weapons, "; live-seed=", Native.Seed.GetValue(humanoid), "; saved-seed=", component.GetZDO().GetInt(ZDOVars.s_seed, 0), "; items=", string.Join(",", (from i in humanoid.GetInventory().GetAllItems() select (!((Object)(object)i.m_dropPrefab == (Object)null)) ? ((Object)i.m_dropPrefab).name : i.m_shared.m_name).ToArray()))); if (!Rules.Matches((SkeletonType)num, weapons)) { Plugin.Warn(string.Concat("Equipment verification mismatch for ", component.GetZDO().m_uid, ": requested=", (SkeletonType)num, ", actual=", weapons, ". Another equipment mod or an unsupported prefab may be involved.")); Plugin.Notify("Skeleton type check failed; see LogOutput.log."); } else { Plugin.Log(string.Concat("Verified skeleton ", component.GetZDO().m_uid, ": requested=", (SkeletonType)num, ", actual=", weapons, ".")); } } } [HarmonyPatch(typeof(SpawnAbility), "Setup")] internal static class CapturePatch { private static void Prefix(SpawnAbility __instance, Character __0, ItemData __4) { Summoning.Capture(__instance, __0, __4); } } [HarmonyPatch] internal static class SpawnPatch { internal static MethodBase TargetMethod() { Type type = typeof(SpawnAbility).GetNestedTypes(BindingFlags.NonPublic).Single((Type t) => t.Name.StartsWith("<Spawn>", StringComparison.Ordinal)); return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); FieldInfo fieldInfo = __originalMethod.DeclaringType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Single((FieldInfo f) => f.FieldType == typeof(SpawnAbility)); int num = 0; for (int num2 = 0; num2 < list.Count; num2++) { MethodInfo methodInfo = list[num2].operand as MethodInfo; if (!(methodInfo == null) && !(methodInfo.DeclaringType != typeof(Object)) && !(methodInfo.Name != "Instantiate") && methodInfo.IsGenericMethod && !(methodInfo.GetGenericArguments()[0] != typeof(GameObject)) && methodInfo.GetParameters().Length == 3) { CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[num2].labels); list[num2].labels.Clear(); if (list[num2].blocks.Count != 0) { throw new InvalidOperationException("Unexpected protected block at skeleton instantiation."); } list.InsertRange(num2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[2] { val, new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo) }); num2 += 2; list[num2].opcode = OpCodes.Call; list[num2].operand = AccessTools.Method(typeof(Summoning), "Instantiate", (Type[])null, (Type[])null); num++; } } if (num != 1) { throw new InvalidOperationException("Expected one SpawnAbility creature Instantiate call; found " + num); } return list; } } [HarmonyPatch(typeof(Humanoid), "Start")] internal static class VerifySpawnPatch { private static void Postfix(Humanoid __instance) { try { Summoning.Verify(__instance); } catch (Exception ex) { Plugin.Warn("Equipment diagnostic failed: " + ex.Message); } } }