Decompiled source of ScalpelsSkellyStaff v1.0.0

BepInEx/plugins/ScalpelsSkellyStaff/ScalpelsSkellyStaff.dll

Decompiled a day ago
using 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);
		}
	}
}