Decompiled source of Summon Mastery v0.1.2

SummonMastery.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SummonMastery")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+5bd97a76275cc046764632bf4414fffbe0aa9904")]
[assembly: AssemblyProduct("SummonMastery")]
[assembly: AssemblyTitle("SummonMastery")]
[assembly: AssemblyVersion("0.1.2.0")]
[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 SummonMastery
{
	internal static class Dismissal
	{
		private const string Request = "local.summonmastery.DismissAll.v1";

		private const string Result = "local.summonmastery.DismissResult.v1";

		private static readonly MethodInfo TakeInput = AccessTools.Method(typeof(Player), "TakeInput", (Type[])null, (Type[])null);

		private static ZRoutedRpc registered;

		private static float nextRequest;

		private static readonly Dictionary<long, float> LastRequest = new Dictionary<long, float>();

		internal static void Update()
		{
			//IL_0097: 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)
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null)
			{
				return;
			}
			if (registered != instance)
			{
				instance.Register("local.summonmastery.DismissAll.v1", (Action<long>)DismissOnServer);
				instance.Register<int>("local.summonmastery.DismissResult.v1", (Action<long, int>)ShowResult);
				registered = instance;
				LastRequest.Clear();
				nextRequest = 0f;
			}
			Player localPlayer = Player.m_localPlayer;
			if (Object.op_Implicit((Object)(object)localPlayer) && Object.op_Implicit((Object)(object)Hud.instance))
			{
				KeyboardShortcut value = Plugin.DismissKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown() && !(Time.unscaledTime < nextRequest) && (bool)TakeInput.Invoke(localPlayer, null))
				{
					nextRequest = Time.unscaledTime + 1f;
					instance.InvokeRoutedRPC("local.summonmastery.DismissAll.v1", Array.Empty<object>());
				}
			}
		}

		private static void DismissOnServer(long sender)
		{
			//IL_006d: 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_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)Plugin.Instance) || !Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer() || ZDOMan.instance == null)
			{
				return;
			}
			long num;
			if (sender == ZDOMan.GetSessionID())
			{
				Player localPlayer = Player.m_localPlayer;
				if (!Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead())
				{
					return;
				}
				num = localPlayer.GetPlayerID();
			}
			else
			{
				ZNetPeer peer = ZNet.instance.GetPeer(sender);
				if (peer == null)
				{
					return;
				}
				ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
				if (zDO == null || zDO.GetOwner() != sender)
				{
					return;
				}
				num = zDO.GetLong(ZDOVars.s_playerID, 0L);
			}
			if (num == 0L || (LastRequest.TryGetValue(sender, out var value) && Time.unscaledTime - value < 1f))
			{
				return;
			}
			LastRequest[sender] = Time.unscaledTime;
			int num2 = 0;
			foreach (ZDOID item in ZDOExtraData.GetAllZDOIDsWithHash((Type)4, StringExtensionMethods.GetStableHashCode("summonmastery.v1.summoner")))
			{
				ZDO zDO2 = ZDOMan.instance.GetZDO(item);
				if (zDO2 != null && zDO2.IsValid() && Scaling.CanDismiss(Plugin.Tagged(zDO2), zDO2.GetLong("summonmastery.v1.summoner", 0L), num, zDO2.GetFloat(ZDOVars.s_health, 1f) <= 0f))
				{
					zDO2.SetOwner(ZDOMan.GetSessionID());
					ZDOMan.instance.DestroyZDO(zDO2);
					num2++;
				}
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "local.summonmastery.DismissResult.v1", new object[1] { num2 });
		}

		private static void ShowResult(long sender, int count)
		{
			if (Object.op_Implicit((Object)(object)Plugin.Instance) && Object.op_Implicit((Object)(object)ZNet.instance) && Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				long num = (ZNet.instance.IsServer() ? ZDOMan.GetSessionID() : (ZNet.instance.GetServerPeer()?.m_uid ?? 0));
				if (sender == num)
				{
					((Character)Player.m_localPlayer).Message((MessageType)1, (count == 0) ? "No summons to dismiss." : string.Format("Dismissed {0} summon{1}.", count, (count == 1) ? "" : "s"), 0, (Sprite)null, false);
				}
			}
		}
	}
	[BepInPlugin("local.summonmastery", "Summon Mastery", "0.1.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Id = "local.summonmastery";

		public const string Version = "0.1.2";

		internal const string Key = "summonmastery.v1.";

		internal static Plugin Instance;

		internal static ConfigEntry<float> MaxHealth;

		internal static ConfigEntry<float> MaxRegen;

		internal static ConfigEntry<float> MaxArmor;

		internal static ConfigEntry<float> MaxSpeed;

		internal static ConfigEntry<float> MaxDamage;

		internal static ConfigEntry<float> RegenDelay;

		internal static ConfigEntry<float> PortalRange;

		internal static ConfigEntry<bool> Portals;

		internal static ConfigEntry<bool> SpiritCallerWolvesOnly;

		internal static ConfigEntry<bool> ExcludeTrollstavScaling;

		internal static ConfigEntry<KeyboardShortcut> DismissKey;

		private Harmony harmony;

		private float nextScan;

		private void Awake()
		{
			//IL_005d: 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_01c0: Expected O, but got Unknown
			Instance = this;
			ExcludeTrollstavScaling = ((BaseUnityPlugin)this).Config.Bind<bool>("Scaling", "Exclude Trollstav", false, "Keep normal stats for newly summoned Trollstav creatures. Dismissal tracking remains enabled. Existing summons keep their recorded stats.");
			SpiritCallerWolvesOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("Summoning", "Spirit Caller wolves only", false, "Spirit Caller summons only ghost wolves when enabled. Affects new casts; existing summons and other weapons are unchanged.");
			DismissKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "Dismiss all summons", new KeyboardShortcut((KeyCode)111, Array.Empty<KeyCode>()), "Dismiss all your tracked summons in this world, including waiting and distant summons. Supports modifier keys. Set to None to disable.");
			MaxHealth = Number("Scaling", "Maximum rank health multiplier", 3f, 1f, 20f, "Multiplies the summon health after normal skill/star scaling. Rank 1 is unchanged.");
			MaxDamage = Number("Scaling", "Maximum rank damage multiplier", 2.5f, 1f, 10f, "Multiplies normal melee and projectile attack damage, retaining Blood Magic bonuses.");
			MaxArmor = Number("Scaling", "Maximum rank added armor", 35f, 0f, 200f, "Extra armor using Valheim's normal damage armor formula. Does not grant damage immunity.");
			MaxSpeed = Number("Scaling", "Maximum rank movement multiplier", 1.3f, 1f, 3f, "Walk, jog, run, swim and flight movement. Attack animation speed is unchanged.");
			MaxRegen = Number("Scaling", "Maximum rank regeneration fraction", 0.005f, 0f, 0.05f, "Fraction of maximum health regenerated per second after the damage delay. 0.005 is 0.5 percent.");
			RegenDelay = Number("Scaling", "Regeneration damage delay seconds", 10f, 1f, 120f, "Any health loss restarts this delay. Existing game regeneration is preserved.");
			Portals = ((BaseUnityPlugin)this).Config.Bind<bool>("Travel", "Follow through portals", true, "Bring nearby, living, tamed summons following their original summoner on successful portal-style teleports.");
			PortalRange = Number("Travel", "Gather radius meters", 30f, 1f, 100f, "Only following summons within this distance at departure travel. Waiting pets stay behind.");
			harmony = new Harmony("local.summonmastery");
			try
			{
				harmony.PatchAll(typeof(Plugin).Assembly);
			}
			catch
			{
				harmony.UnpatchSelf();
				throw;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Summon Mastery 0.1.2 loaded. Scaling is recorded when the weapon summons a creature.");
		}

		private ConfigEntry<float> Number(string section, string name, float value, float min, float max, string text)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			return ((BaseUnityPlugin)this).Config.Bind<float>(section, name, value, new ConfigDescription(text, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>()));
		}

		internal static ZDO Data(Character character)
		{
			if (!Object.op_Implicit((Object)(object)character) || character.IsPlayer())
			{
				return null;
			}
			ZNetView component = ((Component)character).GetComponent<ZNetView>();
			if (!Object.op_Implicit((Object)(object)component) || !component.IsValid())
			{
				return null;
			}
			return component.GetZDO();
		}

		internal static bool Tagged(ZDO data)
		{
			if (data != null)
			{
				return data.GetInt("summonmastery.v1.rank", 0) > 0;
			}
			return false;
		}

		internal static void Warn(string message)
		{
			((BaseUnityPlugin)Instance).Logger.LogWarning((object)message);
		}

		private void Update()
		{
			Dismissal.Update();
			if (Time.unscaledTime < nextScan)
			{
				return;
			}
			nextScan = Time.unscaledTime + 1f;
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if (Object.op_Implicit((Object)(object)allCharacter) && Tagged(Data(allCharacter)) && !Object.op_Implicit((Object)(object)((Component)allCharacter).GetComponent<SummonState>()))
				{
					((Component)allCharacter).gameObject.AddComponent<SummonState>();
				}
			}
			PortalTravel.CheckTimeout();
		}

		private void OnDestroy()
		{
			PortalTravel.Cancel();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			SummonState summonState = default(SummonState);
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if (Object.op_Implicit((Object)(object)allCharacter) && ((Component)allCharacter).TryGetComponent<SummonState>(ref summonState))
				{
					Object.Destroy((Object)(object)summonState);
				}
			}
			Instance = null;
		}
	}
	public sealed class SummonState : MonoBehaviour
	{
		private Character character;

		private ZNetView view;

		private float speedFactor = 1f;

		private float lastHealth;

		private float sinceDamage;

		private float timer;

		internal void NotifyDamage()
		{
			sinceDamage = 0f;
		}

		private void Awake()
		{
			character = ((Component)this).GetComponent<Character>();
			view = ((Component)this).GetComponent<ZNetView>();
			lastHealth = character.GetHealth();
			sinceDamage = 0f;
			ApplySpeed();
		}

		private void ApplySpeed()
		{
			if (Object.op_Implicit((Object)(object)view) && view.IsValid())
			{
				float num = view.GetZDO().GetFloat("summonmastery.v1.speed", 1f);
				float num2 = num / speedFactor;
				Character obj = character;
				obj.m_speed *= num2;
				Character obj2 = character;
				obj2.m_walkSpeed *= num2;
				Character obj3 = character;
				obj3.m_runSpeed *= num2;
				Character obj4 = character;
				obj4.m_swimSpeed *= num2;
				Character obj5 = character;
				obj5.m_flySlowSpeed *= num2;
				Character obj6 = character;
				obj6.m_flyFastSpeed *= num2;
				speedFactor = num;
			}
		}

		private void FixedUpdate()
		{
			if (!Object.op_Implicit((Object)(object)view) || !view.IsValid() || !Object.op_Implicit((Object)(object)character))
			{
				return;
			}
			float health = character.GetHealth();
			if (!view.IsOwner())
			{
				sinceDamage = 0f;
				lastHealth = health;
				return;
			}
			if (health < lastHealth)
			{
				sinceDamage = 0f;
			}
			else
			{
				sinceDamage += Time.fixedDeltaTime;
			}
			lastHealth = health;
			timer += Time.fixedDeltaTime;
			if (!(timer < 1f))
			{
				float val = timer;
				timer = 0f;
				ApplySpeed();
				ZDO zDO = view.GetZDO();
				float num = Scaling.Regeneration(health, character.GetMaxHealth(), zDO.GetFloat("summonmastery.v1.regen", 0f), sinceDamage, zDO.GetFloat("summonmastery.v1.delay", 10f), Math.Min(val, 1.1f));
				if (!character.IsDead() && num > 0f)
				{
					character.Heal(num, false);
				}
				lastHealth = character.GetHealth();
			}
		}

		private void OnDestroy()
		{
			if (Object.op_Implicit((Object)(object)character) && speedFactor != 0f)
			{
				Character obj = character;
				obj.m_speed /= speedFactor;
				Character obj2 = character;
				obj2.m_walkSpeed /= speedFactor;
				Character obj3 = character;
				obj3.m_runSpeed /= speedFactor;
				Character obj4 = character;
				obj4.m_swimSpeed /= speedFactor;
				Character obj5 = character;
				obj5.m_flySlowSpeed /= speedFactor;
				Character obj6 = character;
				obj6.m_flyFastSpeed /= speedFactor;
			}
		}
	}
	internal static class PortalTravel
	{
		private sealed class Journey
		{
			internal Player Player;

			internal Vector3 Origin;

			internal float Deadline;

			internal readonly List<ZDOID> Summons = new List<ZDOID>();
		}

		private static Journey journey;

		private static readonly FieldRef<Character, float> MaxAirAltitude = AccessTools.FieldRefAccess<Character, float>("m_maxAirAltitude");

		internal static long Now
		{
			get
			{
				if (!Object.op_Implicit((Object)(object)ZNet.instance))
				{
					return 0L;
				}
				return ZNet.instance.GetTime().Ticks;
			}
		}

		internal static void Begin(Player player)
		{
			//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_00bb: 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_0120: 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)
			Cancel();
			Journey journey = new Journey
			{
				Player = player,
				Origin = ((Component)player).transform.position,
				Deadline = Time.unscaledTime + 45f
			};
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if (Object.op_Implicit((Object)(object)allCharacter) && !allCharacter.IsPlayer())
				{
					ZDO val = Plugin.Data(allCharacter);
					MonsterAI component = ((Component)allCharacter).GetComponent<MonsterAI>();
					if (Scaling.CanTravel(Plugin.Tagged(val), allCharacter.IsTamed(), Object.op_Implicit((Object)(object)component) && (Object)(object)component.GetFollowTarget() == (Object)(object)((Component)player).gameObject, (val != null) ? val.GetLong("summonmastery.v1.summoner", 0L) : 0, player.GetPlayerID(), Vector3.Distance(((Component)allCharacter).transform.position, ((Component)player).transform.position), Plugin.PortalRange.Value, allCharacter.IsDead()))
					{
						((Component)allCharacter).GetComponent<ZNetView>().ClaimOwnership();
						val.Set("summonmastery.v1.travelUntil", Now + TimeSpan.FromSeconds(45.0).Ticks);
						ZDOMan.instance.ForceSendZDO(val.m_uid);
						journey.Summons.Add(val.m_uid);
					}
				}
			}
			if (journey.Summons.Count > 0)
			{
				PortalTravel.journey = journey;
			}
		}

		internal static void Finish(Player player)
		{
			//IL_0034: 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_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_0074: 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_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: 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_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d2: 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_01ef: 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_0252: 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)
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			if (PortalTravel.journey == null || (Object)(object)PortalTravel.journey.Player != (Object)(object)player)
			{
				return;
			}
			Journey journey = PortalTravel.journey;
			PortalTravel.journey = null;
			bool flag = !((Character)player).IsDead() && Vector3.Distance(((Component)player).transform.position, journey.Origin) > 2f;
			foreach (ZDOID summon in journey.Summons)
			{
				ZDOMan instance = ZDOMan.instance;
				ZDO val = ((instance != null) ? instance.GetZDO(summon) : null);
				if (val == null || !val.IsValid() || !Plugin.Tagged(val) || val.GetLong("summonmastery.v1.summoner", 0L) != player.GetPlayerID() || val.GetFloat(ZDOVars.s_health, 1f) <= 0f)
				{
					continue;
				}
				GameObject val2 = ZNetScene.instance.FindInstance(summon);
				Character val3 = (Object.op_Implicit((Object)(object)val2) ? val2.GetComponent<Character>() : null);
				MonsterAI val4 = (Object.op_Implicit((Object)(object)val3) ? ((Component)val3).GetComponent<MonsterAI>() : null);
				bool flag2 = ((!Object.op_Implicit((Object)(object)val3)) ? (val.GetBool(ZDOVars.s_tamed, true) && val.GetString(ZDOVars.s_follow, "") == player.GetPlayerName()) : (val3.IsTamed() && Object.op_Implicit((Object)(object)val4) && (Object)(object)val4.GetFollowTarget() == (Object)(object)((Component)player).gameObject));
				val.SetOwner(ZDOMan.GetSessionID());
				val.Set("summonmastery.v1.travelUntil", 0L);
				if (!(flag && flag2) || (Object.op_Implicit((Object)(object)val3) && val3.IsDead()))
				{
					continue;
				}
				Vector3 val5 = ((Component)player).transform.position + Vector3.up * 0.2f;
				val.SetPosition(val5);
				val.SetRotation(((Component)player).transform.rotation);
				if (Object.op_Implicit((Object)(object)val3))
				{
					((Component)val3).transform.SetPositionAndRotation(val5, ((Component)player).transform.rotation);
					Rigidbody component = ((Component)val3).GetComponent<Rigidbody>();
					if (Object.op_Implicit((Object)(object)component))
					{
						component.position = val5;
						component.rotation = ((Component)player).transform.rotation;
						component.linearVelocity = Vector3.zero;
						component.angularVelocity = Vector3.zero;
					}
					MaxAirAltitude.Invoke(val3) = val5.y;
					ZSyncTransform component2 = ((Component)val3).GetComponent<ZSyncTransform>();
					if (component2 != null)
					{
						component2.SyncNow();
					}
				}
				ZDOMan.instance.ForceSendZDO(summon);
			}
		}

		internal static void CheckTimeout()
		{
			if (journey != null && (!Object.op_Implicit((Object)(object)journey.Player) || ((Character)journey.Player).IsDead() || Time.unscaledTime > journey.Deadline))
			{
				Cancel();
			}
		}

		internal static void Cancel()
		{
			//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_002e: Unknown result type (might be due to invalid IL or missing references)
			if (journey == null)
			{
				return;
			}
			if (ZDOMan.instance != null)
			{
				foreach (ZDOID summon in journey.Summons)
				{
					ZDO zDO = ZDOMan.instance.GetZDO(summon);
					if (zDO != null && zDO.IsOwner())
					{
						zDO.Set("summonmastery.v1.travelUntil", 0L);
					}
				}
			}
			journey = null;
		}
	}
	[HarmonyPatch(typeof(Player), "TeleportTo")]
	internal static class StartTravel
	{
		private static void Postfix(Player __instance, bool distantTeleport, bool __result)
		{
			if (__result && distantTeleport && Plugin.Portals.Value && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				PortalTravel.Begin(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "UpdateTeleport")]
	internal static class CompleteTravel
	{
		private static void Prefix(Player __instance, out bool __state)
		{
			__state = ((Character)__instance).IsTeleporting();
		}

		private static void Postfix(Player __instance, bool __state)
		{
			if (__state && !((Character)__instance).IsTeleporting())
			{
				PortalTravel.Finish(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Tameable), "UpdateSummon")]
	internal static class ProtectTravel
	{
		internal static bool Prefix(Tameable __instance)
		{
			ZDO val = Plugin.Data(((Component)__instance).GetComponent<Character>());
			if (!Plugin.Tagged(val))
			{
				return true;
			}
			long num = val.GetLong("summonmastery.v1.travelUntil", 0L) - PortalTravel.Now;
			if (num > 0)
			{
				return num > TimeSpan.FromSeconds(46.0).Ticks;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Tameable), "UpdateSavedFollowTarget")]
	internal static class ProtectFollowDuringTravel
	{
		private static bool Prefix(Tameable __instance)
		{
			return ProtectTravel.Prefix(__instance);
		}
	}
	public readonly struct Scaling
	{
		public const string Trollstav = "StaffRedTroll";

		public readonly float Health;

		public readonly float Regen;

		public readonly float Armor;

		public readonly float Speed;

		public readonly float Damage;

		public static bool IsExcluded(bool excludeTrollstav, string weapon)
		{
			if (excludeTrollstav)
			{
				return weapon == "StaffRedTroll";
			}
			return false;
		}

		public static Scaling ForWeapon(bool excludeTrollstav, string weapon, int rank, int maxRank, Scaling maximum)
		{
			if (!IsExcluded(excludeTrollstav, weapon))
			{
				return ForRank(rank, maxRank, maximum);
			}
			return new Scaling(1f, 0f, 0f, 1f, 1f);
		}

		public static bool CanDismiss(bool tagged, long summoner, long player, bool dead)
		{
			if (tagged && player != 0L && summoner == player)
			{
				return !dead;
			}
			return false;
		}

		public Scaling(float health, float regen, float armor, float speed, float damage)
		{
			Health = health;
			Regen = regen;
			Armor = armor;
			Speed = speed;
			Damage = damage;
		}

		public static Scaling ForRank(int rank, int maxRank, Scaling maximum)
		{
			float num = ((maxRank <= 1) ? 0f : Math.Min(1f, Math.Max(0f, ((float)rank - 1f) / ((float)maxRank - 1f))));
			return new Scaling(1f + (maximum.Health - 1f) * num, maximum.Regen * num, maximum.Armor * num, 1f + (maximum.Speed - 1f) * num, 1f + (maximum.Damage - 1f) * num);
		}

		public static bool CanTravel(bool tagged, bool tamed, bool following, long summoner, long player, float distance, float range, bool dead)
		{
			if (tagged && tamed && following && summoner != 0L && summoner == player && distance <= range)
			{
				return !dead;
			}
			return false;
		}

		public static float Regeneration(float health, float maxHealth, float fractionPerSecond, float secondsSinceDamage, float delay, float elapsed)
		{
			if (health <= 0f || maxHealth <= 0f || secondsSinceDamage < delay || elapsed <= 0f)
			{
				return 0f;
			}
			return Math.Max(0f, Math.Min(maxHealth - health, maxHealth * fractionPerSecond * elapsed));
		}
	}
	public static class SpawnSelection
	{
		public const string SpiritCaller = "StaffSpiritCaller";

		public const string SpiritWolf = "Wolf_spiritcaller";

		public static T[] WolvesOnly<T>(bool enabled, string weapon, T[] pool, Func<T, string> name)
		{
			if (!enabled || weapon != "StaffSpiritCaller" || pool == null)
			{
				return null;
			}
			List<T> list = new List<T>();
			foreach (T val in pool)
			{
				if (name(val) == "Wolf_spiritcaller")
				{
					list.Add(val);
				}
			}
			if (list.Count != 0)
			{
				return list.ToArray();
			}
			return null;
		}

		public static bool Advance<T>(IEnumerator coroutine, T[] selected, Func<T[]> getPool, Action<T[]> setPool)
		{
			if (selected == null)
			{
				return coroutine.MoveNext();
			}
			T[] obj = getPool();
			setPool(selected);
			try
			{
				return coroutine.MoveNext();
			}
			finally
			{
				setPool(obj);
			}
		}
	}
	internal sealed class SpawnContext
	{
		[ThreadStatic]
		internal static SpawnContext Current;

		internal readonly List<Character> Created = new List<Character>();

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

		private readonly long summoner;

		private readonly int rank;

		private readonly int maximumRank;

		private readonly string weapon;

		private readonly Scaling scaling;

		private readonly float delay;

		private readonly SpawnAbility ability;

		private readonly GameObject[] selectedPrefabs;

		private readonly bool excludedFromScaling;

		internal SpawnContext(SpawnAbility ability, Player player, ItemData item)
		{
			this.ability = ability;
			summoner = player.GetPlayerID();
			rank = Math.Max(1, item.m_quality);
			maximumRank = Math.Max(1, item.m_shared.m_maxQuality);
			weapon = (Object.op_Implicit((Object)(object)item.m_dropPrefab) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name);
			selectedPrefabs = SpawnSelection.WolvesOnly(Plugin.SpiritCallerWolvesOnly.Value, weapon, ability.m_spawnPrefab, (GameObject prefab) => (!Object.op_Implicit((Object)(object)prefab)) ? null : ((Object)prefab).name);
			if (Plugin.SpiritCallerWolvesOnly.Value && weapon == "StaffSpiritCaller" && selectedPrefabs == null)
			{
				Plugin.Warn("Spirit Caller wolf prefab is absent from this cast's pool; keeping normal summons for this cast.");
			}
			excludedFromScaling = Scaling.IsExcluded(Plugin.ExcludeTrollstavScaling.Value, weapon);
			scaling = Scaling.ForWeapon(Plugin.ExcludeTrollstavScaling.Value, weapon, rank, maximumRank, new Scaling(Plugin.MaxHealth.Value, Plugin.MaxRegen.Value, Plugin.MaxArmor.Value, Plugin.MaxSpeed.Value, Plugin.MaxDamage.Value));
			delay = Plugin.RegenDelay.Value;
			GameObject[] array = selectedPrefabs ?? ability.m_spawnPrefab;
			foreach (GameObject val in array)
			{
				if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.GetComponent<Character>()))
				{
					prefabs.Add(((Object)val).name);
				}
			}
		}

		internal void Capture(Character character)
		{
			if (Object.op_Implicit((Object)(object)character) && prefabs.Contains(Utils.GetPrefabName(((Component)character).gameObject)))
			{
				Created.Add(character);
			}
		}

		internal void Apply()
		{
			foreach (Character item in Created)
			{
				ZDO val = Plugin.Data(item);
				if (val != null && val.IsOwner() && !Plugin.Tagged(val) && !item.IsDead())
				{
					float healthPercentage = item.GetHealthPercentage();
					float num = item.GetMaxHealth() * scaling.Health;
					val.Set("summonmastery.v1.rank", rank);
					val.Set("summonmastery.v1.maxRank", maximumRank);
					val.Set("summonmastery.v1.summoner", summoner);
					val.Set("summonmastery.v1.weapon", weapon);
					val.Set("summonmastery.v1.health", scaling.Health);
					val.Set("summonmastery.v1.regen", scaling.Regen);
					val.Set("summonmastery.v1.armor", scaling.Armor);
					val.Set("summonmastery.v1.speed", scaling.Speed);
					val.Set("summonmastery.v1.damage", scaling.Damage);
					val.Set("summonmastery.v1.delay", delay);
					if (!excludedFromScaling)
					{
						item.SetMaxHealth(num);
						item.SetHealth(num * healthPercentage);
					}
					if (!Object.op_Implicit((Object)(object)((Component)item).GetComponent<SummonState>()))
					{
						((Component)item).gameObject.AddComponent<SummonState>();
					}
				}
			}
			Created.Clear();
		}

		internal IEnumerator Wrap(IEnumerator original)
		{
			try
			{
				while (true)
				{
					SpawnContext current = Current;
					Current = this;
					bool flag;
					try
					{
						flag = SpawnSelection.Advance(original, selectedPrefabs, () => ability.m_spawnPrefab, delegate(GameObject[] pool)
						{
							ability.m_spawnPrefab = pool;
						});
					}
					finally
					{
						Current = current;
						Apply();
					}
					if (flag)
					{
						yield return original.Current;
						continue;
					}
					break;
				}
			}
			finally
			{
				(original as IDisposable)?.Dispose();
			}
		}
	}
	[HarmonyPatch(typeof(SpawnAbility), "Spawn")]
	internal static class CaptureCast
	{
		private static void Postfix(SpawnAbility __instance, Character ___m_owner, ItemData ___m_weapon, ref IEnumerator __result)
		{
			Player val = (Player)(object)((___m_owner is Player) ? ___m_owner : null);
			if (val != null && ___m_weapon != null && __result != null)
			{
				__result = new SpawnContext(__instance, val, ___m_weapon).Wrap(__result);
			}
		}
	}
	[HarmonyPatch(typeof(Character), "Awake")]
	internal static class CaptureCreature
	{
		private static void Postfix(Character __instance)
		{
			SpawnContext.Current?.Capture(__instance);
		}
	}
	[HarmonyPatch(typeof(Character), "GetMaxHealthBase")]
	internal static class RestoreHealth
	{
		private static void Postfix(Character __instance, ref float __result)
		{
			ZDO val = Plugin.Data(__instance);
			if (Plugin.Tagged(val))
			{
				__result *= val.GetFloat("summonmastery.v1.health", 1f);
			}
		}
	}
	[HarmonyPatch(typeof(Attack), "ModifyDamage")]
	internal static class ScaleAttack
	{
		private static void Postfix(Character ___m_character, HitData hitData)
		{
			ZDO val = Plugin.Data(___m_character);
			if (Plugin.Tagged(val))
			{
				((DamageTypes)(ref hitData.m_damage)).Modify(val.GetFloat("summonmastery.v1.damage", 1f));
			}
		}
	}
	[HarmonyPatch(typeof(Aoe), "GetDamage", new Type[] { typeof(int) })]
	internal static class ScaleAreaDamage
	{
		private static void Postfix(Aoe __instance, Character ___m_owner, HitData ___m_hitData, ref DamageTypes __result)
		{
			if (!__instance.m_useAttackSettings || ___m_hitData == null)
			{
				ZDO val = Plugin.Data(___m_owner);
				if (Plugin.Tagged(val))
				{
					((DamageTypes)(ref __result)).Modify(val.GetFloat("summonmastery.v1.damage", 1f));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "GetBodyArmor")]
	internal static class DisplayArmor
	{
		private static void Postfix(Character __instance, ref float __result)
		{
			ZDO val = Plugin.Data(__instance);
			if (Plugin.Tagged(val))
			{
				__result += val.GetFloat("summonmastery.v1.armor", 0f);
			}
		}
	}
	[HarmonyPatch(typeof(Character), "ApplyDamage")]
	internal static class DelayHealingAfterDamage
	{
		private static void Prefix(Character __instance, out float __state)
		{
			__state = __instance.GetHealth();
		}

		private static void Postfix(Character __instance, float __state)
		{
			if (__instance.GetHealth() < __state)
			{
				((Component)__instance).GetComponent<SummonState>()?.NotifyDamage();
			}
		}
	}
	[HarmonyPatch(typeof(Character), "RPC_Damage")]
	internal static class CreatureArmor
	{
		internal static void Apply(Character character, HitData hit)
		{
			ZDO val = Plugin.Data(character);
			if (Plugin.Tagged(val))
			{
				hit.ApplyArmor(val.GetFloat("summonmastery.v1.armor", 0f));
			}
		}

		internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			int matches = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				yield return instruction;
				if (instruction.operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(HitData) && methodInfo.Name == "ApplyResistance")
				{
					matches++;
					yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null);
					yield return new CodeInstruction(OpCodes.Ldarg_2, (object)null);
					yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(CreatureArmor), "Apply", (Type[])null, (Type[])null));
				}
			}
			if (matches != 1)
			{
				throw new InvalidOperationException($"Expected one damage resistance hook, found {matches}. Game API changed.");
			}
		}
	}
}