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 Summon Mastery v0.1.2
SummonMastery.dll
Decompiled a month agousing 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."); } } } }