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 ValheimWeaponClasses v1.1.3
ValheimWeaponClasses.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.GUI; using Jotunn.Managers; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; using ValheimClassObelisk; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ValheimClassObelisk")] [assembly: AssemblyDescription("Class Obelisk build piece for Valheim")] [assembly: AssemblyProduct("ValheimWeaponClasses")] [assembly: AssemblyFileVersion("1.1.3.0")] [assembly: ComVisible(false)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.1.3.0")] public static class ArcherPerkManager { private const string Intro = "Expert marksmen with disciplined bow technique and practical crossbow mastery."; private const string Outro = "Best for players who want ranged weapons to feel steady early, distinct by weapon type mid-game, and deadly at mastery."; public static readonly List<PerkInfo> Perks = new List<PerkInfo> { new PerkInfo { RequiredLevel = 10, Name = "Practiced Aim", Description = "+7% bow and crossbow damage." }, new PerkInfo { RequiredLevel = 20, Name = "Magic Shot", Description = "25% chance to not consume arrows or bolts." }, new PerkInfo { RequiredLevel = 30, Name = "Combat Rhythm", Description = "Bow hits grant Arrow Slinger for 6s, reducing draw time by 25%. Crossbow hits grant Quick Crank for 6s, reducing reload time by 25%." }, new PerkInfo { RequiredLevel = 40, Name = "Storm Fletching", Description = "Bow and crossbow attacks deal bonus lightning damage equal to 12% of weapon damage." }, new PerkInfo { RequiredLevel = 50, Name = "Deadeye", Description = "+15% bow and crossbow damage. Hits beyond 25m gain an additional +10% damage." } }; public const float COMBAT_RHYTHM_DURATION = 10f; public const float COMBAT_RHYTHM_REDUCTION = 0.25f; private static readonly Dictionary<Player, float> bowBuffExpire = new Dictionary<Player, float>(); private static readonly Dictionary<Player, float> crossbowBuffExpire = new Dictionary<Player, float>(); public const float STORM_FLETCHING_LIGHTNING_PERCENT = 0.12f; public const float DEADEYE_RANGE_BONUS = 0.1f; public const float DEADEYE_RANGE_THRESHOLD = 25f; public static bool HasArcherPerk(Player player, int requiredLevel) { if ((Object)(object)player == (Object)null) { return false; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData == null || !playerData.IsClassActive(PlayerClass.Archer)) { return false; } return playerData.GetClassLevel(PlayerClass.Archer) >= requiredLevel; } public static string GetClassDescription(Player player) { int currentLevel = PlayerClassManager.GetPlayerData(player)?.GetClassLevel(PlayerClass.Archer) ?? 0; return PerkDescriptionBuilder.Build("Expert marksmen with disciplined bow technique and practical crossbow mastery.", Perks, "Best for players who want ranged weapons to feel steady early, distinct by weapon type mid-game, and deadly at mastery.", currentLevel); } public static void TriggerArrowSlinger(Player player) { bowBuffExpire[player] = Time.time + 10f; ApplyCombatRhythmStatusEffect(player, "SE_ArrowSlinger", "Arrow Slinger", "Draw speed increased by 25%"); ((Character)player).Message((MessageType)1, $"Arrow Slinger: 25% faster draw for {10f:F0}s!", 0, (Sprite)null, false); } public static void TriggerQuickCrank(Player player) { crossbowBuffExpire[player] = Time.time + 10f; ApplyCombatRhythmStatusEffect(player, "SE_QuickCrank", "Quick Crank", "Reload speed increased by 25%"); ((Character)player).Message((MessageType)1, $"Quick Crank: 25% faster reload for {10f:F0}s!", 0, (Sprite)null, false); } private static void ApplyCombatRhythmStatusEffect(Player player, string effectName, string displayName, string tooltip) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) try { SEMan sEMan = ((Character)player).GetSEMan(); if (sEMan != null) { sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode(effectName), true); SE_Stats val = ScriptableObject.CreateInstance<SE_Stats>(); ((Object)val).name = effectName; ((StatusEffect)val).m_name = displayName; ((StatusEffect)val).m_tooltip = tooltip; ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon(); ((StatusEffect)val).m_icon = ((currentWeapon != null) ? currentWeapon.GetIcon() : null); ((StatusEffect)val).m_ttl = 10f; ((StatusEffect)val).m_startMessage = ""; ((StatusEffect)val).m_startMessageType = (MessageType)2; ((StatusEffect)val).m_stopMessage = ""; ((StatusEffect)val).m_stopMessageType = (MessageType)2; sEMan.AddStatusEffect((StatusEffect)(object)val, true, 0, 0f, (short)(-1)); } } catch (Exception ex) { Logger.LogError((object)("Error adding Combat Rhythm status effect (" + effectName + "): " + ex.Message)); } } public static bool HasArrowSlingerActive(Player player) { if ((Object)(object)player != (Object)null && bowBuffExpire.TryGetValue(player, out var value)) { return Time.time < value; } return false; } public static bool HasQuickCrankActive(Player player) { if ((Object)(object)player != (Object)null && crossbowBuffExpire.TryGetValue(player, out var value)) { return Time.time < value; } return false; } public static void UpdateBuffs() { float currentTime = Time.time; foreach (Player item in (from kvp in bowBuffExpire where (Object)(object)kvp.Key == (Object)null || currentTime >= kvp.Value select kvp.Key).ToList()) { bowBuffExpire.Remove(item); } foreach (Player item2 in (from kvp in crossbowBuffExpire where (Object)(object)kvp.Key == (Object)null || currentTime >= kvp.Value select kvp.Key).ToList()) { crossbowBuffExpire.Remove(item2); } } public static void ApplyStormFletchingLightning(ref HitData hit, float weaponDamage) { hit.m_damage.m_lightning += weaponDamage * 0.12f; } } [HarmonyPatch] public static class ArcherPerkPatches { private static float lastCleanupTime; [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] public static void Character_Damage_Archer_Prefix(Character __instance, ref HitData hit) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) try { if ((int)hit.m_skill == 0) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && !((Object)(object)__instance == (Object)null) && !(__instance is Player) && ClassCombatManager.IsBowWeapon(((Humanoid)val).GetCurrentWeapon()) && ArcherPerkManager.HasArcherPerk(val, 1)) { float totalDamage = hit.GetTotalDamage(); if (ArcherPerkManager.HasArcherPerk(val, 40)) { ArcherPerkManager.ApplyStormFletchingLightning(ref hit, totalDamage); } if (ArcherPerkManager.HasArcherPerk(val, 50) && Vector3.Distance(((Component)val).transform.position, hit.m_point) > 25f) { float num = 1.1f; hit.m_damage.m_damage *= num; hit.m_damage.m_blunt *= num; hit.m_damage.m_slash *= num; hit.m_damage.m_pierce *= num; hit.m_damage.m_chop *= num; hit.m_damage.m_fire *= num; hit.m_damage.m_frost *= num; hit.m_damage.m_lightning *= num; hit.m_damage.m_poison *= num; hit.m_damage.m_spirit *= num; } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Archer_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPostfix] public static void Character_Damage_Archer_Postfix(Character __instance, HitData hit) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Invalid comparison between Unknown and I4 try { if ((int)hit.m_skill == 0) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || (Object)(object)__instance == (Object)null || __instance is Player || hit.GetTotalDamage() <= 0f) { return; } ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); if (ClassCombatManager.IsBowWeapon(currentWeapon) && ArcherPerkManager.HasArcherPerk(val, 30)) { if ((int)currentWeapon.m_shared.m_skillType == 14) { ArcherPerkManager.TriggerQuickCrank(val); } else { ArcherPerkManager.TriggerArrowSlinger(val); } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Archer_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Humanoid), "GetAttackDrawPercentage")] [HarmonyPostfix] public static void Humanoid_GetAttackDrawPercentage_Archer_Postfix(Humanoid __instance, ref float __result) { try { if (!(__result <= 0f)) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && ArcherPerkManager.HasArrowSlingerActive(val)) { __result = Mathf.Clamp01(__result / 0.75f); } } } catch (Exception ex) { Logger.LogError((object)("Error in Humanoid_GetAttackDrawPercentage_Archer_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Player), "UpdateActionQueue")] [HarmonyPrefix] public static void Player_UpdateActionQueue_Archer_Prefix(Player __instance, ref float dt, List<MinorActionData> ___m_actionQueue) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 try { if (___m_actionQueue.Count != 0) { MinorActionData val = ___m_actionQueue[0]; if ((int)val.m_type == 2 && ArcherPerkManager.HasQuickCrankActive(__instance)) { dt /= 0.75f; _ = val.m_time; _ = 0f; } } } catch (Exception ex) { Logger.LogError((object)("Error in Player_UpdateActionQueue_Archer_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[] { typeof(ItemData), typeof(int) })] [HarmonyPrefix] public static void Inventory_RemoveItem_MagicShot_PreFix(Inventory __instance, ItemData item, int amount) { try { if (item == null || !IsArrowItem(item)) { return; } Player val = FindPlayerWithInventory(__instance); if (!((Object)(object)val == (Object)null) && ClassCombatManager.IsBowWeapon(((Humanoid)val).GetCurrentWeapon()) && ArcherPerkManager.HasArcherPerk(val, 20) && Random.Range(0f, 1f) < 0.25f) { List<ItemData> list = (from i in __instance.GetAllItems() where i.m_shared.m_name == item.m_shared.m_name orderby i.m_stack select i).ToList(); if (list.Count > 0) { ItemData obj = list.First(); obj.m_stack += amount; } } } catch (Exception ex) { Logger.LogError((object)("Error in Inventory_RemoveItem_MagicShot_PreFix: " + ex.Message)); } } private static bool IsArrowItem(ItemData item) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (item?.m_shared == null) { return false; } if ((int)item.m_shared.m_itemType != 9 && !item.m_shared.m_name.ToLower().Contains("arrow")) { return item.m_shared.m_name.ToLower().Contains("bolt"); } return true; } private static Player FindPlayerWithInventory(Inventory inventory) { return ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player p) => ((Humanoid)p).GetInventory() == inventory)); } [HarmonyPatch(typeof(Game), "Update")] [HarmonyPostfix] public static void Game_Update_Archer_Postfix() { try { if (Time.time - lastCleanupTime >= 1f) { lastCleanupTime = Time.time; ArcherPerkManager.UpdateBuffs(); } } catch (Exception ex) { Logger.LogError((object)("Error in Game_Update_Archer_Postfix: " + ex.Message)); } } } [HarmonyPatch] public static class AssassinPerkManager { public class PoisonData { public int stacks; public float expireTime; public float pendingTotalDamage; public bool damagePending; public Player source; } public static Dictionary<Character, PoisonData> poisonTracking = new Dictionary<Character, PoisonData>(); private static Dictionary<Player, float> assassinationBuffs = new Dictionary<Player, float>(); private static readonly Dictionary<Player, float> _cachedMoveSpeed = new Dictionary<Player, float>(); public const int MAX_POISON_STACKS = 3; public const float ASSASSINATION_SPEED_DURATION = 5f; public const float ASSASSINATION_SPEED_BONUS = 1.2f; public const float ASSASSINATION_DAMAGE_BONUS = 0.5f; public const float SILENT_HANDS_STAMINA_REDUCTION = 0.15f; private const float VANILLA_BASE_CROUCH_SPEED = 2f; public const float TWIST_KNIFE_DAMAGE_BONUS = 0.15f; public const float TWIST_KNIFE_DAMAGE_REDUCTION = 0.1f; public static bool DAMAGE_MODS_ON = true; private const string Intro = "Stealthy fighters who use knives, positioning, and toxins to end fights quickly."; private const string Outro = "Best for players who want the class to build from light knife mastery into stealth burst and poison execution."; public static readonly List<PerkInfo> Perks = new List<PerkInfo> { new PerkInfo { RequiredLevel = 10, Name = "Cutthroat", Description = "+7% knife damage." }, new PerkInfo { RequiredLevel = 20, Name = "Silent Hands", Description = "Knife attacks and stealth movement consume 15% less stamina. Additionally, you move at normal movement speed while crouched." }, new PerkInfo { RequiredLevel = 30, Name = "Assassination", Description = "First knife hit from stealth deals +50% damage. After a stealth hit, knife attack speed is increased by 20% for 5s." }, new PerkInfo { RequiredLevel = 40, Name = "Venom Coating", Description = "Knife hits apply stacking poison damage over time, up to 3 stacks. Poison damage scales with knife skill." }, new PerkInfo { RequiredLevel = 50, Name = "Twist the Knife", Description = "+15% knife damage against poisoned targets. Poisoned enemies deal 10% less damage to you." } }; public static bool HasAssassinPerk(Player player, int requiredLevel) { if ((Object)(object)player == (Object)null) { return false; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData == null || !playerData.IsClassActive(PlayerClass.Assassin)) { return false; } return playerData.GetClassLevel(PlayerClass.Assassin) >= requiredLevel; } public static string GetClassDescription(Player player) { int currentLevel = PlayerClassManager.GetPlayerData(player)?.GetClassLevel(PlayerClass.Assassin) ?? 0; return PerkDescriptionBuilder.Build("Stealthy fighters who use knives, positioning, and toxins to end fights quickly.", Perks, "Best for players who want the class to build from light knife mastery into stealth burst and poison execution.", currentLevel); } private static void RefreshSilentHandsCrouchSpeed(Player player) { if (!((Object)(object)player == (Object)null)) { float value; float num = (_cachedMoveSpeed.TryGetValue(player, out value) ? value : ((Character)player).m_speed); ((Character)player).m_crouchSpeed = (HasAssassinPerk(player, 20) ? num : 2f); } } private static void ApplyAssassinationSpeedBuff(Player player) { float value = Time.time + 5f; assassinationBuffs[player] = value; AnimationSpeedManager.Set((Character)(object)player, "Assassin_Knife_AS", 1.2f); AddAssassinationStatusEffect(player); } private static void AddAssassinationStatusEffect(Player player) { try { SEMan sEMan = ((Character)player).GetSEMan(); if (sEMan != null) { sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinationSpeed"), true); SE_Stats val = ScriptableObject.CreateInstance<SE_Stats>(); ((Object)val).name = "SE_AssassinationSpeed"; ((StatusEffect)val).m_name = "Assassination"; ((StatusEffect)val).m_tooltip = "+20% knife attack speed"; ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon(); ((StatusEffect)val).m_icon = ((currentWeapon != null) ? currentWeapon.GetIcon() : null); ((StatusEffect)val).m_ttl = 5f; sEMan.AddStatusEffect((StatusEffect)(object)val, true, 0, 0f, (short)(-1)); } } catch (Exception ex) { Logger.LogError((object)("Error adding assassination status effect: " + ex.Message)); } } public static bool IsStealthHit(Player attacker, Character target) { if ((Object)(object)attacker == (Object)null || (Object)(object)target == (Object)null) { return false; } BaseAI baseAI = target.GetBaseAI(); if ((Object)(object)baseAI == (Object)null) { return false; } if (baseAI.IsAlerted()) { return false; } if (baseAI.HaveTarget()) { return false; } return true; } private static float EstimateVanillaPoisonTTL(float totalDamage) { return 2f + Mathf.Pow(totalDamage * 2f, 0.5f); } public static void ApplyLv40_VenomCoating(Player player, Character target, HitData hit) { if (HasAssassinPerk(player, 40) && !((Object)(object)target == (Object)null) && !target.IsDead()) { float skillLevel = ((Character)player).GetSkillLevel((SkillType)2); if (!poisonTracking.TryGetValue(target, out var value) || Time.time >= value.expireTime) { value = new PoisonData(); poisonTracking[target] = value; } if (value.stacks < 3) { value.stacks++; } float num = skillLevel * (float)value.stacks; float num2 = EstimateVanillaPoisonTTL(num); value.pendingTotalDamage = num; value.damagePending = true; value.expireTime = Time.time + num2; value.source = player; AddPoisonStatusEffect(target, value.stacks, num2); } } private static void AddPoisonStatusEffect(Character target, int stacks, float ttl) { try { SEMan sEMan = target.GetSEMan(); if (sEMan != null) { sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinPoison"), true); SE_Stats val = ScriptableObject.CreateInstance<SE_Stats>(); ((Object)val).name = "SE_AssassinPoison"; ((StatusEffect)val).m_name = $"Venom ({stacks} stacks)"; ((StatusEffect)val).m_tooltip = $"Taking poison damage over time. {stacks}/{3} stacks"; Sprite poisonIcon = GetPoisonIcon(); if ((Object)(object)poisonIcon != (Object)null) { ((StatusEffect)val).m_icon = poisonIcon; } ((StatusEffect)val).m_ttl = ttl; sEMan.AddStatusEffect((StatusEffect)(object)val, true, 0, 0f, (short)(-1)); } } catch (Exception ex) { Logger.LogError((object)("Error adding poison status effect: " + ex.Message)); } } private static Sprite GetPoisonIcon() { try { ObjectDB instance = ObjectDB.instance; return ((instance != null) ? instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode("Poison")) : null)?.m_icon; } catch { return null; } } public static void ProcessPoisonExpiry() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) float time = Time.time; List<Character> list = new List<Character>(); foreach (KeyValuePair<Character, PoisonData> item in poisonTracking) { Character key = item.Key; PoisonData value = item.Value; if ((Object)(object)key == (Object)null || key.IsDead() || time > value.expireTime) { list.Add(key); } else if (value.damagePending) { value.damagePending = false; HitData val = new HitData(); val.m_damage.m_poison = value.pendingTotalDamage; Player source = value.source; val.m_attacker = ((source != null) ? ((Character)source).GetZDOID() : ZDOID.None); val.m_point = ((Component)key).transform.position; key.Damage(val); } } foreach (Character item2 in list) { if ((Object)(object)item2 != (Object)null) { SEMan sEMan = item2.GetSEMan(); if (sEMan != null) { sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinPoison"), true); } } poisonTracking.Remove(item2); } } private static bool IsTargetPoisoned(Character target) { if ((Object)(object)target != (Object)null && poisonTracking.TryGetValue(target, out var value)) { return Time.time < value.expireTime; } return false; } public static HitData ModDamage(HitData hit, float mod) { if (hit == null) { return hit; } hit.m_damage.m_damage *= mod; hit.m_damage.m_slash *= mod; hit.m_damage.m_pierce *= mod; hit.m_damage.m_blunt *= mod; hit.m_damage.m_fire *= mod; hit.m_damage.m_frost *= mod; hit.m_damage.m_spirit *= mod; hit.m_damage.m_poison *= mod; return hit; } public static void CleanupBuffs() { float currentTime = Time.time; foreach (Player item in (from kvp in assassinationBuffs where kvp.Value < currentTime select kvp.Key).ToList()) { assassinationBuffs.Remove(item); if ((Object)(object)item != (Object)null) { SEMan sEMan = ((Character)item).GetSEMan(); if (sEMan != null) { sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinationSpeed"), true); } AnimationSpeedManager.Clear((Character)(object)item, "Assassin_Knife_AS"); } } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] public static void Character_Damage_Assassin_Prefix(Character __instance, ref HitData hit) { try { if (hit.m_damage.m_poison > 0f) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || (Object)(object)__instance == (Object)null || __instance is Player) { Character attacker2 = hit.GetAttacker(); PlayerClassData playerData = PlayerClassManager.GetPlayerData(Player.m_localPlayer); if (playerData != null && playerData.IsClassActive(PlayerClass.Assassin) && HasAssassinPerk(Player.m_localPlayer, 50) && IsTargetPoisoned(attacker2)) { hit = ModDamage(hit, 0.9f); } } else { if (!ClassCombatManager.IsKnifeWeapon(((Humanoid)val).GetCurrentWeapon())) { return; } PlayerClassData playerData2 = PlayerClassManager.GetPlayerData(val); if (playerData2 != null && playerData2.IsClassActive(PlayerClass.Assassin)) { float num = 0f; if (HasAssassinPerk(val, 30) && IsStealthHit(val, __instance)) { num += 0.5f; ApplyAssassinationSpeedBuff(val); } if (HasAssassinPerk(val, 50) && IsTargetPoisoned(__instance)) { num += 0.15f; } if (num > 0f && DAMAGE_MODS_ON) { hit = ModDamage(hit, num + 1f); } } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Assassin_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPostfix] public static void Character_Damage_Assassin_Postfix(Character __instance, HitData hit) { try { if (hit.m_damage.m_poison > 0f) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && !((Object)(object)__instance == (Object)null) && !(__instance is Player) && !(hit.GetTotalDamage() <= 0f) && ClassCombatManager.IsKnifeWeapon(((Humanoid)val).GetCurrentWeapon())) { PlayerClassData playerData = PlayerClassManager.GetPlayerData(val); if (playerData != null && playerData.IsClassActive(PlayerClass.Assassin) && (hit.m_damage.m_blunt != 0f || hit.m_damage.m_slash != 0f || hit.m_damage.m_pierce != 0f)) { ApplyLv40_VenomCoating(val, __instance, hit); } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Assassin_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Player), "UseStamina")] [HarmonyPrefix] public static void Player_UseStamina_Assassin_Prefix(Player __instance, ref float v) { try { if (HasAssassinPerk(__instance, 20)) { bool num = ClassCombatManager.IsKnifeWeapon(((Humanoid)__instance).GetCurrentWeapon()); bool flag = ((Character)__instance).IsCrouching(); if (num || flag) { v *= 0.85f; } } } catch (Exception ex) { Logger.LogError((object)("Error in Player_UseStamina_Assassin_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Player), "OnSpawned")] [HarmonyPostfix] public static void Player_OnSpawned_Assassin_Postfix(Player __instance) { try { RefreshSilentHandsCrouchSpeed(__instance); } catch (Exception ex) { Logger.LogError((object)("Error in Player_OnSpawned_Assassin_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Player), "SetCrouch")] [HarmonyPrefix] public static void Player_SetCrouch_Assassin_Prefix(Player __instance) { try { _cachedMoveSpeed[__instance] = ((Character)__instance).m_speed; } catch (Exception ex) { Logger.LogError((object)("Error in Player_SetCrouch_Assassin_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Player), "SetCrouch")] [HarmonyPostfix] public static void Player_SetCrouch_Assassin_Postfix(Player __instance) { try { RefreshSilentHandsCrouchSpeed(__instance); } catch (Exception ex) { Logger.LogError((object)("Error in Player_SetCrouch_Assassin_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Game), "Update")] [HarmonyPostfix] public static void Game_Update_Assassin_Postfix() { try { ProcessPoisonExpiry(); if (Time.time % 1f < Time.deltaTime) { CleanupBuffs(); } } catch (Exception ex) { Logger.LogError((object)("Error in Game_Update_Assassin_Postfix: " + ex.Message)); } } } [HarmonyPatch] public static class ExecutionerPerkManager { private class RendingRhythmData { public int stacks; public float expireTime; } private class BleedData { public float totalDamage; public float damagePerTick; public float expireTime; public float lastTickTime; public Player source; } private const string Intro = "Axe specialists who trade finesse for brutal chopping power, wounds, and relentless finishing pressure."; private const string Outro = "Best for players who want axes to become their own class identity instead of living under Sword Master."; public static readonly List<PerkInfo> Perks = new List<PerkInfo> { new PerkInfo { RequiredLevel = 10, Name = "Chopper's Training", Description = "+8% axe damage with one-handed axes and battleaxes." }, new PerkInfo { RequiredLevel = 20, Name = "Woodsman's Carry", Description = "Axes weigh 50% less and impose no movement speed penalties." }, new PerkInfo { RequiredLevel = 30, Name = "Rending Rhythm", Description = "One-handed axe hits against the same target build up to 3 stacks; each stack grants +5% damage to that target for 5s. Battleaxe special attacks apply all 3 stacks at once." }, new PerkInfo { RequiredLevel = 40, Name = "Hemorrhage", Description = "Axe attacks apply bleed, dealing slash damage over time equal to 12% of weapon damage over 5s. Reapplying bleed refreshes the duration." }, new PerkInfo { RequiredLevel = 50, Name = "Execute", Description = "+15% axe damage. Axe attacks deal an additional +10% damage against enemies below 40% health." } }; private const float RENDING_RHYTHM_DURATION = 5f; private const float RENDING_RHYTHM_PER_STACK = 0.05f; private const int RENDING_RHYTHM_MAX_STACKS = 3; private static readonly Dictionary<Character, RendingRhythmData> rendingRhythmStacks = new Dictionary<Character, RendingRhythmData>(); private const float HEMORRHAGE_DURATION = 5f; private const float HEMORRHAGE_PERCENT = 0.12f; private const int HEMORRHAGE_TICKS = 5; private static readonly Dictionary<Character, BleedData> bleedTracking = new Dictionary<Character, BleedData>(); private static readonly Dictionary<Player, bool> pendingTwoHandedSpecialAttack = new Dictionary<Player, bool>(); public static bool HasExecutionerPerk(Player player, int requiredLevel) { if ((Object)(object)player == (Object)null) { return false; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData == null || !playerData.IsClassActive(PlayerClass.Executioner)) { return false; } return playerData.GetClassLevel(PlayerClass.Executioner) >= requiredLevel; } public static string GetClassDescription(Player player) { int currentLevel = PlayerClassManager.GetPlayerData(player)?.GetClassLevel(PlayerClass.Executioner) ?? 0; return PerkDescriptionBuilder.Build("Axe specialists who trade finesse for brutal chopping power, wounds, and relentless finishing pressure.", Perks, "Best for players who want axes to become their own class identity instead of living under Sword Master.", currentLevel); } private static float GetRendingRhythmBonus(Character target) { if ((Object)(object)target == (Object)null) { return 0f; } if (!rendingRhythmStacks.TryGetValue(target, out var value)) { return 0f; } if (Time.time >= value.expireTime) { return 0f; } return (float)value.stacks * 0.05f; } private static void AddRendingRhythmStack(Character target, bool applyAllStacks) { if (!((Object)(object)target == (Object)null)) { if (!rendingRhythmStacks.TryGetValue(target, out var value) || Time.time >= value.expireTime) { value = new RendingRhythmData(); rendingRhythmStacks[target] = value; } value.stacks = (applyAllStacks ? 3 : Mathf.Min(value.stacks + 1, 3)); value.expireTime = Time.time + 5f; } } private static void ApplyHemorrhage(Player player, Character target, float hitDamage) { if (!HasExecutionerPerk(player, 40) || (Object)(object)target == (Object)null || target.IsDead()) { return; } float num = hitDamage * 0.12f; if (!(num <= 0f)) { if (!bleedTracking.TryGetValue(target, out var value) || Time.time >= value.expireTime) { value = new BleedData(); bleedTracking[target] = value; } value.totalDamage = Mathf.Max(value.totalDamage, num); value.damagePerTick = value.totalDamage / 5f; value.expireTime = Time.time + 5f; value.source = player; } } private static void ProcessHemorrhageTicks() { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00a3: 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) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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_00c3: Unknown result type (might be due to invalid IL or missing references) float time = Time.time; List<Character> list = new List<Character>(); foreach (KeyValuePair<Character, BleedData> item in bleedTracking) { Character key = item.Key; BleedData value = item.Value; if ((Object)(object)key == (Object)null || key.IsDead() || time > value.expireTime) { list.Add(key); } else if (time - value.lastTickTime >= 1f) { value.lastTickTime = time; HitData val = new HitData(); val.m_damage.m_slash = value.damagePerTick; Player source = value.source; val.m_attacker = ((source != null) ? ((Character)source).GetZDOID() : ZDOID.None); val.m_point = ((Component)key).transform.position; val.m_hitType = (HitType)7; key.Damage(val); } } foreach (Character item2 in list) { bleedTracking.Remove(item2); } } [HarmonyPatch(typeof(Player), "GetEquipmentMovementModifier")] [HarmonyPostfix] public static void Player_GetEquipmentMovementModifier_Executioner_Postfix(Player __instance, ref float __result) { try { if (!HasExecutionerPerk(__instance, 20)) { return; } ItemData currentWeapon = ((Humanoid)__instance).GetCurrentWeapon(); if (currentWeapon != null && ClassCombatManager.IsAxeWeapon(currentWeapon)) { float movementModifier = currentWeapon.m_shared.m_movementModifier; if (movementModifier < 0f) { __result -= movementModifier; } } } catch (Exception ex) { Logger.LogError((object)("Error in Player_GetEquipmentMovementModifier_Executioner_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(ItemData), "GetWeight")] [HarmonyPostfix] public static void ItemData_GetWeight_Executioner_Postfix(ItemData __instance, ref float __result) { try { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && HasExecutionerPerk(localPlayer, 20) && ClassCombatManager.IsAxeWeapon(__instance)) { __result *= 0.5f; } } catch (Exception ex) { Logger.LogError((object)("Error in ItemData_GetWeight_Executioner_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Humanoid), "StartAttack")] [HarmonyPrefix] public static void Humanoid_StartAttack_Executioner_Prefix(Humanoid __instance, bool secondaryAttack) { try { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); if (currentWeapon == null || !ClassCombatManager.IsAxeWeapon(currentWeapon)) { pendingTwoHandedSpecialAttack.Remove(val); } else { pendingTwoHandedSpecialAttack[val] = secondaryAttack && ClassCombatManager.IsTwoHandedWeapon(currentWeapon); } } } catch (Exception ex) { Logger.LogError((object)("Error in Humanoid_StartAttack_Executioner_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] public static void Character_Damage_Executioner_Prefix(Character __instance, ref HitData hit) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) try { if ((int)hit.m_skill == 0) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || (Object)(object)__instance == (Object)null || __instance is Player || !ClassCombatManager.IsAxeWeapon(((Humanoid)val).GetCurrentWeapon())) { return; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(val); if (playerData != null && playerData.IsClassActive(PlayerClass.Executioner)) { float num = 0f; if (HasExecutionerPerk(val, 30)) { num += GetRendingRhythmBonus(__instance); } if (HasExecutionerPerk(val, 50) && __instance.GetHealthPercentage() < 0.4f) { num += 0.1f; } if (num > 0f) { float num2 = 1f + num; hit.m_damage.m_slash *= num2; hit.m_damage.m_pierce *= num2; hit.m_damage.m_blunt *= num2; hit.m_damage.m_chop *= num2; } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Executioner_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPostfix] public static void Character_Damage_Executioner_Postfix(Character __instance, HitData hit) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { if ((int)hit.m_skill == 0) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && !((Object)(object)__instance == (Object)null) && !(__instance is Player) && !(hit.GetTotalDamage() <= 0f) && ClassCombatManager.IsAxeWeapon(((Humanoid)val).GetCurrentWeapon())) { if (HasExecutionerPerk(val, 30)) { bool value; bool applyAllStacks = pendingTwoHandedSpecialAttack.TryGetValue(val, out value) && value; AddRendingRhythmStack(__instance, applyAllStacks); } if (HasExecutionerPerk(val, 40)) { ApplyHemorrhage(val, __instance, hit.GetTotalDamage()); } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Executioner_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Game), "Update")] [HarmonyPostfix] public static void Game_Update_Executioner_Postfix() { try { ProcessHemorrhageTicks(); } catch (Exception ex) { Logger.LogError((object)("Error in Game_Update_Executioner_Postfix: " + ex.Message)); } } } public static class ClassCombatManager { private class PendingSummonCast { public long ownerPlayerID; public SkillType weaponSkill; public float time; public Vector3 position; } private class FallbackAttribution { public Player owner; public SkillType skill; } private class PoisonSource { public Character attacker; public float time; } private const float SUMMON_FALLBACK_TIME_WINDOW = 15f; private const float SUMMON_FALLBACK_RADIUS = 10f; private static readonly List<PendingSummonCast> pendingSummonCasts = new List<PendingSummonCast>(); private static readonly ConditionalWeakTable<Character, FallbackAttribution> fallbackCache = new ConditionalWeakTable<Character, FallbackAttribution>(); private const float POISON_SOURCE_WINDOW = 60f; private static readonly ConditionalWeakTable<Character, PoisonSource> poisonSourceCache = new ConditionalWeakTable<Character, PoisonSource>(); private static bool IsWeaponItemType(ItemData weapon) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if (weapon?.m_shared == null) { return false; } ItemType itemType = weapon.m_shared.m_itemType; if ((int)itemType != 3 && (int)itemType != 14 && (int)itemType != 22) { return (int)itemType == 4; } return true; } public static bool IsTwoHandedWeapon(ItemData weapon) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 if (weapon?.m_shared == null) { return false; } ItemType itemType = weapon.m_shared.m_itemType; if ((int)itemType != 14 && (int)itemType != 22) { return (int)itemType == 4; } return true; } public static bool IsSwordWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } return (int)weapon.m_shared.m_skillType == 1; } public static bool IsAxeWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } return (int)weapon.m_shared.m_skillType == 7; } public static bool IsBowWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } if ((int)weapon.m_shared.m_skillType != 8) { return (int)weapon.m_shared.m_skillType == 14; } return true; } public static bool IsBluntWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } return (int)weapon.m_shared.m_skillType == 3; } public static bool IsKnifeWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } return (int)weapon.m_shared.m_skillType == 2; } public static bool IsSpearWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } if ((int)weapon.m_shared.m_skillType != 5) { return (int)weapon.m_shared.m_skillType == 4; } return true; } public static bool IsPolearmWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } return (int)weapon.m_shared.m_skillType == 4; } public static bool IsUnarmedAttack(ItemData weapon) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (weapon != null && (int)weapon.m_shared.m_itemType != 0 && (int)weapon.m_shared.m_skillType != 11) { return weapon.m_shared.m_name == "Unarmed"; } return true; } public static bool IsSkillAppropriateForClass(SkillType skill, string className) { //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Invalid comparison between Unknown and I4 //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Invalid comparison between Unknown and I4 //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Invalid comparison between Unknown and I4 //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Invalid comparison between Unknown and I4 //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Invalid comparison between Unknown and I4 //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Invalid comparison between Unknown and I4 //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Invalid comparison between Unknown and I4 //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Invalid comparison between Unknown and I4 //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Invalid comparison between Unknown and I4 //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Invalid comparison between Unknown and I4 //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Invalid comparison between Unknown and I4 switch (className) { case "Sword Master": return (int)skill == 1; case "Executioner": return (int)skill == 7; case "Archer": if ((int)skill != 8) { return (int)skill == 14; } return true; case "Crusher": return (int)skill == 3; case "Assassin": return (int)skill == 2; case "Brawler": return (int)skill == 11; case "Wizard": return (int)skill == 9; case "Warlock": return (int)skill == 10; case "Lancer": if ((int)skill != 5) { return (int)skill == 4; } return true; case "Bulwark": return true; default: return false; } } public static bool IsMagicWeapon(ItemData weapon) { if (!IsElementalMagicWeapon(weapon)) { return IsBloodMagicWeapon(weapon); } return true; } public static Player GetCommandingPlayer(Character character) { MonsterAI val = ((character != null) ? ((Component)character).GetComponent<MonsterAI>() : null); if ((Object)(object)val != (Object)null) { GameObject followTarget = val.GetFollowTarget(); Player val2 = ((followTarget != null) ? followTarget.GetComponent<Player>() : null); if ((Object)(object)val2 != (Object)null) { return val2; } } return ResolveFallback(character)?.owner; } public static SkillType GetCommandedSummonSkill(Character character) { //IL_0017: 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_001f: Unknown result type (might be due to invalid IL or missing references) Tameable val = ((character != null) ? ((Component)character).GetComponent<Tameable>() : null); if ((Object)(object)val != (Object)null && (int)val.m_levelUpOwnerSkill != 0) { return val.m_levelUpOwnerSkill; } return (SkillType)(((??)ResolveFallback(character)?.skill) ?? 0); } public static void RecordSummonCast(Player owner, Vector3 position, SkillType weaponSkill) { //IL_0017: 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_0029: 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) pendingSummonCasts.Add(new PendingSummonCast { ownerPlayerID = owner.GetPlayerID(), weaponSkill = weaponSkill, time = Time.time, position = position }); } private static FallbackAttribution ResolveFallback(Character character) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null) { return null; } if (fallbackCache.TryGetValue(character, out var value)) { return value; } if (pendingSummonCasts.Count == 0) { return null; } if (character.IsMonsterFaction(0f)) { return null; } Vector3 position = ((Component)character).transform.position; for (int num = pendingSummonCasts.Count - 1; num >= 0; num--) { PendingSummonCast cast = pendingSummonCasts[num]; if (Time.time - cast.time > 15f) { pendingSummonCasts.RemoveAt(num); } else if (Vector3.Distance(cast.position, position) <= 10f) { Player val = ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player p) => p.GetPlayerID() == cast.ownerPlayerID)); if ((Object)(object)val == (Object)null) { return null; } FallbackAttribution fallbackAttribution = new FallbackAttribution { owner = val, skill = cast.weaponSkill }; fallbackCache.Add(character, fallbackAttribution); return fallbackAttribution; } } return null; } public static void RecordPoisonSource(Character target, Character attacker) { if (!((Object)(object)target == (Object)null) && !((Object)(object)attacker == (Object)null)) { if (poisonSourceCache.TryGetValue(target, out var value)) { value.attacker = attacker; value.time = Time.time; } else { poisonSourceCache.Add(target, new PoisonSource { attacker = attacker, time = Time.time }); } } } public static Character GetRecentPoisonAttacker(Character target) { if ((Object)(object)target != (Object)null && poisonSourceCache.TryGetValue(target, out var value) && Time.time - value.time <= 60f) { return value.attacker; } return null; } public static bool IsElementalMagicWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } return (int)weapon.m_shared.m_skillType == 9; } public static bool IsBloodMagicWeapon(ItemData weapon) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if (!IsWeaponItemType(weapon)) { return false; } return (int)weapon.m_shared.m_skillType == 10; } public static float GetClassDamageMultiplier(Player player, ItemData weapon) { if ((Object)(object)player == (Object)null) { return 1f; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData == null || playerData.activeClasses.Count == 0) { return 1f; } float num = 1f; foreach (string activeClass in playerData.activeClasses) { float classSpecificDamageMultiplier = GetClassSpecificDamageMultiplier(activeClass, playerData, weapon); if (classSpecificDamageMultiplier > 1f) { num += classSpecificDamageMultiplier - 1f; } } return num; } private static float GetClassSpecificDamageMultiplier(string className, PlayerClassData playerData, ItemData weapon) { int classLevel = playerData.GetClassLevel(className); return className switch { "Sword Master" => GetSwordMasterDamageBonus(classLevel, weapon), "Executioner" => GetExecutionerDamageBonus(classLevel, weapon), "Archer" => GetArcherDamageBonus(classLevel, weapon), "Crusher" => GetCrusherDamageBonus(classLevel, weapon), "Assassin" => GetAssassinDamageBonus(classLevel, weapon), "Brawler" => GetBrawlerDamageBonus(classLevel, weapon), "Wizard" => GetWizardDamageBonus(classLevel, weapon), "Warlock" => GetWarlockDamageBonus(classLevel, weapon), "Lancer" => GetLancerDamageBonus(classLevel, weapon), "Bulwark" => GetBulwarkDamageBonus(classLevel, weapon), _ => 1f, }; } private static float GetSwordMasterDamageBonus(int level, ItemData weapon) { if (!IsSwordWeapon(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.07f; } return 1f + num; } private static float GetExecutionerDamageBonus(int level, ItemData weapon) { if (!IsAxeWeapon(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.08f; } if (level >= 50) { num += 0.15f; } return 1f + num; } private static float GetArcherDamageBonus(int level, ItemData weapon) { if (!IsBowWeapon(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.07f; } if (level >= 50) { num += 0.15f; } return 1f + num; } private static float GetCrusherDamageBonus(int level, ItemData weapon) { if (!IsBluntWeapon(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.08f; } if (level >= 50) { num += 0.15f; } return 1f + num; } private static float GetAssassinDamageBonus(int level, ItemData weapon) { if (!IsKnifeWeapon(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.07f; } return 1f + num; } private static float GetBrawlerDamageBonus(int level, ItemData weapon) { if (!IsUnarmedAttack(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.08f; } return 1f + num; } private static float GetWizardDamageBonus(int level, ItemData weapon) { if (!IsElementalMagicWeapon(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.07f; } return 1f + num; } private static float GetWarlockDamageBonus(int level, ItemData weapon) { if (!IsBloodMagicWeapon(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.07f; } return 1f + num; } private static float GetLancerDamageBonus(int level, ItemData weapon) { if (!IsSpearWeapon(weapon)) { return 1f; } float num = 0f; if (level >= 10) { num += 0.08f; } if (level >= 50) { num += 0.15f; } return 1f + num; } private static float GetBulwarkDamageBonus(int level, ItemData weapon) { return 1f; } public static float GetClassSpecificDamageMultiplierPublic(string className, PlayerClassData playerData, ItemData weapon) { int classLevel = playerData.GetClassLevel(className); return className switch { "Sword Master" => GetSwordMasterDamageBonus(classLevel, weapon), "Executioner" => GetExecutionerDamageBonus(classLevel, weapon), "Archer" => GetArcherDamageBonus(classLevel, weapon), "Crusher" => GetCrusherDamageBonus(classLevel, weapon), "Assassin" => GetAssassinDamageBonus(classLevel, weapon), "Brawler" => GetBrawlerDamageBonus(classLevel, weapon), "Wizard" => GetWizardDamageBonus(classLevel, weapon), "Warlock" => GetWarlockDamageBonus(classLevel, weapon), "Lancer" => GetLancerDamageBonus(classLevel, weapon), "Bulwark" => GetBulwarkDamageBonus(classLevel, weapon), _ => 1f, }; } } [HarmonyPatch] public static class CombatPatches { [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] public static void Character_Damage_Prefix(Character __instance, ref HitData hit) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) try { if ((int)hit.m_skill == 0) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); float classDamageMultiplier = ClassCombatManager.GetClassDamageMultiplier(val, currentWeapon); if (classDamageMultiplier > 1f) { hit.m_damage.m_slash *= classDamageMultiplier; hit.m_damage.m_pierce *= classDamageMultiplier; hit.m_damage.m_blunt *= classDamageMultiplier; hit.m_damage.m_chop *= classDamageMultiplier; } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(SpawnAbility), "Setup")] [HarmonyPostfix] public static void SpawnAbility_Setup_Postfix(Character owner) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) try { Player val = (Player)(object)((owner is Player) ? owner : null); if (val != null) { SkillType valueOrDefault = (((Humanoid)val).GetCurrentWeapon()?.m_shared?.m_skillType).GetValueOrDefault(); ClassCombatManager.RecordSummonCast(val, ((Component)val).transform.position, valueOrDefault); } } catch (Exception ex) { Logger.LogError((object)("Error in SpawnAbility_Setup_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] public static void Character_Damage_PoisonSource_Prefix(Character __instance, HitData hit) { try { if (!((Object)(object)__instance == (Object)null) && !(hit.m_damage.m_poison <= 0f)) { Character attacker = hit.GetAttacker(); if (!((Object)(object)attacker == (Object)null)) { ClassCombatManager.RecordPoisonSource(__instance, attacker); } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_PoisonSource_Prefix: " + ex.Message)); } } } [HarmonyPatch] public static class ProjectilePatches { private class ProjectileInfo { public Player owner; public ItemData weapon; public float createdTime; public string projectileName; } private static Dictionary<int, ProjectileInfo> trackedProjectiles = new Dictionary<int, ProjectileInfo>(); [HarmonyPatch(typeof(Projectile), "Setup")] [HarmonyPostfix] public static void Projectile_Setup_Postfix(Projectile __instance, Character owner, Vector3 velocity, float hitNoise, HitData hitData, ItemData item) { try { Player val = (Player)(object)((owner is Player) ? owner : null); if (val != null && (Object)(object)__instance != (Object)null) { ItemData weaponFromProjectileContext = GetWeaponFromProjectileContext(val, __instance, item); ProjectileInfo value = new ProjectileInfo { owner = val, weapon = weaponFromProjectileContext, createdTime = Time.time, projectileName = ((Object)__instance).name }; trackedProjectiles[((Object)__instance).GetInstanceID()] = value; Debug.Log((object)("Registered projectile " + ((Object)__instance).name + " for " + val.GetPlayerName() + " with weapon type " + (weaponFromProjectileContext?.m_shared?.m_name ?? "unknown"))); } } catch (Exception ex) { Logger.LogError((object)("Error in Projectile_Setup_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Projectile), "Awake")] [HarmonyPostfix] public static void Projectile_Awake_Postfix(Projectile __instance) { try { if ((Object)(object)__instance != (Object)null && !trackedProjectiles.ContainsKey(((Object)__instance).GetInstanceID())) { ProjectileInfo value = new ProjectileInfo { owner = null, weapon = null, createdTime = Time.time, projectileName = ((Object)__instance).name }; trackedProjectiles[((Object)__instance).GetInstanceID()] = value; } } catch (Exception ex) { Logger.LogError((object)("Error in Projectile_Awake_Postfix: " + ex.Message)); } } private static ItemData GetWeaponFromProjectileContext(Player player, Projectile projectile, ItemData item) { //IL_007c: 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_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown if (item != null) { return item; } if ((Object)(object)player == (Object)null || (Object)(object)projectile == (Object)null) { return null; } string text = ((Object)projectile).name.ToLower(); if (text.Contains("arrow")) { return ((IEnumerable<ItemData>)((Humanoid)player).GetInventory().GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => (int)i.m_shared.m_itemType == 4)); } if (text.Contains("spear") || text.Contains("harpoon")) { return new ItemData { m_shared = new SharedData(), m_shared = { m_itemType = (ItemType)14 }, m_shared = { m_name = "spear" } }; } return ((Humanoid)player).GetCurrentWeapon(); } private static Player ResolveProjectileOwner(Projectile projectile) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) try { ZNetView component = ((Component)projectile).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { long ownerID = component.GetZDO().GetLong("owner", 0L); if (ownerID != 0L) { return ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player p) => p.GetPlayerID() == ownerID)); } } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && Vector3.Distance(((Component)localPlayer).transform.position, ((Component)projectile).transform.position) < 50f) { return localPlayer; } return null; } catch { return null; } } [HarmonyPatch(typeof(Game), "Update")] [HarmonyPostfix] public static void Game_Update_Postfix() { try { if (Time.frameCount % 300 != 0) { return; } List<int> list = new List<int>(); float time = Time.time; foreach (KeyValuePair<int, ProjectileInfo> trackedProjectile in trackedProjectiles) { if (time - trackedProjectile.Value.createdTime > 30f) { list.Add(trackedProjectile.Key); } } foreach (int item in list) { trackedProjectiles.Remove(item); } } catch (Exception ex) { Logger.LogError((object)("Error in Game_Update_Postfix (projectile cleanup): " + ex.Message)); } } } [BepInPlugin("com.bunzboi.valheimweaponclass", "ValheimWeaponClasses", "1.1.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] internal class ClassObeliskMod : BaseUnityPlugin { public const string PluginGUID = "com.bunzboi.valheimweaponclass"; public const string PluginName = "ValheimWeaponClasses"; public const string PluginVersion = "1.1.3"; private GameObject TestPanel; private static ClassObeliskMod _instance; private Harmony _harmony; private ConfigEntry<bool> _enableMod; public static ClassObeliskMod Instance => _instance; private void Awake() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown _instance = this; _enableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableMod", true, "Enable the class obelisk mod"); if (_enableMod.Value) { _harmony = new Harmony("com.bunzboi.valheimweaponclass"); _harmony.PatchAll(); XPConfigManager.Initialize(); PrefabManager.OnVanillaPrefabsAvailable += AddClassObelisk; ((BaseUnityPlugin)this).Logger.LogInfo((object)"ValheimWeaponClasses loaded successfully!"); } } private void AddClassObelisk() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown try { GameObject val = CreateClassObeliskPrefab(); if ((Object)(object)val != (Object)null) { PieceConfig val2 = new PieceConfig(); val2.Name = "$class_obelisk"; val2.PieceTable = "Hammer"; val2.Category = "Crafting"; val2.AddRequirement("Wood", 2, true); CustomPiece val3 = new CustomPiece(val, true, val2); PieceManager.Instance.AddPiece(val3); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Class Obelisk piece added successfully!"); PrefabManager.OnVanillaPrefabsAvailable -= AddClassObelisk; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Error adding Class Obelisk: " + ex.Message)); } } private GameObject CreateClassObeliskPrefab() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) try { GameObject prefab = PrefabManager.Instance.GetPrefab("piece_workbench"); if ((Object)(object)prefab == (Object)null) { ((BaseUnityPlugin)this).Logger.LogError((object)"Could not find workbench prefab!"); return null; } GameObject obj = PrefabManager.Instance.CreateClonedPrefab("ClassObelisk", prefab); new PieceConfig { Name = "$class_obelisk", PieceTable = "Hammer", Category = "Crafting" }.AddRequirement("Wood", 2, true); CraftingStation component = obj.GetComponent<CraftingStation>(); if ((Object)(object)component != (Object)null) { if ((Object)(object)component.m_areaMarker != (Object)null) { component.m_areaMarker.SetActive(false); } if ((Object)(object)component.m_inUseObject != (Object)null) { component.m_inUseObject.SetActive(false); } if ((Object)(object)component.m_haveFireObject != (Object)null) { component.m_haveFireObject.SetActive(false); } Object.DestroyImmediate((Object)(object)component); } obj.AddComponent<ClassObeliskInteract>(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Class Obelisk prefab created successfully!"); return obj; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Error creating Class Obelisk prefab: " + ex.Message)); return null; } } } public class ClassObeliskInteract : MonoBehaviour, Hoverable, Interactable { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static UnityAction <>9__30_0; internal void <CreateEnhancedCloseButton>b__30_0() { CloseClassSelectionGUI(); } } public static GameObject classSelectionPanel; public static Text descriptionText; public static ScrollRect descriptionScrollRect; public static Dictionary<string, GameObject> classButtons = new Dictionary<string, GameObject>(); public static GameObject activationButton; public static Text limitHintText; public static string selectedClassName = ""; private static readonly Color ActiveClassColor = new Color(0.45f, 0.85f, 0.45f, 1f); private static readonly Color InactiveClassColor = Color.white; public static readonly string[] ClassNames = new string[10] { "Sword Master", "Archer", "Crusher", "Assassin", "Brawler", "Wizard", "Lancer", "Bulwark", "Executioner", "Warlock" }; private static readonly Dictionary<string, Func<Player, string>> DynamicClassDescriptionProviders = new Dictionary<string, Func<Player, string>> { { "Sword Master", SwordMasterPerkManager.GetClassDescription }, { "Archer", ArcherPerkManager.GetClassDescription }, { "Crusher", CrusherPerkManager.GetClassDescription }, { "Assassin", AssassinPerkManager.GetClassDescription }, { "Brawler", BrawlerPerkManager.GetClassDescription }, { "Wizard", WizardPerkManager.GetClassDescription }, { "Lancer", LancerPerkManager.GetClassDescription }, { "Bulwark", BulwarkPerkManager.GetClassDescription }, { "Executioner", ExecutionerPerkManager.GetClassDescription }, { "Warlock", WarlockPerkManager.GetClassDescription } }; private void Start() { } private void Update() { if ((Object)(object)classSelectionPanel != (Object)null && classSelectionPanel.activeSelf && ZInput.instance != null && (ZInput.GetButtonDown("Escape") || ZInput.GetButtonDown("JoyMenu"))) { CloseClassSelectionGUI(); } } public string GetHoverText() { return "Class Obelisk\n[<color=yellow>E</color>] Select your combat class"; } public string GetHoverName() { return "Class Obelisk"; } public float GetHoverOffset() { return 0f; } public bool Interact(Humanoid user, bool hold, bool alt) { if (hold || alt) { return false; } Player val = (Player)(object)((user is Player) ? user : null); if ((Object)(object)val == (Object)null) { return false; } OpenClassSelectionGUI(val); return true; } public bool UseItem(Humanoid user, ItemData item) { return false; } private void OpenClassSelectionGUI(Player player) { CloseClassSelectionGUI(); CreateEnhancedClassSelectionGUI(player); } private void CreateEnhancedClassSelectionGUI(Player player) { try { PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData == null) { Debug.LogError((object)"Could not get player data for GUI creation"); return; } CreateWoodenGUIPanel(); CreateClassButtonGrid(player); CreateDescriptionWindow(player); CreateActivationButton(player); CreateEnhancedCloseButton(); string text = ((playerData.activeClasses.Count > 0) ? string.Join(", ", playerData.activeClasses) : "None"); string text2 = (playerData.CanSelectSecondClass() ? "Click a class to see its description, then Activate/Deactivate below (up to 2 active)." : "Click a class to see its description, then Activate/Deactivate below. Reach level 50 to unlock a second active class."); UpdateDescriptionText("Current Active Classes: " + text + "\n\n" + text2); selectedClassName = ""; UpdateActivationButton(player); } catch (Exception ex) { Debug.LogError((object)("Error creating enhanced GUI: " + ex.Message)); } } private void CreateWoodenGUIPanel() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)classSelectionPanel)) { if (GUIManager.Instance == null) { Logger.LogError((object)"GUIManager instance is null"); return; } if (!Object.op_Implicit((Object)(object)GUIManager.CustomGUIFront)) { Logger.LogError((object)"GUIManager CustomGUI is null"); return; } classSelectionPanel = GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 0f), 1450f, 900f, false); classSelectionPanel.SetActive(false); classSelectionPanel.AddComponent<DragWindowCntrl>(); GUIManager.Instance.CreateText("Choose a class!", classSelectionPanel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -50f), GUIManager.Instance.AveriaSerifBold, 30, GUIManager.Instance.ValheimOrange, true, Color.black, 400f, 50f, false).GetComponent<Text>().alignment = (TextAnchor)4; } bool flag = !classSelectionPanel.activeSelf; classSelectionPanel.SetActive(flag); GUIManager.BlockInput(flag); } private void CreateClassButtonGrid(Player player) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0032: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("ButtonContainer"); val.transform.SetParent(classSelectionPanel.transform, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = new Vector2(0.05f, 0.65f); obj.anchorMax = new Vector2(0.95f, 0.85f); obj.offsetMin = Vector2.zero; obj.offsetMax = Vector2.zero; float num = 210f; float num2 = 48f; float num3 = 25f; float num4 = 16f; int num5 = Mathf.CeilToInt((float)ClassNames.Length / 5f); float num6 = (0f - (num * 5f + num3 * 4f)) / 2f + num / 2f; float num7 = (num2 + num4) * (float)(num5 - 1) / 2f; for (int i = 0; i < ClassNames.Length; i++) { int num8 = i % 5; int num9 = i / 5; float x = num6 + (float)num8 * (num + num3); float y = num7 - (float)num9 * (num2 + num4); CreateEnhancedClassButton(val, ClassNames[i], player, x, y, num, num2); } } private void CreateEnhancedClassButton(GameObject parent, string className, Player player, float x, float y, float width, float height) { //IL_005f: 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_0077: 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) //IL_00a6: Expected O, but got Unknown int classLevel = PlayerClassManager.GetPlayerData(player).GetClassLevel(className); string text = $"{className} ({classLevel})"; GameObject val = GUIManager.Instance.CreateButton(text, parent.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(x, y), (float)(int)width, (float)(int)height); ((UnityEvent)val.GetComponent<Button>().onClick).AddListener((UnityAction)delegate { OnClassButtonClicked(className, player); }); val.AddComponent<ButtonHoverEffect>(); classButtons[className] = val; ApplyClassButtonHighlight(className, player); } private void ApplyClassButtonHighlight(string className, Player player) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_0087: Unknown result type (might be due to invalid IL or missing references) if (classButtons.TryGetValue(className, out var value) && !((Object)(object)value == (Object)null)) { PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); bool flag = playerData?.IsClassActive(className) ?? false; int num = playerData?.GetClassLevel(className) ?? 0; Button component = value.GetComponent<Button>(); Image component2 = value.GetComponent<Image>(); Text componentInChildren = value.GetComponentInChildren<Text>(); Color val = (flag ? ActiveClassColor : InactiveClassColor); ColorBlock colors = ((Selectable)component).colors; ((ColorBlock)(ref colors)).normalColor = val; ((Selectable)component).colors = colors; if ((Object)(object)component2 != (Object)null) { ((Graphic)component2).color = val; } if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = string.Format("{0} ({1}){2}", className, num, flag ? " [Active]" : ""); } } } private void RefreshClassButtonHighlights(Player player) { string[] classNames = ClassNames; foreach (string className in classNames) { ApplyClassButtonHighlight(className, player); } } private void CreateDescriptionWindow(Player player) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0032: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: 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_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("DescriptionContainer"); val.transform.SetParent(classSelectionPanel.transform, false); RectTransform obj = val.AddComponent<RectTransform>(); obj.anchorMin = new Vector2(0.15f, 0.2f); obj.anchorMax = new Vector2(0.85f, 0.6f); obj.offsetMin = Vector2.zero; obj.offsetMax = Vector2.zero; ((Graphic)val.AddComponent<Image>()).color = new Color(0.1f, 0.1f, 0.1f, 0.8f); GameObject val2 = new GameObject("Viewport"); val2.transform.SetParent(val.transform, false); RectTransform val3 = val2.AddComponent<RectTransform>(); val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; val3.offsetMin = new Vector2(15f, 15f); val3.offsetMax = new Vector2(-15f, -15f); val2.AddComponent<RectMask2D>(); GameObject obj2 = GUIManager.Instance.CreateText("Select a class to view its description...", val2.transform, Vector2.zero, Vector2.one, Vector2.zero, GUIManager.Instance.AveriaSerif, 18, new Color(0.9f, 0.9f, 0.9f, 1f), false, Color.black, 0f, 0f, false); descriptionText = obj2.GetComponent<Text>(); descriptionText.alignment = (TextAnchor)0; descriptionText.horizontalOverflow = (HorizontalWrapMode)0; descriptionText.verticalOverflow = (VerticalWrapMode)1; RectTransform component = obj2.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(1f, 1f); component.pivot = new Vector2(0.5f, 1f); component.anchoredPosition = Vector2.zero; component.sizeDelta = Vector2.zero; ContentSizeFitter obj3 = obj2.AddComponent<ContentSizeFitter>(); obj3.horizontalFit = (FitMode)0; obj3.verticalFit = (FitMode)2; descriptionScrollRect = val.AddComponent<ScrollRect>(); descriptionScrollRect.viewport = val3; descriptionScrollRect.content = component; descriptionScrollRect.horizontal = false; descriptionScrollRect.vertical = true; descriptionScrollRect.movementType = (MovementType)2; descriptionScrollRect.scrollSensitivity = 20f; } private void CreateActivationButton(Player player) { //IL_0032: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) activationButton = GUIManager.Instance.CreateButton("Select a Class", classSelectionPanel.transform, new Vector2(0.5f, 0.08f), new Vector2(0.5f, 0.08f), new Vector2(0f, 20f), 200f, 35f); ((UnityEvent)activationButton.GetComponent<Button>().onClick).AddListener((UnityAction)delegate { OnActivationButtonClicked(player); }); limitHintText = GUIManager.Instance.CreateText("", classSelectionPanel.transform, new Vector2(0.5f, 0.08f), new Vector2(0.5f, 0.08f), new Vector2(0f, -20f), GUIManager.Instance.AveriaSerif, 16, new Color(0.85f, 0.45f, 0.35f, 1f), false, Color.black, 600f, 30f, false).GetComponent<Text>(); limitHintText.alignment = (TextAnchor)4; limitHintText.fontStyle = (FontStyle)2; UpdateActivationButton(player); } private void OnActivationButtonClicked(Player player) { if (string.IsNullOrEmpty(selectedClassName)) { return; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData != null) { if (playerData.IsClassActive(selectedClassName)) { PlayerClassManager.DeactivateClass(player, selectedClassName); } else { PlayerClassManager.ActivateClass(player, selectedClassName); } OnClassButtonClicked(selectedClassName, player); RefreshClassButtonHighlights(player); } } private void UpdateActivationButton(Player player) { if ((Object)(object)activationButton == (Object)null) { return; } Button component = activationButton.GetComponent<Button>(); Text componentInChildren = activationButton.GetComponentInChildren<Text>(); PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (string.IsNullOrEmpty(selectedClassName) || playerData == null) { ((Selectable)component).interactable = false; if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = "Select a Class"; } SetLimitHintVisible(visible: false); return; } if (playerData.IsClassActive(selectedClassName)) { ((Selectable)component).interactable = true; if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = "Deactivate"; } SetLimitHintVisible(visible: false); return; } bool num = playerData.activeClasses.Count < playerData.GetMaxActiveClasses(); bool flag = playerData.GetMaxActiveClasses() == 1; bool flag2 = num || flag; ((Selectable)component).interactable = flag2; if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = "Activate"; } SetLimitHintVisible(!flag2); } private void SetLimitHintVisible(bool visible) { if (!((Object)(object)limitHintText == (Object)null)) { limitHintText.text = (visible ? "Class limit reached - de-activate another class before picking a new one." : ""); } } private void CreateEnhancedCloseButton() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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) //IL_00bc: 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) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown GameObject obj = GUIManager.Instance.CreateButton("X", classSelectionPanel.transform, new Vector2(0.92f, 0.92f), new Vector2(0.98f, 0.98f), Vector2.zero, 35f, 35f); Button component = obj.GetComponent<Button>(); Image component2 = obj.GetComponent<Image>(); if ((Object)(object)component2 != (Object)null) { ((Graphic)component2).color = new Color(0.8f, 0.3f, 0.3f, 0.9f); ColorBlock colors = ((Selectable)component).colors; ((ColorBlock)(ref colors)).normalColor = new Color(0.8f, 0.3f, 0.3f, 0.9f); ((ColorBlock)(ref colors)).highlightedColor = new Color(0.9f, 0.4f, 0.4f, 1f); ((ColorBlock)(ref colors)).pressedColor = new Color(0.7f, 0.2f, 0.2f, 1f); ((Selectable)component).colors = colors; } Text componentInChildren = obj.GetComponentInChildren<Text>(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.fontSize = 18; componentInChildren.fontStyle = (FontStyle)1; ((Graphic)componentInChildren).color = Color.white; } ButtonClickedEvent onClick = component.onClick; object obj2 = <>c.<>9__30_0; if (obj2 == null) { UnityAction val = delegate { CloseClassSelectionGUI(); }; <>c.<>9__30_0 = val; obj2 = (object)val; } ((UnityEvent)onClick).AddListener((UnityAction)obj2); } private void OnClassButtonClicked(string className, Player player) { selectedClassName = className; Func<Player, string> value; string text = (DynamicClassDescriptionProviders.TryGetValue(className, out value) ? value(player) : ("Description for " + className + " coming soon...")); PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData != null && playerData.IsClassActive(className)) { text += "\n\n\n<size=24><b>Active</b></size>"; } UpdateDescriptionText(text); UpdateActivationButton(player); } private void UpdateDescriptionText(string description) { if ((Object)(object)descriptionText != (Object)null) { descriptionText.text = description; descriptionText.horizontalOverflow = (HorizontalWrapMode)0; descriptionText.verticalOverflow = (VerticalWrapMode)1; LayoutRebuilder.ForceRebuildLayoutImmediate(((Graphic)descriptionText).rectTransform); Canvas.ForceUpdateCanvases(); if ((Object)(object)descriptionScrollRect != (Object)null) { descriptionScrollRect.verticalNormalizedPosition = 1f; } } else { Debug.LogError((object)"descriptionText is null when trying to update!"); } } private static void CloseClassSelectionGUI() { if ((Object)(object)classSelectionPanel != (Object)null) { Object.Destroy((Object)(object)classSelectionPanel); classSelectionPanel = null; descriptionText = null; descriptionScrollRect = null; classButtons.Clear(); activationButton = null; limitHintText = null; selectedClassName = ""; if (GUIManager.Instance != null) { GUIManager.BlockInput(false); } } } } public class ButtonHoverEffect : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler { private Vector3 originalScale; private bool isHovering; private void Start() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) originalScale = ((Component)this).transform.localScale; } public void OnPointerEnter(PointerEventData eventData) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (!isHovering) { isHovering = true; ((Component)this).transform.localScale = originalScale * 1.05f; } } public void OnPointerExit(PointerEventData eventData) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (isHovering) { isHovering = false; ((Component)this).transform.localScale = originalScale; } } } public static class CrusherPerkManager { private const string Intro = "Heavy blunt weapon specialists who break armor, shake crowds, and turn slow weapons into decisive hits."; private const string Outro = "Best for players who want both maces and two-handed hammers to receive a meaningful version of each perk tier."; public static readonly List<PerkInfo> Perks = new List<PerkInfo> { new PerkInfo { RequiredLevel = 10, Name = "Bonebreaker", Description = "+8% blunt damage with clubs, maces, and two-handed hammers." }, new PerkInfo { RequiredLevel = 20, Name = "Colossus", Description = "Blunt weapons weigh 50% less and impose no movement speed penalties." }, new PerkInfo { RequiredLevel = 30, Name = "Thundering Blows", Description = "One-handed mace heavy attacks create a 3m shockwave for 25% weapon damage. Two-handed hammer attacks create a 3m aftershock on direct enemy hits for 20% weapon damage." }, new PerkInfo { RequiredLevel = 40, Name = "Cold Steel", Description = "Blunt attacks deal bonus frost damage equal to 12% of weapon damage." }, new PerkInfo { RequiredLevel = 50, Name = "Earthshaker", Description = "+15% blunt damage. Staggering an enemy grants +10% attack speed with blunt weapons for 5s." } }; public const float THUNDERING_BLOWS_RADIUS = 3f; public const float THUNDERING_BLOWS_1H_PERCENT = 0.25f; public const float THUNDERING_BLOWS_2H_PERCENT = 0.2f; private static readonly HashSet<Player> thunderingBlowsInProgress = new HashSet<Player>(); public const float COLD_STEEL_FROST_PERCENT = 0.12f; public const float EARTHSHAKER_ATTACK_SPEED = 1.1f; public const float EARTHSHAKER_DURATION = 5f; public const string EARTHSHAKER_AS_KEY = "Crusher_Earthshaker_AS"; private static readonly Dictionary<Player, float> earthshakerExpire = new Dictionary<Player, float>(); public static bool HasCrusherPerk(Player player, int requiredLevel) { if ((Object)(object)player == (Object)null) { return false; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData == null || !playerData.IsClassActive(PlayerClass.Crusher)) { return false; } return playerData.GetClassLevel(PlayerClass.Crusher) >= requiredLevel; } public static string GetClassDescription(Player player) { int currentLevel = PlayerClassManager.GetPlayerData(player)?.GetClassLevel(PlayerClass.Crusher) ?? 0; return PerkDescriptionBuilder.Build("Heavy blunt weapon specialists who break armor, shake crowds, and turn slow weapons into decisive hits.", Perks, "Best for players who want both maces and two-handed hammers to receive a meaningful version of each perk tier.", currentLevel); } public static void TriggerThunderingBlows(Player player, Vector3 hitPoint, float weaponDamage, float percent) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00db: 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_00e8: Unknown result type (might be due to invalid IL or missing references) if (!thunderingBlowsInProgress.Add(player)) { return; } try { List<Character> list = (from c in Physics.OverlapSphere(hitPoint, 3f) select ((Component)c).GetComponent<Character>() into c where (Object)(object)c != (Object)null && (Object)(object)c != (Object)(object)player && !c.IsDead() && c.IsMonsterFaction(0f) select c).ToList(); if (list.Count == 0) { return; } float blunt = weaponDamage * percent; foreach (Character item in list) { HitData val = new HitData(); val.m_attacker = ((Character)player).GetZDOID(); val.m_damage.m_blunt = blunt; val.m_point = ((Component)item).transform.position; Vector3 val2 = ((Component)item).transform.position - hitPoint; val.m_dir = ((Vector3)(ref val2)).normalized; val.m_skill = (SkillType)3; item.Damage(val); } } finally { thunderingBlowsInProgress.Remove(player); } } public static void ApplyColdSteelFrost(ref HitData hit, float weaponDamage) { hit.m_damage.m_frost += weaponDamage * 0.12f; } public static void TriggerEarthshaker(Player player) { AnimationSpeedManager.Set((Character)(object)player, "Crusher_Earthshaker_AS", 1.1f); earthshakerExpire[player] = Time.time + 5f; } public static void ProcessEarthshakerExpiry() { List<Player> list = new List<Player>(); foreach (KeyValuePair<Player, float> item in earthshakerExpire) { if ((Object)(object)item.Key == (Object)null || Time.time >= item.Value) { if ((Object)(object)item.Key != (Object)null) { AnimationSpeedManager.Clear((Character)(object)item.Key, "Crusher_Earthshaker_AS"); } list.Add(item.Key); } } foreach (Player item2 in list) { earthshakerExpire.Remove(item2); } } } [HarmonyPatch] public static class CrusherPerkPatches { private static readonly Dictionary<Player, bool> pendingHeavyAttack = new Dictionary<Player, bool>(); [HarmonyPatch(typeof(Humanoid), "StartAttack")] [HarmonyPrefix] public static void Humanoid_StartAttack_Crusher_Prefix(Humanoid __instance, bool secondaryAttack) { try { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); if (currentWeapon == null || !ClassCombatManager.IsBluntWeapon(currentWeapon)) { pendingHeavyAttack.Remove(val); } else { pendingHeavyAttack[val] = secondaryAttack; } } } catch (Exception ex) { Logger.LogError((object)("Error in Humanoid_StartAttack_Crusher_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] public static void Character_Damage_Crusher_Prefix(Character __instance, ref HitData hit, out bool __state) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) __state = false; try { if ((int)hit.m_skill == 0) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && !((Object)(object)__instance == (Object)null) && !(__instance is Player) && ClassCombatManager.IsBluntWeapon(((Humanoid)val).GetCurrentWeapon()) && CrusherPerkManager.HasCrusherPerk(val, 1)) { __state = __instance.IsStaggering(); if (CrusherPerkManager.HasCrusherPerk(val, 40)) { CrusherPerkManager.ApplyColdSteelFrost(ref hit, hit.GetTotalDamage()); } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Crusher_Prefix: " + ex.Message)); } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPostfix] public static void Character_Damage_Crusher_Postfix(Character __instance, HitData hit, bool __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) try { if ((int)hit.m_skill == 0) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || (Object)(object)__instance == (Object)null || __instance is Player || hit.GetTotalDamage() <= 0f) { return; } ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); if (!ClassCombatManager.IsBluntWeapon(currentWeapon)) { return; } if (CrusherPerkManager.HasCrusherPerk(val, 30)) { bool value; if (ClassCombatManager.IsTwoHandedWeapon(currentWeapon)) { CrusherPerkManager.TriggerThunderingBlows(val, hit.m_point, hit.GetTotalDamage(), 0.2f); } else if (pendingHeavyAttack.TryGetValue(val, out value) && value) { CrusherPerkManager.TriggerThunderingBlows(val, hit.m_point, hit.GetTotalDamage(), 0.25f); } } if (CrusherPerkManager.HasCrusherPerk(val, 50) && !__state && __instance.IsStaggering()) { CrusherPerkManager.TriggerEarthshaker(val); } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Crusher_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Player), "GetEquipmentMovementModifier")] [HarmonyPostfix] public static void Player_GetEquipmentMovementModifier_Crusher_Postfix(Player __instance, ref float __result) { try { if (!CrusherPerkManager.HasCrusherPerk(__instance, 20)) { return; } ItemData currentWeapon = ((Humanoid)__instance).GetCurrentWeapon(); if (currentWeapon != null && ClassCombatManager.IsBluntWeapon(currentWeapon)) { float movementModifier = currentWeapon.m_shared.m_movementModifier; if (movementModifier < 0f) { __result -= movementModifier; } } } catch (Exception ex) { Logger.LogError((object)("Error in Player_GetEquipmentMovementModifier_Crusher_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(ItemData), "GetWeight")] [HarmonyPostfix] public static void ItemData_GetWeight_Crusher_Postfix(ItemData __instance, ref float __result) { try { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && CrusherPerkManager.HasCrusherPerk(localPlayer, 20) && ClassCombatManager.IsBluntWeapon(__instance)) { __result *= 0.5f; } } catch (Exception ex) { Logger.LogError((object)("Error in ItemData_GetWeight_Crusher_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Game), "Update")] [HarmonyPostfix] public static void Game_Update_Crusher_Postfix() { try { CrusherPerkManager.ProcessEarthshakerExpiry(); } catch (Exception ex) { Logger.LogError((object)("Error in Game_Update_Crusher_Postfix: " + ex.Message)); } } } public enum PlayerClass { SwordMaster, Archer, Crusher, Assassin, Brawler, Wizard, Lancer, Bulwark, Executioner, Warlock } public static class PlayerClassHelper { private static readonly Dictionary<PlayerClass, string> DisplayNames = new Dictionary<PlayerClass, string> { { PlayerClass.SwordMaster, "Sword Master" }, { PlayerClass.Archer, "Archer" }, { PlayerClass.Crusher, "Crusher" }, { PlayerClass.Assassin, "Assassin" }, { PlayerClass.Brawler, "Brawler" }, { PlayerClass.Wizard, "Wizard" }, { PlayerClass.Lancer, "Lancer" }, { PlayerClass.Bulwark, "Bulwark" }, { PlayerClass.Executioner, "Executioner" }, { PlayerClass.Warlock, "Warlock" } }; private static readonly Dictionary<PlayerClass, string> InternalNames = new Dictionary<PlayerClass, string> { { PlayerClass.SwordMaster, "Sword Master" }, { PlayerClass.Archer, "Archer" }, { PlayerClass.Crusher, "Crusher" }, { PlayerClass.Assassin, "Assassin" }, { PlayerClass.Brawler, "Brawler" }, { PlayerClass.Wizard, "Wizard" }, { PlayerClass.Lancer, "Lancer" }, { PlayerClass.Bulwark, "Bulwark" }, { PlayerClass.Executioner, "Executioner" }, { PlayerClass.Warlock, "Warlock" } }; private static readonly Dictionary<PlayerClass, string> WeaponTypes = new Dictionary<PlayerClass, string> { { PlayerClass.SwordMaster, "Swords" }, { PlayerClass.Archer, "Bows & Crossbows" }, { PlayerClass.Crusher, "Maces & Hammers" }, { PlayerClass.Assassin, "Knives" }, { PlayerClass.Brawler, "Unarmed" }, { PlayerClass.Wizard, "Elemental Magic Staves" }, { PlayerClass.Lancer, "Spears & Polearms" }, { PlayerClass.Bulwark, "Shields" }, { PlayerClass.Executioner, "Axes & Battleaxes" }, { PlayerClass.Warlock, "Blood Magic Staves" } }; public static string GetDisplayName(PlayerClass playerClass) { return DisplayNames[playerClass]; } public static string GetInternalName(PlayerClass playerClass) {