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 ValheimWeaponClasses; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ValheimWeaponClasses")] [assembly: AssemblyDescription("Class Obelisk build piece for Valheim")] [assembly: AssemblyProduct("ValheimWeaponClasses")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: ComVisible(false)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] public static class ArcherPerkManager { private static Dictionary arrowSlingerBuffs = new Dictionary(); private static Dictionary originalDrawDurations = new Dictionary(); public const float ARROW_SLINGER_DURATION = 10f; private const string Intro = "Expert marksmen with unparalleled bow and crossbow mastery."; private const string Outro = "Perfect for players who enjoy ranged combat and precision shooting."; public static readonly List Perks = new List { new PerkInfo { RequiredLevel = 10, Name = "Steady Draw", Description = "-15% stamina drain while drawing" }, new PerkInfo { RequiredLevel = 20, Name = "Arrow Slinger", Description = "Arrows give a buff on hit that reduces draw time by 50% for 10 seconds" }, new PerkInfo { RequiredLevel = 30, Name = "Wind Reader", Description = "+15% damage beyond 25m; -25% stamina while aiming" }, new PerkInfo { RequiredLevel = 40, Name = "Magic Shot", Description = "50% chance to not consume an arrow on attack" }, new PerkInfo { RequiredLevel = 50, Name = "Adrenaline Rush", Description = "Consecutive hits return 5% stamina" } }; 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 unparalleled bow and crossbow mastery.", Perks, "Perfect for players who enjoy ranged combat and precision shooting.", currentLevel); } public static float ApplyLv10_SteadyDrawStamina(Player player, float staminaCost) { if (!HasArcherPerk(player, 10)) { return staminaCost; } if (((Character)player).IsDrawingBow()) { return staminaCost * 0.85f; } return staminaCost; } public static void ApplyArrowSlingerDrawSpeed(Player player, ItemData weapon) { if (HasArcherPerk(player, 20) && weapon?.m_shared?.m_attack != null) { string name = weapon.m_shared.m_name; Attack attack = weapon.m_shared.m_attack; if (!originalDrawDurations.ContainsKey(name)) { originalDrawDurations[name] = attack.m_drawDurationMin; } long playerID = player.GetPlayerID(); if (arrowSlingerBuffs.ContainsKey(playerID) && Time.time < arrowSlingerBuffs[playerID]) { attack.m_drawDurationMin = originalDrawDurations[name] * 0.5f; } else { attack.m_drawDurationMin = originalDrawDurations[name]; } } } public static void RestoreDrawDuration(ItemData weapon) { if (weapon?.m_shared?.m_attack != null) { string name = weapon.m_shared.m_name; if (originalDrawDurations.ContainsKey(name)) { weapon.m_shared.m_attack.m_drawDurationMin = originalDrawDurations[name]; } } } public static void TriggerArrowSlingerBuff(Player archer) { if (HasArcherPerk(archer, 20)) { long playerID = archer.GetPlayerID(); float num = Time.time + 10f; if (!arrowSlingerBuffs.ContainsKey(playerID) || arrowSlingerBuffs[playerID] < num) { arrowSlingerBuffs[playerID] = num; AddArrowSlingerStatusEffect(archer); ((Character)archer).Message((MessageType)1, "Arrow Slinger: 50% faster draw for 10s!", 0, (Sprite)null); } } } public static void AddArrowSlingerStatusEffect(Player player) { //IL_007d: 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) try { SEMan sEMan = ((Character)player).GetSEMan(); if (sEMan != null) { RemoveArrowSlingerStatusEffect(player); ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon(); Sprite val = ((currentWeapon != null) ? currentWeapon.GetIcon() : null); if ((Object)(object)val == (Object)null) { val = GetDefaultBowIcon(); } SE_Stats val2 = ScriptableObject.CreateInstance(); ((Object)val2).name = "SE_ArrowSlinger"; ((StatusEffect)val2).m_name = "Arrow Slinger"; ((StatusEffect)val2).m_tooltip = "Draw speed increased by 50%"; ((StatusEffect)val2).m_icon = val; ((StatusEffect)val2).m_ttl = 10f; ((StatusEffect)val2).m_startMessage = ""; ((StatusEffect)val2).m_startMessageType = (MessageType)2; ((StatusEffect)val2).m_stopMessage = ""; ((StatusEffect)val2).m_stopMessageType = (MessageType)2; sEMan.AddStatusEffect((StatusEffect)(object)val2, true, 0, 0f); } } catch (Exception ex) { Logger.LogError((object)("Error adding Arrow Slinger status effect: " + ex.Message)); } } private static void RemoveArrowSlingerStatusEffect(Player player) { try { SEMan sEMan = ((Character)player).GetSEMan(); if (sEMan != null) { sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_ArrowSlinger"), true); } } catch (Exception ex) { Logger.LogError((object)("Error removing Arrow Slinger status effect: " + ex.Message)); } } private static Sprite GetDefaultBowIcon() { try { string[] array = new string[5] { "Bow", "BowFineWood", "BowHuntsman", "BowDraugrFang", "CrossbowArbalest" }; foreach (string text in array) { ObjectDB instance = ObjectDB.instance; GameObject val = ((instance != null) ? instance.GetItemPrefab(text) : null); if ((Object)(object)val != (Object)null) { ItemDrop component = val.GetComponent(); object obj; if (component == null) { obj = null; } else { ItemData itemData = component.m_itemData; obj = ((itemData != null) ? itemData.GetIcon() : null); } if ((Object)obj != (Object)null) { return component.m_itemData.GetIcon(); } } } return null; } catch { return null; } } public static bool HasActiveArrowSlingerBuff(long playerID) { if (arrowSlingerBuffs.ContainsKey(playerID)) { return Time.time < arrowSlingerBuffs[playerID]; } return false; } public static float ApplyLv30_WindReaderDamage(Player archer, Vector3 shotOrigin, Vector3 hitPoint, float baseDamage) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!HasArcherPerk(archer, 30)) { return baseDamage; } float num = Vector3.Distance(shotOrigin, hitPoint); if (num > 25f) { float num2 = baseDamage * 0.15f; if (Random.Range(0f, 1f) < 0.2f) { ((Character)archer).Message((MessageType)1, $"Long Shot! +15% damage ({num:F0}m)", 0, (Sprite)null); } return baseDamage + num2; } return baseDamage; } public static float ApplyLv30_WindReaderStamina(Player archer, float staminaDrain) { if (!HasArcherPerk(archer, 30)) { return staminaDrain; } if (((Character)archer).IsDrawingBow()) { return staminaDrain * 0.75f; } return staminaDrain; } public static bool ShouldConsumeArrow(Player archer) { if (!HasArcherPerk(archer, 40)) { return true; } if (Random.Range(0f, 1f) < 0.5f) { ((Character)archer).Message((MessageType)1, "Magic Shot! Arrow not consumed", 0, (Sprite)null); return false; } return true; } public static void TriggerAdrenalineRush(Player archer) { if (HasArcherPerk(archer, 50)) { float num = ((Character)archer).GetMaxStamina() * 0.05f; ((Character)archer).AddStamina(num); if (Random.Range(0f, 1f) < 0.3f) { ((Character)archer).Message((MessageType)1, $"Adrenaline Rush! +{num:F0} stamina", 0, (Sprite)null); } } } public static void UpdateBuffs() { float currentTime = Time.time; foreach (long playerID in (from kvp in arrowSlingerBuffs where kvp.Value < currentTime select kvp.Key).ToList()) { arrowSlingerBuffs.Remove(playerID); Player val = ((IEnumerable)Player.GetAllPlayers()).FirstOrDefault((Func)((Player p) => p.GetPlayerID() == playerID)); if ((Object)(object)val != (Object)null) { RemoveArrowSlingerStatusEffect(val); } } } } [HarmonyPatch] public static class ArcherPerkPatches { private static float lastCleanupTime; [HarmonyPatch(typeof(Player), "UpdateAttackBowDraw")] [HarmonyPrefix] public static void Player_UpdateAttackBowDraw_Prefix(Player __instance, ItemData weapon, float dt) { try { if (!((Object)(object)__instance == (Object)null) && weapon != null && ClassCombatManager.IsBowWeapon(weapon)) { ArcherPerkManager.ApplyArrowSlingerDrawSpeed(__instance, weapon); } } catch (Exception ex) { Logger.LogError((object)("Error in Player_UpdateAttackBowDraw_Prefix (Archer): " + ex.Message)); } } [HarmonyPatch(typeof(Projectile), "OnHit")] [HarmonyPrefix] public static void Projectile_OnHit_Prefix(Projectile __instance, Collider collider, Vector3 hitPoint) { try { if ((Object)(object)__instance == (Object)null || (Object)(object)collider == (Object)null) { return; } Character component = ((Component)collider).GetComponent(); if ((Object)(object)component == (Object)null || component is Player) { return; } Player val = FindProjectileOwner(__instance); if ((Object)(object)val == (Object)null) { return; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(val); if (playerData != null && playerData.IsClassActive(PlayerClass.Archer)) { ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); if (ClassCombatManager.IsBowWeapon(currentWeapon)) { ArcherPerkManager.RestoreDrawDuration(currentWeapon); } ArcherPerkManager.TriggerArrowSlingerBuff(val); ArcherPerkManager.TriggerAdrenalineRush(val); } } catch (Exception ex) { Logger.LogError((object)("Error in Projectile_OnHit_Prefix (Archer): " + ex.Message)); } } [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] public static void Character_Damage_Prefix_ArcherPerks(Character __instance, ref HitData hit) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) try { Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || (Object)(object)__instance == (Object)null || __instance is Player || !IsProjectileDamage(hit) || !ClassCombatManager.IsBowWeapon(((Humanoid)val).GetCurrentWeapon())) { return; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(val); if (playerData != null && playerData.IsClassActive(PlayerClass.Archer)) { float totalDamage = hit.GetTotalDamage(); Vector3 position = ((Component)val).transform.position; Vector3 point = hit.m_point; float num = ArcherPerkManager.ApplyLv30_WindReaderDamage(val, position, point, totalDamage); if (num > totalDamage) { float num2 = num / totalDamage; hit.m_damage.m_damage *= num2; hit.m_damage.m_pierce *= num2; hit.m_damage.m_slash *= num2; } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Prefix_ArcherPerks: " + ex.Message)); } } [HarmonyPatch(typeof(Player), "UseStamina")] [HarmonyPrefix] public static void Player_UseStamina_Prefix(Player __instance, ref float v) { try { if (!((Object)(object)__instance == (Object)null)) { v = ArcherPerkManager.ApplyLv10_SteadyDrawStamina(__instance, v); v = ArcherPerkManager.ApplyLv30_WindReaderStamina(__instance, v); } } catch (Exception ex) { Logger.LogError((object)("Error in Player_UseStamina_Prefix (Archer): " + ex.Message)); } } [HarmonyPatch(typeof(Game), "Update")] [HarmonyPostfix] public static void Game_Update_Postfix() { try { if (Time.time - lastCleanupTime >= 1f) { lastCleanupTime = Time.time; ArcherPerkManager.UpdateBuffs(); } } catch (Exception ex) { Logger.LogError((object)("Error in Game_Update_Postfix (Archer cleanup): " + ex.Message)); } } private static Player FindProjectileOwner(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(); if ((Object)(object)component != (Object)null && component.IsValid()) { long ownerID = component.GetZDO().GetLong("owner", 0L); if (ownerID != 0L) { return ((IEnumerable)Player.GetAllPlayers()).FirstOrDefault((Func)((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) < 100f) { return localPlayer; } return null; } catch { return null; } } private static bool IsProjectileDamage(HitData hit) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((int)hit.m_skill != 8 && (int)hit.m_skill != 14) { if (hit.m_damage.m_pierce > 0f && hit.m_damage.m_blunt == 0f) { return hit.m_damage.m_slash == 0f; } return false; } return true; } [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, 40) && Random.Range(0f, 1f) < 0.5f) { List 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; ((Character)val).Message((MessageType)1, "Magic Shot! Arrow not consumed", 0, (Sprite)null); } } } 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.GetAllPlayers()).FirstOrDefault((Func)((Player p) => ((Humanoid)p).GetInventory() == inventory)); } } [HarmonyPatch] public static class AssassinPerkManager { public class PoisonData { public int stacks; public float endTime; public float lastTickTime; public float damagePerTick; public float totalDamage; public Player source; public float originalSpeed; public float originalRunSpeed; } public static Dictionary poisonTracking = new Dictionary(); private static Dictionary assassinationBuffs = new Dictionary(); public const int MAX_POISON_STACKS = 3; public const float POISON_DURATION = 10f; public const float ASSASSINATION_SPEED_DURATION = 5f; public const float ASSASSINATION_SPEED_BONUS = 2f; public const float ENVENOMOUS_SLOW_PER_STACK = 0.2f; public const float TWIST_KNIFE_DAMAGE_BONUS = 0.25f; public const float TWIST_KNIFE_DAMAGE_REDUCTION = 0.15f; public const float ASSASSIN_BACKSTAB_BONUS = 0.3f; public const float ASSASSIN_CUT_THROAT_BONUS = 0.15f; public const float ASSASSIN_STEALTH_BONUS = 1f; public static bool DAMAGE_MODS_ON = true; private const string Intro = "Stealthy fighters who strike from the shadows with deadly precision."; private const string Outro = "Great for players who prefer tactical, stealthy gameplay and damage over time."; public static readonly List Perks = new List { new PerkInfo { RequiredLevel = 10, Name = "Cutthroat", Description = "+15% knife damage; +30% backstab multiplier" }, new PerkInfo { RequiredLevel = 20, Name = "Venom Coating", Description = "Knife hits apply stacking Poison (up to 3 stacks) based on skill level" }, new PerkInfo { RequiredLevel = 30, Name = "Envenomous", Description = "Poisons apply 15% movement speed slow per stack" }, new PerkInfo { RequiredLevel = 40, Name = "Assassination", Description = "First knife hit from stealth deals +100% damage" }, new PerkInfo { RequiredLevel = 50, Name = "Twist the Knife", Description = "+25% damage to poisoned targets; poisoned enemies deal -10% damage" } }; 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 strike from the shadows with deadly precision.", Perks, "Great for players who prefer tactical, stealthy gameplay and damage over time.", currentLevel); } public static void ApplyLv20_VenomCoating(Player player, Character target, HitData hit) { if (HasAssassinPerk(player, 20) && !((Object)(object)target == (Object)null) && !target.IsDead()) { float skillLevel = ((Character)player).GetSkillLevel((SkillType)2); float num = skillLevel / 10f; if (!poisonTracking.ContainsKey(target)) { poisonTracking[target] = new PoisonData(); } PoisonData poisonData = poisonTracking[target]; if (poisonData.stacks < 3) { poisonData.stacks++; AddPoisonStatusEffect(target, poisonData.stacks); ((Character)player).Message((MessageType)1, $"Venom Applied! {poisonData.stacks}/{3} stacks", 0, (Sprite)null); } poisonData.endTime = Time.time + 10f; poisonData.damagePerTick = num * (float)poisonData.stacks; poisonData.source = player; poisonData.totalDamage = skillLevel * (float)poisonData.stacks; target.GetSEMan().GetStatusEffect(StringExtensionMethods.GetStableHashCode("Poison")); float num2 = 1f - (float)poisonData.stacks * 0.2f; poisonData.originalSpeed = target.m_speed; target.m_speed *= num2; poisonData.originalRunSpeed = target.m_runSpeed; target.m_runSpeed *= num2; } } private static void AddPoisonStatusEffect(Character target, int stacks) { try { SEMan sEMan = target.GetSEMan(); if (sEMan != null) { sEMan.RemoveStatusEffect(StringExtensionMethods.GetStableHashCode("SE_AssassinPoison"), true); SE_Stats val = ScriptableObject.CreateInstance(); ((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 = 10f; sEMan.AddStatusEffect((StatusEffect)(object)val, true, 0, 0f); } } catch (Exception ex) { Logger.LogError((object)("Error adding poison status effect: " + ex.Message)); } } private static Sprite GetPoisonIcon() { try { ObjectDB instance = ObjectDB.instance; StatusEffect val = ((instance != null) ? instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode("Poison")) : null); if ((Object)(object)val != (Object)null) { return val.m_icon; } return null; } catch { return null; } } public static float ApplyLv30_EnvenomousMovementSlow(Character target, float baseSpeed) { if ((Object)(object)target == (Object)null || !poisonTracking.ContainsKey(target)) { return baseSpeed; } PoisonData poisonData = poisonTracking[target]; if ((Object)(object)poisonData.source == (Object)null || !HasAssassinPerk(poisonData.source, 30)) { return baseSpeed; } float num = 1f - (float)poisonData.stacks * 0.2f; num = Mathf.Max(num, 0.25f); return baseSpeed * num; } private static void ApplyAssassinationSpeedBuff(Character c, Player player) { player.GetPlayerID(); float value = Time.time + 5f; assassinationBuffs[player] = value; AnimationSpeedManager.Set((Character)(object)player, "Assassin_Knife_AS", 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(); ((Object)val).name = "SE_AssassinationSpeed"; ((StatusEffect)val).m_name = "Assassination Speed"; ((StatusEffect)val).m_tooltip = "+25% 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); } } catch (Exception ex) { Logger.LogError((object)("Error adding assassination status effect: " + ex.Message)); } } public static void ProcessPoisonDamage() { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) float time = Time.time; List list = new List(); foreach (KeyValuePair item in poisonTracking) { Character key = item.Key; PoisonData value = item.Value; if ((Object)(object)key == (Object)null || key.IsDead() || time > value.endTime) { ClearSpeedDebuff(key, value.originalSpeed, value.originalRunSpeed); list.Add(key); } else if (time - value.lastTickTime >= 1f) { value.lastTickTime = time; HitData val = new HitData(); val.m_damage.m_poison = value.damagePerTick; 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); } } public static void ClearSpeedDebuff(Character target, float originalSpeed, float originalRunSpeed) { if (!target.IsDead()) { target.m_speed = originalSpeed; target.m_runSpeed = originalRunSpeed; } } 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; } public static bool IsBackstab(Vector3 hitPoint, Character target) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null) { return false; } Vector3 val = hitPoint - ((Component)target).transform.position; Vector3 normalized = ((Vector3)(ref val)).normalized; return Vector3.Angle(((Component)target).transform.forward, normalized) > 120f; } 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) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) try { PlayerClassData playerData = PlayerClassManager.GetPlayerData(Player.m_localPlayer); if (!playerData.IsClassActive("Assassin") || 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(); if (poisonTracking.ContainsKey(attacker2)) { float mod = 0.15f; hit = ModDamage(hit, mod); } } else if (ClassCombatManager.IsKnifeWeapon(((Humanoid)val).GetCurrentWeapon()) && playerData != null && playerData.IsClassActive(PlayerClass.Assassin)) { float num = 0f; if (HasAssassinPerk(val, 10)) { num += 0.15f; } if (IsBackstab(hit.m_point, __instance) && HasAssassinPerk(val, 10)) { num += 0.3f; } if (IsStealthHit(val, __instance) && HasAssassinPerk(val, 40)) { num += 1f; ApplyAssassinationSpeedBuff(__instance, val); } if (HasAssassinPerk(val, 50)) { num += 0.25f; } _ = hit.m_damage; _ = hit.m_damage; if (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()) && (hit.m_damage.m_blunt != 0f || hit.m_damage.m_slash != 0f || hit.m_damage.m_pierce != 0f)) { PlayerClassData playerData = PlayerClassManager.GetPlayerData(val); if (playerData != null && playerData.IsClassActive(PlayerClass.Assassin)) { ApplyLv20_VenomCoating(val, __instance, hit); } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Assassin_Postfix: " + ex.Message)); } } [HarmonyPatch(typeof(Game), "Update")] [HarmonyPostfix] public static void Game_Update_Assassin_Postfix() { try { ProcessPoisonDamage(); if (Time.time % 1f < Time.deltaTime) { CleanupBuffs(); } } catch (Exception ex) { Logger.LogError((object)("Error in Game_Update_Assassin_Postfix: " + ex.Message)); } } } public static class ClassCombatManager { public static bool IsSwordWeapon(ItemData weapon) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Invalid comparison between Unknown and I4 if ((weapon == null || (int)(weapon.m_shared?.m_itemType).GetValueOrDefault() != 3) && (weapon == null || (int)(weapon.m_shared?.m_itemType).GetValueOrDefault() != 14)) { return false; } string text = weapon.m_shared.m_name?.ToLower() ?? ""; if (!text.Contains("sword") && !text.Contains("blade") && !text.Contains("saber")) { return text.Contains("katana"); } return true; } public static bool IsBowWeapon(ItemData weapon) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (weapon == null) { return false; } return (int)(weapon.m_shared?.m_itemType).GetValueOrDefault() == 4; } public static bool IsBluntWeapon(ItemData weapon) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Invalid comparison between Unknown and I4 if ((weapon == null || (int)(weapon.m_shared?.m_itemType).GetValueOrDefault() != 3) && (weapon == null || (int)(weapon.m_shared?.m_itemType).GetValueOrDefault() != 14)) { return false; } string text = weapon.m_shared.m_name?.ToLower() ?? ""; if (!text.Contains("mace") && !text.Contains("hammer") && !text.Contains("club")) { return text.Contains("sledge"); } return true; } public static bool IsKnifeWeapon(ItemData weapon) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 if (weapon == null || (int)(weapon.m_shared?.m_itemType).GetValueOrDefault() != 3) { return false; } string text = weapon.m_shared.m_name?.ToLower() ?? ""; if (!text.Contains("knife") && !text.Contains("dagger") && !text.Contains("seax")) { return text.Contains("razor"); } return true; } public static bool IsSpearWeapon(ItemData weapon) { string name = weapon.m_shared.m_name; if (name == null || !name.ToLower().Contains("spear")) { string name2 = weapon.m_shared.m_name; if (name2 == null || !name2.ToLower().Contains("atgeir")) { return weapon.m_shared.m_name?.ToLower().Contains("halberd") ?? false; } } return true; } public static bool IsUnarmedAttack(ItemData weapon) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (weapon != null && (int)weapon.m_shared.m_itemType != 0) { string name = weapon.m_shared.m_name; if (name == null || !name.ToLower().Contains("claws")) { return weapon.m_shared.m_name == "Unarmed"; } } return true; } public static bool IsMagicWeapon(ItemData weapon) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if (weapon != null && (int)(weapon.m_shared?.m_itemType).GetValueOrDefault() == 14) { string name = weapon.m_shared.m_name; if (name == null || !name.ToLower().Contains("staff")) { return (Object)(object)weapon.m_shared.m_attackStatusEffect != (Object)null; } return true; } return false; } 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), "Archer" => GetArcherDamageBonus(classLevel, weapon), "Crusher" => GetCrusherDamageBonus(classLevel, weapon), "Assassin" => GetAssassinDamageBonus(classLevel, weapon), "Brawler" => GetBrawlerDamageBonus(classLevel, weapon), "Wizard" => GetWizardDamageBonus(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; num += (float)level * 0.005f; if (level >= 10) { num += 0.1f; } return 1f + num; } private static float GetArcherDamageBonus(int level, ItemData weapon) { if (!IsBowWeapon(weapon)) { return 1f; } float num = 0f; num += (float)level * 0.005f; if (level >= 50) { num += 0.2f; } return 1f + num; } private static float GetCrusherDamageBonus(int level, ItemData weapon) { if (!IsBluntWeapon(weapon)) { return 1f; } float num = 0f; num += (float)level * 0.005f; if (level >= 10) { num += 0.12f; } return 1f + num; } private static float GetAssassinDamageBonus(int level, ItemData weapon) { if (!IsKnifeWeapon(weapon)) { return 1f; } float num = 0f; num += (float)level * 0.005f; if (level >= 10) { num += 0.12f; } return 1f + num; } private static float GetBrawlerDamageBonus(int level, ItemData weapon) { if (!IsUnarmedAttack(weapon)) { return 1f; } float num = 0f; num += (float)level * 0.005f; if (level >= 10) { num += 0.15f; } return 1f + num; } private static float GetWizardDamageBonus(int level, ItemData weapon) { if (!IsMagicWeapon(weapon)) { return 1f; } float num = 0f; num += (float)level * 0.005f; if (level >= 10) { num += 0.08f; } return 1f + num; } private static float GetLancerDamageBonus(int level, ItemData weapon) { if (!IsSpearWeapon(weapon)) { return 1f; } float num = 0f; num += (float)level * 0.005f; if (level >= 10) { num += 0.1f; } return 1f + num; } private static float GetBulwarkDamageBonus(int level, ItemData weapon) { return 1f; } public static void ShowDamageBonusMessage(Player player, float multiplier, string weaponType) { if (multiplier > 1f && (Object)(object)player == (Object)(object)Player.m_localPlayer) { float num = (multiplier - 1f) * 100f; if (Random.Range(0f, 1f) < 0.1f) { ((Character)player).Message((MessageType)1, $"{weaponType} Mastery: +{num:F0}% damage!", 0, (Sprite)null); } } } public static float GetClassSpecificDamageMultiplierPublic(string className, PlayerClassData playerData, ItemData weapon) { int classLevel = playerData.GetClassLevel(className); return className switch { "Sword Master" => GetSwordMasterDamageBonus(classLevel, weapon), "Archer" => GetArcherDamageBonus(classLevel, weapon), "Crusher" => GetCrusherDamageBonus(classLevel, weapon), "Assassin" => GetAssassinDamageBonus(classLevel, weapon), "Brawler" => GetBrawlerDamageBonus(classLevel, weapon), "Wizard" => GetWizardDamageBonus(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) { try { 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; string weaponTypeName = GetWeaponTypeName(currentWeapon); ClassCombatManager.ShowDamageBonusMessage(val, classDamageMultiplier, weaponTypeName); } } } catch (Exception ex) { Logger.LogError((object)("Error in Character_Damage_Prefix: " + ex.Message)); } } private static string GetWeaponTypeName(ItemData weapon) { if (ClassCombatManager.IsSwordWeapon(weapon)) { return "Sword"; } if (ClassCombatManager.IsBowWeapon(weapon)) { return "Bow"; } if (ClassCombatManager.IsBluntWeapon(weapon)) { return "Blunt"; } if (ClassCombatManager.IsKnifeWeapon(weapon)) { return "Knife"; } if (ClassCombatManager.IsSpearWeapon(weapon)) { return "Spear"; } if (ClassCombatManager.IsUnarmedAttack(weapon)) { return "Unarmed"; } if (ClassCombatManager.IsMagicWeapon(weapon)) { return "Magic"; } return "Weapon"; } } [HarmonyPatch] public static class ProjectilePatches { private class ProjectileInfo { public Player owner; public ItemData weapon; public float createdTime; public string projectileName; } private static Dictionary trackedProjectiles = new Dictionary(); [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)((Humanoid)player).GetInventory().GetAllItems()).FirstOrDefault((Func)((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(); if ((Object)(object)component != (Object)null && component.IsValid()) { long ownerID = component.GetZDO().GetLong("owner", 0L); if (ownerID != 0L) { return ((IEnumerable)Player.GetAllPlayers()).FirstOrDefault((Func)((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 list = new List(); float time = Time.time; foreach (KeyValuePair 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)); } } } public static class ClassDamageBonusManager { public static float DamageBonusPerLevel = 0.005f; public static float MaxDamageBonus = 0.25f; public static float CalculateDamageBonus(Player attacker, ItemData weapon) { if ((Object)(object)attacker == (Object)null || weapon == null) { return 1f; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(attacker); if (playerData == null || playerData.activeClasses.Count == 0) { return 1f; } float num = 1f; bool flag = false; foreach (string activeClass in playerData.activeClasses) { if (IsWeaponAppropriateForClass(weapon, activeClass)) { float num2 = (float)playerData.GetClassLevel(activeClass) * DamageBonusPerLevel; num2 = Mathf.Min(num2, MaxDamageBonus); num += num2; flag = true; } } return num; } private static bool IsWeaponAppropriateForClass(ItemData weapon, string className) { return className switch { "Sword Master" => ClassCombatManager.IsSwordWeapon(weapon), "Archer" => ClassCombatManager.IsBowWeapon(weapon), "Crusher" => ClassCombatManager.IsBluntWeapon(weapon), "Assassin" => ClassCombatManager.IsKnifeWeapon(weapon), "Brawler" => ClassCombatManager.IsUnarmedAttack(weapon), "Wizard" => ClassCombatManager.IsMagicWeapon(weapon), "Lancer" => ClassCombatManager.IsSpearWeapon(weapon), "Bulwark" => false, _ => false, }; } public static string GetDamageBonusInfo(Player player, ItemData weapon) { if ((Object)(object)player == (Object)null || weapon == null) { return "No weapon equipped"; } PlayerClassData playerData = PlayerClassManager.GetPlayerData(player); if (playerData == null || playerData.activeClasses.Count == 0) { return "No active classes"; } StringBuilder stringBuilder = new StringBuilder(); float num = 0f; bool flag = false; foreach (string activeClass in playerData.activeClasses) { if (IsWeaponAppropriateForClass(weapon, activeClass)) { int classLevel = playerData.GetClassLevel(activeClass); float num2 = (float)classLevel * DamageBonusPerLevel; num2 = Mathf.Min(num2, MaxDamageBonus); stringBuilder.AppendLine($"{activeClass} Lv.{classLevel}: +{num2 * 100f:F1}%"); num += num2; flag = true; } } if (!flag) { return "No active classes match " + weapon.m_shared.m_name; } stringBuilder.AppendLine($"Total Bonus: +{num * 100f:F1}%"); stringBuilder.AppendLine($"Damage Multiplier: {1f + num:F3}x"); return stringBuilder.ToString().TrimEnd(Array.Empty()); } } [HarmonyPatch] public static class DamageBonusPatches { [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] public static void Character_Damage_Prefix(Character __instance, ref HitData hit) { try { Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || __instance is Player) { return; } ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); if (currentWeapon != null) { float num = ClassDamageBonusManager.CalculateDamageBonus(val, currentWeapon); if (num > 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_pickaxe *= 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_Prefix (Damage Bonus): " + ex.Message)); } } } [BepInPlugin("com.bunzboi.valheimweaponclass", "ValheimWeaponClasses", "1.0.0")] [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.0.0"; private GameObject TestPanel; private static ClassObeliskMod _instance; private Harmony _harmony; private ConfigEntry _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("General", "EnableMod", true, "Enable the class obelisk mod"); if (_enableMod.Value) { _harmony = new Harmony("com.bunzboi.valheimweaponclass"); _harmony.PatchAll(); 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(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); } obj.AddComponent(); ((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__20_0; internal void b__20_0() { CloseClassSelectionGUI(); } } public static GameObject classSelectionPanel; public static Text descriptionText; public static ScrollRect descriptionScrollRect; public static GameObject selectClassButton; public static string selectedClassName = ""; public static readonly string[] ClassNames = new string[8] { "Sword Master", "Archer", "Crusher", "Assassin", "Brawler", "Wizard", "Lancer", "Bulwark" }; private static readonly Dictionary> DynamicClassDescriptionProviders = new Dictionary> { { "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 } }; 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[E] Select your combat class"; } public string GetHoverName() { return "Class Obelisk"; } 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(); CreateTwoColumnClassButtons(player); CreateDescriptionWindow(player); CreateSelectClassButton(player); CreateEnhancedCloseButton(); string text = ((playerData.activeClasses.Count > 0) ? string.Join(", ", playerData.activeClasses) : "None"); UpdateDescriptionText("Current Active Classes: " + text + "\n\nClick on a class above to see its description and benefits."); selectedClassName = ""; UpdateSelectButton(); } 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(); 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().alignment = (TextAnchor)4; } bool flag = !classSelectionPanel.activeSelf; classSelectionPanel.SetActive(flag); GUIManager.BlockInput(flag); } private void CreateTwoColumnClassButtons(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(); 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 = 180f; float num2 = 50f; float num3 = 25f; float num4 = 15f; float num5 = (0f - (num * 4f + num3 * 3f)) / 2f + num / 2f; float num6 = 25f; for (int i = 0; i < ClassNames.Length; i++) { int num7 = i % 4; int num8 = i / 4; float x = num5 + (float)num7 * (num + num3); float y = num6 - (float)num8 * (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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown int classLevel = PlayerClassManager.GetPlayerData(player).GetClassLevel(className); string text = $"{className} ({classLevel})"; GameObject obj = 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)obj.GetComponent