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 BloodMagicCompanion v1.4.1
plugins/BloodMagicCompanion.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("1.4.1.0")] namespace BloodMagicCompanion; internal static class CasterXP { private sealed class Credit { internal ZDOID Caster; internal string Token = Guid.NewGuid().ToString("N"); internal bool Consumed; } [HarmonyPatch(typeof(Player), "Awake")] private static class AdvertiseProtocol { private static void Postfix(Player __instance) { Advertise(__instance); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static class RegisterProtocol { private static void Postfix(ZRoutedRpc __instance) { credits = new ConditionalWeakTable<SE_Shield, Credit>(); receipts.Clear(); __instance.Register<ZPackage>("BloodMagicCasterXP_Break_v3", (Action<long, ZPackage>)ReceiveBreak); } } [HarmonyPatch(typeof(Character), "RPC_Damage")] private static class CaptureCaster { internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return ReplaceCall(instructions, AccessTools.Method(typeof(StatusEffect), "SetAttacker", (Type[])null, (Type[])null), AccessTools.Method(typeof(CasterXP), "RememberCaster", (Type[])null, (Type[])null), appendInstance: false); } } [HarmonyPatch(typeof(SE_Shield), "IsDone")] private static class RedirectReward { internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return ReplaceCall(instructions, AccessTools.Method(typeof(Character), "GetSkills", (Type[])null, (Type[])null), AccessTools.Method(typeof(CasterXP), "GetRewardSkills", (Type[])null, (Type[])null), appendInstance: true); } } private const string LegacyId = "local.valheim.bloodmagiccasterxp"; internal const string RpcName = "BloodMagicCasterXP_Break_v3"; internal static readonly int ShieldHash = StringExtensionMethods.GetStableHashCode("Staff_shield"); internal static readonly int ProtocolHash = StringExtensionMethods.GetStableHashCode("local.valheim.bloodmagiccasterxp.protocol"); private static ConditionalWeakTable<SE_Shield, Credit> credits = new ConditionalWeakTable<SE_Shield, Credit>(); private static readonly ReceiptLedger receipts = new ReceiptLedger(4096); private static ManualLogSource log; private static ConfigEntry<bool> diagnostics; private static bool ready; internal static void Configure(ConfigFile config, ManualLogSource logger) { log = logger; diagnostics = config.Bind<bool>("Logging", "Diagnostics", false, "Log shield attribution, Trollstav tracking and XP delivery for multiplayer troubleshooting."); } internal static void Patch(Harmony harmony) { Type[] array = new Type[4] { typeof(AdvertiseProtocol), typeof(RegisterProtocol), typeof(CaptureCaster), typeof(RedirectReward) }; foreach (Type type in array) { harmony.CreateClassProcessor(type).Patch(); } } internal static void SetEnabled(bool enabled) { ready = enabled; Advertise(Player.m_localPlayer); } internal static void Advertise(Player player) { if (!((Object)(object)player == (Object)null)) { ZNetView component = ((Component)player).GetComponent<ZNetView>(); int num = (ready ? 3 : 0); if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner() && component.GetZDO().GetInt(ProtocolHash, 0) != num) { component.GetZDO().Set(ProtocolHash, num, false); } } } internal static void Trace(string message) { if (diagnostics != null && diagnostics.Value) { log.LogInfo((object)message); } } internal static void RememberCaster(StatusEffect effect, Character attacker) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) effect.SetAttacker(attacker); if (!ready) { return; } SE_Shield val = (SE_Shield)(object)((effect is SE_Shield) ? effect : null); if ((Object)(object)val == (Object)null || ((StatusEffect)val).NameHash() != ShieldHash) { return; } credits.Remove(val); Player val2 = (Player)(object)((attacker is Player) ? attacker : null); if (!((Object)(object)val2 == (Object)null)) { ZNetView component = ((Component)val2).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { credits.Add(val, new Credit { Caster = ((Character)val2).GetZDOID() }); Trace("Tracked shield caster " + ((Character)val2).GetZDOID()); } } } internal static Skills GetRewardSkills(Character protectedCharacter, SE_Shield shield) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) if (!ready) { return protectedCharacter.GetSkills(); } if (((StatusEffect)shield).NameHash() != ShieldHash || (int)shield.m_levelUpSkillOnBreak != 10 || !credits.TryGetValue(shield, out var value)) { return protectedCharacter.GetSkills(); } Player localPlayer = Player.m_localPlayer; bool casterIsLocal = (Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsOwner() && ((Character)localPlayer).GetZDOID() == value.Caster; ZDO val = ((ZDOMan.instance == null) ? null : ZDOMan.instance.GetZDO(value.Caster)); int casterProtocol = ((val != null) ? val.GetInt(ProtocolHash, 0) : 0); long num = ((val == null) ? 0 : val.GetOwner()); ZRoutedRpc instance = ZRoutedRpc.instance; RewardRoute rewardRoute = RewardRouting.Select(protectedCharacter.IsOwner(), value.Consumed, casterIsLocal, casterProtocol, num, instance != null); if (rewardRoute == RewardRoute.Suppress) { return null; } value.Consumed = true; switch (rewardRoute) { case RewardRoute.Vanilla: Trace("Caster unavailable or incompatible; using vanilla shield reward."); return protectedCharacter.GetSkills(); case RewardRoute.LocalCaster: Trace("Shield broke; XP awarded directly to local caster."); return ((Character)localPlayer).GetSkills(); default: { ZPackage val2 = new ZPackage(); val2.Write(value.Caster); val2.Write(protectedCharacter.GetZDOID()); val2.Write(value.Token); val2.Write(shield.m_levelUpSkillFactor); instance.InvokeRoutedRPC(num, "BloodMagicCasterXP_Break_v3", new object[1] { val2 }); Trace(string.Concat("Redirected shield-break XP to ", value.Caster, " (recipient receives none).")); return null; } } } internal static void ReceiveBreak(long sender, ZPackage packet) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (!ready) { return; } try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !((Character)localPlayer).IsOwner()) { return; } ZDOID val = packet.ReadZDOID(); ZDOID val2 = packet.ReadZDOID(); string token = packet.ReadString(); float num = packet.ReadSingle(); if (val != ((Character)localPlayer).GetZDOID() || val2 == val) { return; } ZDO zDO = ZDOMan.instance.GetZDO(val2); if (ReceiptLedger.IsAuthorizedSender(sender, (zDO == null) ? ((long?)null) : new long?(zDO.GetOwner()))) { Skills skills = ((Character)localPlayer).GetSkills(); if (!((Object)(object)skills == (Object)null) && receipts.Accept(sender, token, num)) { skills.RaiseSkill((SkillType)10, num); Trace("Received shield-break Blood Magic XP, factor " + num + "."); } } } catch (Exception ex) { log.LogWarning((object)("Ignored invalid shield XP message: " + ex.Message)); } } internal static IEnumerable<CodeInstruction> ReplaceCall(IEnumerable<CodeInstruction> instructions, MethodInfo original, MethodInfo replacement, bool appendInstance) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(); int num = 0; foreach (CodeInstruction instruction in instructions) { if (!CodeInstructionExtensions.Calls(instruction, original)) { list.Add(instruction); continue; } num++; if (appendInstance) { CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(instruction.labels); val.blocks.AddRange(instruction.blocks); instruction.labels.Clear(); instruction.blocks.Clear(); list.Add(val); } instruction.opcode = OpCodes.Call; instruction.operand = replacement; list.Add(instruction); } if (num != 1) { throw new InvalidOperationException(string.Concat("Expected exactly one ", original, "; found ", num)); } return list; } } internal static class CostModel { internal static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } internal static float Surcharge(float eitr, float skill, float rate, float reduction, float item) { if (!Finite(eitr) || !Finite(skill) || !Finite(rate) || !Finite(reduction) || !Finite(item) || eitr < 0f || rate <= 0f || item <= 0f || reduction < 0f || reduction >= 1f) { return float.MaxValue; } double num = 1.0 - (double)reduction * Math.Max(0.0, Math.Min(1.0, skill)); double val = (double)eitr * (double)rate * (double)item * num; return (float)Math.Min(3.4028234663852886E+38, val); } internal static float Total(float nativeStamina, float surcharge) { if (!Finite(nativeStamina) || !Finite(surcharge) || surcharge < 0f) { return float.MaxValue; } return (float)Math.Min(3.4028234663852886E+38, Math.Max(0.0, nativeStamina) + (double)surcharge); } internal static float Setting(float value, float minimum, float maximum, float fallback) { if (!Finite(value) || !(value >= minimum) || !(value <= maximum)) { return fallback; } return value; } } [BepInIncompatibility("local.valheim.bloodmagiccasterxp")] [BepInPlugin("local.valheim.bloodmagiccompanion", "Blood Magic Companion", "1.4.1")] [BepInIncompatibility("local.valheim.bloodmagichealthcost")] public sealed class Plugin : BaseUnityPlugin { public const string Id = "local.valheim.bloodmagiccompanion"; public const string Version = "1.4.1"; private Harmony harmony; private void Awake() { //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Expected O, but got Unknown bool value = ((BaseUnityPlugin)this).Config.Bind<bool>("HealthCosts", "Enabled", true, "Deprecated: only seeds StaminaCosts/Enabled if the new entry does not exist. Other old HealthCosts settings are ignored.").Value; bool value2 = ((BaseUnityPlugin)this).Config.Bind<bool>("StaminaCosts", "Enabled", value, "Replace Blood Magic eitr with stamina; keep original health costs. Restart required.").Value; bool value3 = ((BaseUnityPlugin)this).Config.Bind<bool>("CasterXP", "Enabled", true, "Redirect shield-break XP to the caster when the simulation owner and caster support it. Restart required.").Value; bool value4 = ((BaseUnityPlugin)this).Config.Bind<bool>("Summons", "AttackTrainingDummy", false, "Allow owned summoned skeletons to attack T.W.I.G. when no ordinary enemy is available. The skeleton simulation owner needs this enabled. Restart required.").Value; bool value5 = ((BaseUnityPlugin)this).Config.Bind<bool>("Summons", "LavaDamageImmunity", false, "Protect owned summoned skeletons from lava damage and lava-applied burning. Other fire damage and boiling ocean damage remain. The simulation owner needs this enabled. Restart required.").Value; bool value6 = ((BaseUnityPlugin)this).Config.Bind<bool>("TrollXP", "Enabled", false, "Award caster Blood Magic XP for summoned troll damage to enemies. Caster and victim simulation owner need this enabled. Resummon trolls after upgrading. Restart required.").Value; TrollXP.Configure(((BaseUnityPlugin)this).Config); StaminaCosts.Configure(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); CasterXP.Configure(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); harmony = new Harmony("local.valheim.bloodmagiccompanion"); try { if (value2) { StaminaCosts.Patch(harmony); } if (value3) { CasterXP.Patch(harmony); } if (value4) { SummonTraining.Patch(harmony); } if (value5) { SummonLavaProtection.Patch(harmony); } if (value6) { TrollXP.Patch(harmony); } StaminaCosts.Active = value2; SummonTraining.Active = value4; SummonLavaProtection.Active = value5; CasterXP.SetEnabled(value3); TrollXP.SetEnabled(value6); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Blood Magic Companion 1.4.1 loaded. StaminaCosts=" + value2 + "; CasterXP=" + value3 + "; AttackTrainingDummy=" + value4 + "; LavaDamageImmunity=" + value5 + "; TrollXP=" + value6 + ". Server and other clients are optional.")); } catch (Exception ex) { StaminaCosts.Active = false; SummonTraining.Active = false; SummonLavaProtection.Active = false; CasterXP.SetEnabled(enabled: false); TrollXP.SetEnabled(enabled: false); harmony.UnpatchSelf(); ((BaseUnityPlugin)this).Logger.LogError((object)("Could not install all requested patches; vanilla behavior retained. " + ex)); } } private void Update() { CasterXP.Advertise(Player.m_localPlayer); TrollXP.Advertise(Player.m_localPlayer); } private void OnDestroy() { StaminaCosts.Active = false; SummonTraining.Active = false; SummonLavaProtection.Active = false; CasterXP.SetEnabled(enabled: false); TrollXP.SetEnabled(enabled: false); if (harmony != null) { harmony.UnpatchSelf(); } } } internal sealed class ReceiptLedger { private readonly int capacity; private readonly HashSet<string> seen = new HashSet<string>(); private readonly Queue<string> order = new Queue<string>(); internal ReceiptLedger(int capacity) { this.capacity = capacity; } internal void Clear() { seen.Clear(); order.Clear(); } internal static bool IsAuthorizedSender(long sender, long? currentOwner) { if (sender != 0 && currentOwner.HasValue) { return currentOwner.Value == sender; } return false; } internal bool Accept(long sender, string token, float factor) { if (sender == 0 || token == null || token.Length != 32 || !Guid.TryParseExact(token, "N", out var _) || float.IsNaN(factor) || float.IsInfinity(factor) || factor <= 0f || factor > 100f) { return false; } string item = sender.ToString(CultureInfo.InvariantCulture) + ":" + token; if (!seen.Add(item)) { return false; } order.Enqueue(item); while (order.Count > capacity) { seen.Remove(order.Dequeue()); } return true; } } internal enum RewardRoute { Vanilla, Suppress, LocalCaster, RemoteCaster } internal static class RewardRouting { internal const int Protocol = 3; internal static RewardRoute Select(bool ownsRecipient, bool consumed, bool casterIsLocal, int casterProtocol, long casterPeer, bool routerAvailable) { if (!ownsRecipient || consumed) { return RewardRoute.Suppress; } if (casterIsLocal) { return RewardRoute.LocalCaster; } if (casterProtocol == 3 && casterPeer != 0 && routerAvailable) { return RewardRoute.RemoteCaster; } return RewardRoute.Vanilla; } } internal static class StaminaCosts { internal static bool Active; internal static bool Tooltips; internal static float EitrRate; internal static float SkillReduction; private static readonly Dictionary<string, float> Multipliers = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static ConfigFile config; private static ManualLogSource log; internal static void Configure(ConfigFile settings, ManualLogSource logger) { config = settings; log = logger; Multipliers.Clear(); Tooltips = config.Bind<bool>("StaminaCosts", "ShowCostInTooltip", true, "Show the additional stamina converted from eitr. Original health costs remain unchanged. Restart required.").Value; EitrRate = ReadNumber("StaminaPerEitr", 1f, 0.01f, 100f, "Stamina per BASE eitr point, before the Blood Magic skill discount. This is a fixed amount, not a fraction of remaining stamina."); SkillReduction = ReadNumber("CostReductionAtSkill100", 0.33f, 0f, 0.9f, "Discount on converted stamina at Blood Magic level 100. Applied once; 0.33 means 33%. Does not change vanilla health or native stamina costs."); string value = config.Bind<string>("StaminaCosts", "ItemCostMultipliers", "StaffShield=1;StaffSkeleton=1;StaffTroll=1", "Prefab-name=multiplier entries separated by semicolons. Applies only to converted stamina. Unknown items use 1; range 0.01 to 100; last valid duplicate wins. Restart required.").Value; string[] array = value.Split(';'); foreach (string text in array) { if (!string.IsNullOrWhiteSpace(text)) { string[] array2 = text.Split('='); if (array2.Length != 2 || string.IsNullOrWhiteSpace(array2[0]) || !float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !CostModel.Finite(result) || result < 0.01f || result > 100f) { log.LogWarning((object)("Ignoring invalid ItemCostMultipliers entry: " + text)); } else { Multipliers[array2[0].Trim()] = result; } } } } internal static void Patch(Harmony harmony) { Type[] array = new Type[4] { typeof(EitrCostPatch), typeof(TooltipEitrPatch), typeof(StaminaCostPatch), typeof(CostTooltipPatch) }; foreach (Type type in array) { harmony.CreateClassProcessor(type).Patch(); } } private static float ReadNumber(string key, float fallback, float minimum, float maximum, string description) { float value = config.Bind<float>("StaminaCosts", key, fallback, description + " Valid range: " + minimum.ToString(CultureInfo.InvariantCulture) + " to " + maximum.ToString(CultureInfo.InvariantCulture) + ". Restart required.").Value; float num = CostModel.Setting(value, minimum, maximum, fallback); if (num != value) { log.LogWarning((object)("Invalid " + key + "; using " + fallback.ToString(CultureInfo.InvariantCulture))); } return num; } internal static bool Applies(Attack attack, Character character, ItemData weapon) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 if (Active && attack != null && (Object)(object)character != (Object)null && (Object)(object)character == (Object)(object)Player.m_localPlayer && character.IsOwner() && weapon != null && weapon.m_shared != null && (int)weapon.m_shared.m_skillType == 10 && attack.m_attackEitr > 0f) { return !attack.m_attackKillsSelf; } return false; } internal static float Surcharge(Attack attack, Character character, ItemData weapon) { float value = 1f; if ((Object)(object)weapon.m_dropPrefab != (Object)null) { Multipliers.TryGetValue(((Object)weapon.m_dropPrefab).name, out value); } if (value <= 0f) { value = 1f; } return CostModel.Surcharge(attack.m_attackEitr, character.GetSkillFactor((SkillType)10), EitrRate, SkillReduction, value); } } [HarmonyPatch(typeof(Attack), "GetAttackEitr", new Type[] { })] internal static class EitrCostPatch { private static bool Prefix(Attack __instance, Humanoid ___m_character, ItemData ___m_weapon, ref float __result) { if (!StaminaCosts.Applies(__instance, (Character)(object)___m_character, ___m_weapon)) { return true; } __result = 0f; return false; } } [HarmonyPatch(typeof(Attack), "GetAttackEitr", new Type[] { typeof(Character), typeof(ItemData) })] internal static class TooltipEitrPatch { private static bool Prefix(Attack __instance, Character __0, ItemData __1, ref float __result) { if (!StaminaCosts.Applies(__instance, __0, __1)) { return true; } __result = 0f; return false; } } [HarmonyPatch(typeof(Attack), "GetAttackStamina", new Type[] { })] internal static class StaminaCostPatch { private static void Postfix(Attack __instance, Humanoid ___m_character, ItemData ___m_weapon, ref float __result) { if (StaminaCosts.Applies(__instance, (Character)(object)___m_character, ___m_weapon)) { __result = CostModel.Total(__result, StaminaCosts.Surcharge(__instance, (Character)(object)___m_character, ___m_weapon)); } } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] internal static class CostTooltipPatch { private static void Postfix(ItemData __0, ref string __result) { if (StaminaCosts.Active && StaminaCosts.Tooltips && __0 != null && __0.m_shared != null && !((Object)(object)Player.m_localPlayer == (Object)null)) { string text = Line("Primary", __0.m_shared.m_attack, __0); string text2 = Line("Secondary", __0.m_shared.m_secondaryAttack, __0); if (text.Length + text2.Length != 0) { string text3 = __result; __result = text3 + "\n\n<color=orange>Stamina-powered Blood Magic</color>" + text + text2 + "\nAdded to any native stamina cost. Original health cost unchanged. Enough stamina is required."; } } } private static string Line(string label, Attack attack, ItemData item) { if (!StaminaCosts.Applies(attack, (Character)(object)Player.m_localPlayer, item)) { return ""; } float num = StaminaCosts.Surcharge(attack, (Character)(object)Player.m_localPlayer, item); if (num == float.MaxValue) { return "\n" + label + ": unavailable (invalid or excessive cost)"; } return "\n" + label + ": <color=orange>+" + num.ToString("0.00", CultureInfo.InvariantCulture) + " stamina</color> from eitr, 0 eitr. Includes your Blood Magic skill."; } } internal static class SummonLavaProtection { [HarmonyPatch(typeof(Character), "UpdateHeatDamage", new Type[] { typeof(float) })] internal static class HeatDamagePatch { internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator) { //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Expected O, but got Unknown //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Expected O, but got Unknown //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); FieldInfo fieldInfo = AccessTools.Field(typeof(Character), "m_lavaHeatLevel"); FieldInfo fieldInfo2 = AccessTools.Field(typeof(Character), "m_ashlandsOceanHeatLevel"); int num = -1; int num2 = -1; int num3 = 0; int num4 = 0; for (int i = 0; i < list.Count; i++) { if (list[i].blocks.Count != 0) { throw new InvalidOperationException("Unsupported heat-damage exception layout."); } if (list[i].opcode != OpCodes.Ldfld) { continue; } if (object.Equals(list[i].operand, fieldInfo)) { num3++; if (num < 0) { num = i - 1; } } if (object.Equals(list[i].operand, fieldInfo2)) { num4++; num2 = i - 1; } } if (fieldInfo == null || fieldInfo2 == null || num3 != 2 || num4 != 1 || num < 0 || num2 <= num || list[num].opcode != OpCodes.Ldarg_0 || list[num2].opcode != OpCodes.Ldarg_0) { throw new InvalidOperationException("Expected lava and ocean damage branches were not found."); } for (int j = num2 + 1; j < list.Count; j++) { if (list[j].opcode == OpCodes.Ldfld && object.Equals(list[j].operand, fieldInfo)) { throw new InvalidOperationException("Unexpected lava branch after ocean heat."); } } Label label = generator.DefineLabel(); list[num2].labels.Add(label); CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[num].labels); list[num].labels.Clear(); list.InsertRange(num, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3] { val, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(SummonLavaProtection), "Applies", (Type[])null, (Type[])null)), new CodeInstruction(OpCodes.Brtrue, (object)label) }); return list; } } internal static bool Active; internal static bool Applies(Character character) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 if (!Active || (Object)(object)character == (Object)null) { return false; } string name = ((Object)((Component)character).gameObject).name; if ((name == "Skeleton_Friendly" || name == "Skeleton_Friendly(Clone)") && character.IsOwner() && character.IsTamed() && !character.IsDead()) { return (int)character.GetFaction() == 0; } return false; } internal static void Patch(Harmony harmony) { harmony.CreateClassProcessor(typeof(HeatDamagePatch)).Patch(); } } internal static class SummonTraining { [HarmonyPatch(typeof(BaseAI), "FindEnemy", new Type[] { })] internal static class FindEnemyPatch { private static void Postfix(BaseAI __instance, Character ___m_character, ref Character __result) { __result = SelectTarget(__instance, ___m_character, __result); } } [HarmonyPatch(typeof(BaseAI), "CanSeeTarget", new Type[] { typeof(Transform), typeof(Vector3), typeof(float), typeof(float), typeof(bool), typeof(bool), typeof(Character) })] internal static class VisibilityPatch { internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); int num = -1; for (int i = 0; i < list.Count; i++) { if (!(list[i].opcode != OpCodes.Call) && object.Equals(list[i].operand, RaycastMethod)) { if (num >= 0 || list[i].blocks.Count != 0) { throw new InvalidOperationException("Unsupported BaseAI visibility raycast layout."); } num = i; } } if (RaycastMethod == null || num < 0) { throw new InvalidOperationException("Expected BaseAI visibility raycast was not found."); } CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[num].labels); list[num].labels.Clear(); list[num].operand = AccessTools.Method(typeof(SummonTraining), "SightBlocked", (Type[])null, (Type[])null); list.Insert(num, val); list.Insert(num + 1, new CodeInstruction(OpCodes.Ldarg_S, (object)(byte)6)); return list; } } internal static bool Active; internal static readonly MethodInfo RaycastMethod = AccessTools.Method(typeof(Physics), "Raycast", new Type[4] { typeof(Vector3), typeof(Vector3), typeof(float), typeof(int) }, (Type[])null); internal static void Patch(Harmony harmony) { harmony.CreateClassProcessor(typeof(FindEnemyPatch)).Patch(); harmony.CreateClassProcessor(typeof(VisibilityPatch)).Patch(); } private static bool Prefab(Character character, string name) { if ((Object)(object)character != (Object)null) { if (!(((Object)((Component)character).gameObject).name == name)) { return ((Object)((Component)character).gameObject).name == name + "(Clone)"; } return true; } return false; } internal static bool Eligible(Character character) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 if (Active && Prefab(character, "Skeleton_Friendly") && character.IsOwner() && character.IsTamed() && !character.IsDead()) { return (int)character.GetFaction() == 0; } return false; } internal static bool IsDummy(Character character) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 if (Prefab(character, "piece_TrainingDummy")) { return (int)character.GetFaction() == 12; } return false; } internal static Character SelectTarget(BaseAI ai, Character character, Character original) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)original != (Object)null || (Object)(object)ai == (Object)null || !Eligible(character)) { return original; } Character targetCreature = ai.GetTargetCreature(); if ((Object)(object)targetCreature != (Object)null && !targetCreature.IsDead() && !IsDummy(targetCreature)) { return original; } Character result = null; float num = float.PositiveInfinity; foreach (Character allCharacter in Character.GetAllCharacters()) { if (IsDummy(allCharacter) && !allCharacter.IsDead()) { float num2 = Vector3.Distance(((Component)ai).transform.position, ((Component)allCharacter).transform.position); if (!(num2 >= num) && ai.CanSeeTarget(allCharacter)) { result = allCharacter; num = num2; } } } return result; } internal static bool SightBlocked(Vector3 origin, Vector3 direction, float distance, int mask, Transform self, Character target) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!Active || (Object)(object)self == (Object)null || !IsDummy(target) || !Eligible(((Component)self).GetComponent<Character>())) { return Physics.Raycast(origin, direction, distance, mask); } RaycastHit[] array = Physics.RaycastAll(origin, direction, distance, mask); for (int i = 0; i < array.Length; i++) { RaycastHit val = array[i]; if ((Object)(object)((RaycastHit)(ref val)).collider == (Object)null || (Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent<Character>() != (Object)(object)target) { return true; } } return false; } } internal static class TrollXP { [HarmonyPatch(typeof(SpawnAbility), "SetupAoe")] private static class CaptureSpawn { private static void Prefix(SpawnAbility __instance, Character owner, Character ___m_owner) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((___m_owner is Player) ? ___m_owner : null); if (ready && !((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && ((Character)val).IsOwner() && !((Object)(object)owner == (Object)null) && owner.IsOwner() && TrollXpPolicy.IsSpawn(((Object)((Component)__instance).gameObject).name) && TrollXpPolicy.IsTroll(((Object)((Component)owner).gameObject).name)) { ZDO val2 = Data(owner); if (val2 != null) { val2.Set("local.valheim.bloodmagiccompanion.trollCaster.v1", ((Character)val).GetZDOID()); CasterXP.Trace(string.Concat("Tracked Trollstav caster ", ((Character)val).GetZDOID(), " for ", owner.GetZDOID())); } } } } internal sealed class DamageCredit { internal float Before; internal ZDOID Caster; internal ZDOID Troll; internal ZDOID Victim; } [HarmonyPatch(typeof(Character), "ApplyDamage")] private static class RewardDamage { private static void Prefix(Character __instance, HitData hit, out DamageCredit __state) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Invalid comparison between Unknown and I4 //IL_00ca: 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_00d3: 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_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) __state = null; if (!ready || hit == null || !__instance.IsOwner() || __instance.IsDead()) { return; } Character attacker = hit.GetAttacker(); if ((Object)(object)attacker == (Object)null || !TrollXpPolicy.IsTroll(((Object)((Component)attacker).gameObject).name)) { return; } ZDO val = Data(attacker); if (val != null) { ZDOID zDOID = val.GetZDOID("local.valheim.bloodmagiccompanion.trollCaster.v1"); Player val2 = FindCaster(zDOID); if (!((Object)(object)val2 == (Object)null) && TrollXpPolicy.Eligible(ready, __instance.IsOwner(), !__instance.IsDead(), __instance.IsPlayer(), __instance.IsTamed(), BaseAI.IsEnemy((Character)(object)val2, __instance), (int)__instance.GetFaction() == 12, (Object)(object)attacker == (Object)(object)__instance, ((Object)((Component)attacker).gameObject).name)) { __state = new DamageCredit { Before = __instance.GetHealth(), Caster = zDOID, Troll = attacker.GetZDOID(), Victim = __instance.GetZDOID() }; } } } private static void Postfix(Character __instance, DamageCredit __state) { if (ready && __state != null && TrollXpPolicy.LostHealth(__state.Before, __instance.GetHealth())) { Report(__state); } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static class RegisterProtocol { private static void Postfix(ZRoutedRpc __instance) { receipts.Clear(); __instance.Register<ZPackage>("BloodMagicCompanion_TrollHit_v1", (Action<long, ZPackage>)Receive); } } internal const string CasterKey = "local.valheim.bloodmagiccompanion.trollCaster.v1"; internal const string RpcName = "BloodMagicCompanion_TrollHit_v1"; internal static readonly int ProtocolHash = StringExtensionMethods.GetStableHashCode("local.valheim.bloodmagiccompanion.trollXp.v1"); private static readonly ReceiptLedger receipts = new ReceiptLedger(4096); private static ConfigEntry<float> factorSetting; private static bool ready; internal static void Configure(ConfigFile config) { factorSetting = config.Bind<float>("TrollXP", "Factor", 0.5f, "Blood Magic skill gain factor per accepted damage event (not whole skill levels). Range 0.01-10. Caster setting applies. Restart required."); } internal static void Patch(Harmony harmony) { Type[] array = new Type[3] { typeof(CaptureSpawn), typeof(RewardDamage), typeof(RegisterProtocol) }; foreach (Type type in array) { harmony.CreateClassProcessor(type).Patch(); } } internal static void SetEnabled(bool enabled) { ready = enabled; receipts.Clear(); Advertise(Player.m_localPlayer); } internal static void Advertise(Player player) { if (!((Object)(object)player == (Object)null)) { ZNetView component = ((Component)player).GetComponent<ZNetView>(); int num = (ready ? 3 : 0); if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner() && component.GetZDO().GetInt(ProtocolHash, 0) != num) { component.GetZDO().Set(ProtocolHash, num, false); } } } private static ZDO Data(Character character) { ZNetView val = (((Object)(object)character == (Object)null) ? null : ((Component)character).GetComponent<ZNetView>()); if (!((Object)(object)val != (Object)null) || !val.IsValid()) { return null; } return val.GetZDO(); } private static ZDO Lookup(ZDOID id) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (ZDOMan.instance != null) { return ZDOMan.instance.GetZDO(id); } return null; } private static Player FindCaster(ZDOID id) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)ZNetScene.instance == (Object)null) ? null : ZNetScene.instance.FindInstance(id)); if (!((Object)(object)val == (Object)null)) { return val.GetComponent<Player>(); } return null; } private static void Report(DamageCredit credit) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown //IL_0090: 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_00aa: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; bool casterIsLocal = (Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsOwner() && ((Character)localPlayer).GetZDOID() == credit.Caster; ZDO val = Lookup(credit.Caster); long num = ((val == null) ? 0 : val.GetOwner()); switch (RewardRouting.Select(ownsRecipient: true, consumed: false, casterIsLocal, (val != null) ? val.GetInt(ProtocolHash, 0) : 0, num, ZRoutedRpc.instance != null)) { case RewardRoute.LocalCaster: Award(localPlayer, credit.Troll); break; case RewardRoute.RemoteCaster: { ZPackage val2 = new ZPackage(); val2.Write(credit.Caster); val2.Write(credit.Troll); val2.Write(credit.Victim); val2.Write(Guid.NewGuid().ToString("N")); ZRoutedRpc.instance.InvokeRoutedRPC(num, "BloodMagicCompanion_TrollHit_v1", new object[1] { val2 }); break; } } } private static void Award(Player player, ZDOID troll) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) if (ready && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && ((Character)player).IsOwner()) { Skills skills = ((Character)player).GetSkills(); if (!((Object)(object)skills == (Object)null)) { float num = CostModel.Setting(factorSetting.Value, 0.01f, 10f, 0.5f); skills.RaiseSkill((SkillType)10, num); CasterXP.Trace("Awarded Trollstav Blood Magic XP to local caster, factor " + num + ", troll " + troll); } } } private static void Receive(long sender, ZPackage packet) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) if (!ready) { return; } try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !((Character)localPlayer).IsOwner()) { return; } ZDOID val = packet.ReadZDOID(); ZDOID val2 = packet.ReadZDOID(); ZDOID val3 = packet.ReadZDOID(); string token = packet.ReadString(); if (!(val != ((Character)localPlayer).GetZDOID()) && !(val3 == val) && !(val3 == val2)) { ZDO val4 = Lookup(val3); ZDO val5 = Lookup(val2); if (val5 != null && !(val5.GetZDOID("local.valheim.bloodmagiccompanion.trollCaster.v1") != val) && val5.GetPrefab() == StringExtensionMethods.GetStableHashCode("Troll_Summoned") && ReceiptLedger.IsAuthorizedSender(sender, (val4 == null) ? ((long?)null) : new long?(val4.GetOwner())) && receipts.Accept(sender, token, 1f)) { Award(localPlayer, val2); } } } catch (Exception ex) { CasterXP.Trace("Ignored invalid Trollstav XP report: " + ex.Message); } } } internal static class TrollXpPolicy { internal static bool IsTroll(string name) { if (!(name == "Troll_Summoned")) { return name == "Troll_Summoned(Clone)"; } return true; } internal static bool IsSpawn(string name) { if (!(name == "staff_redtroll_spawn")) { return name == "staff_redtroll_spawn(Clone)"; } return true; } internal static bool Eligible(bool active, bool ownsVictim, bool living, bool player, bool tame, bool enemy, bool dummy, bool self, string attacker) { if (active && ownsVictim && living && !player && !tame && enemy && !dummy && !self) { return IsTroll(attacker); } return false; } internal static bool LostHealth(float before, float after) { if (CostModel.Finite(before) && CostModel.Finite(after) && before > 0f) { return after < before; } return false; } }