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 UsefulSkills v1.1.2
UsefulSkills.dll
Decompiled a week 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.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace UsefulSkills { internal enum ArmorClass { None, Light, Medium, Heavy } internal static class ArmorClasses { private sealed class Buckler { public string Name; public string Material; public float Power; } private static readonly Dictionary<SharedData, ArmorClass> Cache = new Dictionary<SharedData, ArmorClass>(); private static readonly Dictionary<string, float> Bucklers = new Dictionary<string, float>(); private static readonly Dictionary<string, string> BucklerSource = new Dictionary<string, string>(); private static readonly Dictionary<string, string> BucklerShield = new Dictionary<string, string>(); private static readonly Dictionary<string, bool> Wood = new Dictionary<string, bool>(); private static readonly Dictionary<string, bool> Ore = new Dictionary<string, bool>(); private static Regex _light; private static Regex _medium; private static Regex _heavy; private static Regex _wood; private static Regex _ore; private static bool _built; private static string _shield; private static List<Buckler> _bucklerList; private static ObjectDB _bucklersFor; private static ObjectDB _powersFor; internal static string BucklerShieldOf(string prefab) { BucklerPower(prefab, out var _); if (!BucklerShield.TryGetValue(prefab, out var value)) { return null; } return value; } internal static void Reset() { Cache.Clear(); Bucklers.Clear(); BucklerSource.Clear(); BucklerShield.Clear(); Wood.Clear(); Ore.Clear(); _bucklerList = null; _built = false; } private static void Build() { if (!_built) { _heavy = Glob(Plugin.HeavyItems.Value); _medium = Glob(Plugin.MediumItems.Value); _light = Glob(Plugin.LightItems.Value); _wood = Glob(Plugin.WoodItems.Value); _ore = Glob(Plugin.OreItems.Value); _built = true; } } internal static bool IsWood(string prefab) { return Matches(prefab, Wood, () => _wood); } internal static bool IsOre(string prefab) { return Matches(prefab, Ore, () => _ore); } private static bool Matches(string prefab, Dictionary<string, bool> cache, Func<Regex> regex) { if (string.IsNullOrEmpty(prefab)) { return false; } if (cache.TryGetValue(prefab, out var value)) { return value; } Build(); bool flag = regex()?.IsMatch(prefab) ?? false; cache[prefab] = flag; return flag; } internal static float BucklerPower(string prefab, out string source) { if ((Object)(object)_powersFor != (Object)(object)ObjectDB.instance) { _powersFor = ObjectDB.instance; Bucklers.Clear(); BucklerSource.Clear(); BucklerShield.Clear(); _bucklerList = null; } if (Bucklers.TryGetValue(prefab, out var value)) { source = BucklerSource[prefab]; return value; } if (!Object.op_Implicit((Object)(object)ObjectDB.instance)) { source = "game data not loaded"; return 0f; } _shield = null; bool matched; float num = Override(prefab, out source, out matched); if (!matched) { num = FindBuckler(prefab, out source); } Bucklers[prefab] = num; BucklerSource[prefab] = source; BucklerShield[prefab] = ((num > 0f) ? _shield : null); return num; } private static float Override(string prefab, out string source, out bool matched) { matched = false; source = null; string[] array = (Plugin.HeavyBucklers.Value ?? "").Split(new char[1] { ',' }); foreach (string text in array) { int num = text.IndexOf('='); if (num <= 0) { continue; } string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 1).Trim(); if (text2.Length != 0 && prefab.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0) { matched = true; if (float.TryParse(text3, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { source = "override " + text2 + "=" + text3; return Mathf.Max(0f, result); } ItemDrop val = ItemPrefab(text3); if ((Object)(object)val == (Object)null) { source = "override " + text2 + "=" + text3 + " (shield not found)"; return 0f; } source = "override " + text3; _shield = text3; return val.m_itemData.m_shared.m_blockPower; } } return 0f; } private static ItemDrop ItemPrefab(string name) { GameObject val = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.GetItemPrefab(name) : null); ItemDrop val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<ItemDrop>() : null); if (val2?.m_itemData?.m_shared == null) { return null; } return val2; } internal static string MainMaterial(string prefab) { Recipe val = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.m_recipes : null)?.Find((Recipe r) => Object.op_Implicit((Object)(object)r) && Object.op_Implicit((Object)(object)r.m_item) && ((Object)r.m_item).name == prefab); if (!Object.op_Implicit((Object)(object)val) || val.m_resources == null) { return null; } Requirement val2 = null; Requirement[] resources = val.m_resources; foreach (Requirement val3 in resources) { if ((Object)(object)val3?.m_resItem != (Object)null && (val2 == null || val3.m_amount > val2.m_amount)) { val2 = val3; } } if (val2 == null) { return null; } return ((Object)val2.m_resItem).name; } private static List<Buckler> AllBucklers() { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Invalid comparison between Unknown and I4 if (_bucklerList != null && (Object)(object)_bucklersFor == (Object)(object)ObjectDB.instance) { return _bucklerList; } _bucklersFor = ObjectDB.instance; _bucklerList = new List<Buckler>(); foreach (GameObject item in ObjectDB.instance.m_items) { SharedData val = (Object.op_Implicit((Object)(object)item) ? item.GetComponent<ItemDrop>() : null)?.m_itemData?.m_shared; if (val != null && (int)val.m_itemType == 5 && ((Object)item).name.IndexOf("Buckler", StringComparison.OrdinalIgnoreCase) >= 0) { string text = MainMaterial(((Object)item).name); if (text != null) { _bucklerList.Add(new Buckler { Name = ((Object)item).name, Material = text, Power = val.m_blockPower }); } } } return _bucklerList; } private static float FindBuckler(string prefab, out string source) { //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) List<Buckler> list = AllBucklers(); if (list.Count == 0) { source = "no bucklers in the game"; return 0f; } string text = MainMaterial(prefab); Buckler buckler = null; foreach (Buckler item in list) { if (text != null && item.Material == text && (buckler == null || item.Power > buckler.Power)) { buckler = item; } } if (buckler != null) { source = buckler.Name + " (same material, " + text + ")"; _shield = buckler.Name; return buckler.Power; } SharedData val = ItemPrefab(prefab)?.m_itemData.m_shared; float num = -1f; string text2 = null; if (val != null) { foreach (GameObject item2 in ObjectDB.instance.m_items) { SharedData val2 = (Object.op_Implicit((Object)(object)item2) ? item2.GetComponent<ItemDrop>() : null)?.m_itemData?.m_shared; if (val2 != null && val2.m_itemType == val.m_itemType && !(((Object)item2).name == prefab) && !(val2.m_armor > val.m_armor) && !(val2.m_armor <= num) && Classify(((Object)item2).name, val2, out var _) == ArmorClass.Heavy) { string m = MainMaterial(((Object)item2).name); Buckler buckler2 = ((m == null) ? null : list.Find((Buckler x) => x.Material == m)); if (buckler2 != null) { num = val2.m_armor; buckler = buckler2; text2 = ((Object)item2).name; } } } } if (buckler != null) { source = buckler.Name + " (no " + (text ?? "recipe") + " buckler; earlier set " + text2 + ")"; _shield = buckler.Name; return buckler.Power; } buckler = list[0]; foreach (Buckler item3 in list) { if (item3.Power < buckler.Power) { buckler = item3; } } source = buckler.Name + " (weakest; nothing earlier found)"; _shield = buckler.Name; return buckler.Power; } internal static bool IsArmorSlot(ItemData item) { //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_0022: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 if (item?.m_shared == null) { return false; } ItemType itemType = item.m_shared.m_itemType; if ((int)itemType != 6 && (int)itemType != 7) { return (int)itemType == 11; } return true; } internal static ArmorClass Classify(ItemData item) { if (!IsArmorSlot(item)) { return ArmorClass.None; } if (Cache.TryGetValue(item.m_shared, out var value)) { return value; } bool real; string why; ArmorClass armorClass = Classify(PrefabName(item, out real), item.m_shared, out why); if (real) { Cache[item.m_shared] = armorClass; } return armorClass; } internal static string PrefabName(ItemData item) { bool real; return PrefabName(item, out real); } private static string PrefabName(ItemData item, out bool real) { real = true; if (Object.op_Implicit((Object)(object)item.m_dropPrefab)) { return ((Object)item.m_dropPrefab).name; } GameObject val = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.GetItemPrefab(item.m_shared) : null); if (Object.op_Implicit((Object)(object)val)) { return ((Object)val).name; } real = false; return item.m_shared.m_name; } internal static ArmorClass Classify(string prefab, SharedData shared, out string why) { Build(); if (_heavy != null && _heavy.IsMatch(prefab)) { why = "Heavy armor list"; return ArmorClass.Heavy; } if (_medium != null && _medium.IsMatch(prefab)) { why = "Medium armor list"; return ArmorClass.Medium; } if (_light != null && _light.IsMatch(prefab)) { why = "Light armor list"; return ArmorClass.Light; } if (shared.m_armor <= 0f && shared.m_armorPerLevel <= 0f) { why = "no armor"; return ArmorClass.None; } if (shared.m_movementModifier < 0f) { why = $"slows you {shared.m_movementModifier * 100f:0.#}%"; return ArmorClass.Heavy; } if (shared.m_eitrRegenModifier > 0f) { why = $"eitr regen +{shared.m_eitrRegenModifier * 100f:0.#}%"; return ArmorClass.Light; } why = "no speed penalty or eitr regen"; return ArmorClass.Medium; } private static Regex Glob(string list) { List<string> list2 = new List<string>(); string[] array = (list ?? "").Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list2.Add(Regex.Escape(text).Replace("\\*", ".*").Replace("\\?", ".")); } } if (list2.Count != 0) { return new Regex("^(?:" + string.Join("|", list2) + ")$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } return null; } } internal static class ArmorSkills { internal struct Worn { public int Light; public int Medium; public int Heavy; public float MediumPenalty; public float HeavyPenalty; public float HeavyBlock; public bool Has(ArmorClass c) { int value = Plugin.PiecesNeeded.Value; return c switch { ArmorClass.Light => Light >= value, ArmorClass.Medium => Medium >= value, ArmorClass.Heavy => Heavy >= value, _ => false, }; } } internal const SkillType Light = (SkillType)1398400001; internal const SkillType Medium = (SkillType)1398400002; internal const SkillType Heavy = (SkillType)1398400003; internal const SkillType Sailing = (SkillType)1398400004; private static readonly FieldRef<Humanoid, ItemData> HelmetRef = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_helmetItem"); private static readonly FieldRef<Humanoid, ItemData> ChestRef = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_chestItem"); private static readonly FieldRef<Humanoid, ItemData> LegsRef = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_legItem"); private static readonly ArmorClass[] AllClasses = new ArmorClass[3] { ArmorClass.Light, ArmorClass.Medium, ArmorClass.Heavy }; private static readonly float[] Distance = new float[4]; private static float _lastHitTime = -100f; private static string _lastWorn = ""; private static readonly Action<Localization, string, string> AddWord; internal static bool IsOurs(SkillType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 if ((int)type != 1398400001 && (int)type != 1398400002 && (int)type != 1398400003) { return (int)type == 1398400004; } return true; } internal static bool IsArmor(SkillType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 if ((int)type != 1398400001 && (int)type != 1398400002) { return (int)type == 1398400003; } return true; } internal static SkillType SkillOf(ArmorClass c) { return (SkillType)(c switch { ArmorClass.Heavy => 1398400003, ArmorClass.Medium => 1398400002, ArmorClass.Light => 1398400001, _ => 0, }); } internal static string DisplayName(SkillType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 if ((int)type != 1398400001) { if ((int)type != 1398400002) { if ((int)type != 1398400003) { if ((int)type != 1398400004) { return null; } return "Sailing"; } return "Heavy Armor"; } return "Medium Armor"; } return "Light Armor"; } internal static string LocalizationKey(SkillType type) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected I4, but got Unknown return "skill_" + (int)type; } internal static bool TryParse(string name, out SkillType type) { switch ((name ?? "").Replace("_", "").Replace(" ", "").ToLowerInvariant()) { case "lightarmor": type = (SkillType)1398400001; return true; case "mediumarmor": type = (SkillType)1398400002; return true; case "heavyarmor": type = (SkillType)1398400003; return true; case "sailing": type = (SkillType)1398400004; return true; default: type = (SkillType)0; return false; } } internal static Worn GetWorn(Humanoid h) { Worn worn = new Worn { HeavyBlock = -1f }; Add(ref worn, HelmetRef.Invoke(h)); Add(ref worn, ChestRef.Invoke(h)); Add(ref worn, LegsRef.Invoke(h)); return worn; } private static void Add(ref Worn worn, ItemData item) { switch (ArmorClasses.Classify(item)) { case ArmorClass.Light: worn.Light++; break; case ArmorClass.Medium: worn.Medium++; worn.MediumPenalty += Mathf.Min(0f, item.m_shared.m_movementModifier); break; case ArmorClass.Heavy: { worn.Heavy++; worn.HeavyPenalty += Mathf.Min(0f, item.m_shared.m_movementModifier); string source; float num = ArmorClasses.BucklerPower(ArmorClasses.PrefabName(item), out source); if (num > 0f && (worn.HeavyBlock < 0f || num < worn.HeavyBlock)) { worn.HeavyBlock = num; } break; } } } internal static IEnumerable<ItemData> WornPieces(Humanoid h) { yield return HelmetRef.Invoke(h); yield return ChestRef.Invoke(h); yield return LegsRef.Invoke(h); } internal static void OnMove(Player p, Vector3 velocity, float dt) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) Worn worn = GetWorn((Humanoid)(object)p); LogWornChange(p, worn); if (Plugin.XpPerMeter.Value <= 0f || ((Character)p).IsDead() || ((Character)p).IsAttached() || ((Character)p).IsRiding() || p.InDebugFlyMode() || ((Character)p).IsTeleporting() || (!((Character)p).IsOnGround() && !((Character)p).IsSwimming())) { return; } velocity.y = 0f; float num = ((Vector3)(ref velocity)).magnitude * dt; if (num <= 0f || num > 0.5f) { return; } ArmorClass[] allClasses = AllClasses; foreach (ArmorClass armorClass in allClasses) { if (worn.Has(armorClass)) { Distance[(int)armorClass] += num; if (!(Distance[(int)armorClass] < 10f)) { float num2 = Distance[(int)armorClass] * Plugin.XpPerMeter.Value; Distance[(int)armorClass] = 0f; ((Character)p).RaiseSkill(SkillOf(armorClass), num2); Plugin.Debugf($"{DisplayName(SkillOf(armorClass))}: +{num2:0.###} XP for 10 m on foot"); } } } } internal static void OnHit(Player p, HitData hit) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (Plugin.XpPerHit.Value <= 0f || Time.time - _lastHitTime < 0.3f) { return; } Worn worn = GetWorn((Humanoid)(object)p); bool flag = false; ArmorClass[] allClasses = AllClasses; foreach (ArmorClass c in allClasses) { if (worn.Has(c)) { flag = true; ((Character)p).RaiseSkill(SkillOf(c), Plugin.XpPerHit.Value); Plugin.Debugf($"{DisplayName(SkillOf(c))}: +{Plugin.XpPerHit.Value:0.###} XP for a " + $"{hit.GetTotalDamage():0.#} damage hit"); } } if (flag) { _lastHitTime = Time.time; } } private static void LogWornChange(Player p, Worn worn) { if (!Plugin.DebugLog.Value) { return; } string text = $"light {worn.Light}, medium {worn.Medium}, heavy {worn.Heavy}"; if (text == _lastWorn) { return; } _lastWorn = text; List<string> list = new List<string>(); foreach (ItemData item in WornPieces((Humanoid)(object)p)) { if (item != null) { list.Add($"{ArmorClasses.PrefabName(item)} = {ArmorClasses.Classify(item)}"); } } Plugin.Log.LogInfo((object)("Armor worn: " + text + " (" + string.Join(", ", list) + ")")); } internal static void Register(Skills skills) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 AddWords(); Sprite val = null; Sprite val2 = null; foreach (SkillDef skill in skills.m_skills) { if ((int)skill.m_skill == 6) { val = skill.m_icon; } if ((int)skill.m_skill == 103) { val2 = skill.m_icon; } } Register(skills, (SkillType)1398400001, Icon("ArmorMageChest", "ArmorMageChest_Ashlands") ?? val); Register(skills, (SkillType)1398400002, Icon("ArmorTrollLeatherChest", "ArmorRootChest") ?? val); Register(skills, (SkillType)1398400003, Icon("ArmorIronChest", "ArmorBronzeChest") ?? val); Register(skills, (SkillType)1398400004, PieceIcon("Karve", "Raft", "VikingShip") ?? val2 ?? val); } private static void Register(Skills skills, SkillType type, Sprite icon) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown foreach (SkillDef skill in skills.m_skills) { if (skill.m_skill == type) { return; } } skills.m_skills.Add(new SkillDef { m_skill = type, m_icon = icon, m_description = Descriptions.OwnText(type, null), m_increseStep = 1f }); } private static Sprite PieceIcon(params string[] prefabs) { if (!Object.op_Implicit((Object)(object)ZNetScene.instance)) { return null; } foreach (string text in prefabs) { GameObject prefab = ZNetScene.instance.GetPrefab(text); Piece val = (Object.op_Implicit((Object)(object)prefab) ? prefab.GetComponent<Piece>() : null); if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.m_icon)) { return val.m_icon; } } return null; } private static Sprite Icon(params string[] prefabs) { if (!Object.op_Implicit((Object)(object)ObjectDB.instance)) { return null; } foreach (string text in prefabs) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text); ItemDrop val = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>() : null); object obj; if ((Object)(object)val != (Object)null) { ItemData itemData = val.m_itemData; if (itemData != null && itemData.m_shared?.m_icons?.Length > 0) { obj = val.m_itemData.GetIcon(); goto IL_00ac; } } obj = null; goto IL_00ac; IL_00ac: Sprite val2 = (Sprite)obj; if (Object.op_Implicit((Object)(object)val2)) { return val2; } } return null; } internal static void AddWords() { //IL_003a: 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_0047: Unknown result type (might be due to invalid IL or missing references) Localization instance = Localization.instance; if (instance == null) { return; } if (AddWord == null) { Plugin.Log.LogWarning((object)"Localization.AddWord not found; the armor skills show without names."); return; } SkillType[] array = new SkillType[4]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); SkillType[] array2 = (SkillType[])(object)array; foreach (SkillType type in array2) { AddWord(instance, LocalizationKey(type), DisplayName(type)); } } static ArmorSkills() { MethodInfo methodInfo = AccessTools.Method(typeof(Localization), "AddWord", new Type[2] { typeof(string), typeof(string) }, (Type[])null); AddWord = ((methodInfo != null) ? AccessTools.MethodDelegate<Action<Localization, string, string>>(methodInfo, (object)null, true) : null); } } internal static class Combat { private const float VanillaParryWindow = 0.25f; internal static int ParryPatchCount; private static readonly MethodInfo AddInworldText = AccessTools.Method(typeof(DamageText), "AddInworldText", (Type[])null, (Type[])null); internal static bool SwingApplies(SkillType skill) { //IL_000b: 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) //IL_0043: Expected I4, but got Unknown SwingSkills value = Plugin.SwingFor.Value; return (skill - 1) switch { 0 => (value & SwingSkills.Swords) != 0, 1 => (value & SwingSkills.Knives) != 0, 2 => (value & SwingSkills.Clubs) != 0, 3 => (value & SwingSkills.Polearms) != 0, 4 => (value & SwingSkills.Spears) != 0, 6 => (value & SwingSkills.Axes) != 0, 10 => (value & SwingSkills.Unarmed) != 0, 11 => (value & SwingSkills.Pickaxes) != 0, _ => false, }; } internal static AnimationState Style(ItemData weapon) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (weapon?.m_shared == null) { return (AnimationState)0; } return weapon.m_shared.m_animationState; } internal static bool IsGreatsword(ItemData weapon) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 return (int)Style(weapon) == 13; } internal static bool IsBattleaxe(ItemData weapon) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 return (int)Style(weapon) == 8; } internal static float SwingMultiplier(Player p) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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_006e: Invalid comparison between Unknown and I4 //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (!((Character)p).InAttack()) { return 1f; } ItemData currentWeapon = ((Humanoid)p).GetCurrentWeapon(); if (currentWeapon?.m_shared == null) { return 1f; } SkillType skillType = currentWeapon.m_shared.m_skillType; float num = 1f; if (Plugin.SwingSpeed.Value > 0 && SwingApplies(skillType)) { num += (float)Plugin.SwingSpeed.Value / 100f * ((Character)p).GetSkillFactor(skillType); } if ((int)skillType == 7 && IsBattleaxe(currentWeapon)) { num += BerserkBonus(p); } return num; } internal static float BerserkBonus(Player p) { float num = Perks.Bonus(Plugin.BattleaxeBerserk, p, (SkillType)7); if (num <= 0f) { return 0f; } float num2 = 1f - ((Character)p).GetHealthPercentage(); return num * Mathf.Clamp01(num2 / Mathf.Max(0.01f, (float)Plugin.BattleaxeFullAt.Value / 100f)); } public static float ParryWindow(Humanoid h) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)h != (Object)(object)localPlayer || !Object.op_Implicit((Object)(object)localPlayer) || Plugin.ParryWindow.Value <= 0) { return 0.25f; } return 0.25f * (1f + (float)Plugin.ParryWindow.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)6)); } internal static void CheckParryPatch() { if (ParryPatchCount != 1) { Plugin.Log.LogWarning((object)($"Parry window: found {ParryPatchCount} places to patch in Humanoid.BlockAttack " + "(expected 1); the Blocking bonus may not work in this game version.")); } } internal static void ShowLocalText(TextType type, Vector3 pos, string text) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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) DamageText instance = DamageText.instance; Camera mainCamera = Utils.GetMainCamera(); if (Object.op_Implicit((Object)(object)instance) && Object.op_Implicit((Object)(object)mainCamera) && !(AddInworldText == null)) { AddInworldText.Invoke(instance, new object[5] { type, pos, Vector3.Distance(((Component)mainCamera).transform.position, pos), text, true }); } } } [HarmonyPatch(typeof(Character), "Damage")] internal static class OutgoingHitPatch { private const int FlurryStacks = 5; private static int _flurry; private static float _lastKnifeHit = -100f; private static readonly FieldRef<Humanoid, Attack> CurrentAttack = AccessTools.FieldRefAccess<Humanoid, Attack>("m_currentAttack"); private static Attack _brawled; private static void Prefix(Character __instance, HitData hit) { //IL_0013: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Invalid comparison between Unknown and I4 //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Invalid comparison between Unknown and I4 //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Invalid comparison between Unknown and I4 //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Invalid comparison between Unknown and I4 //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Invalid comparison between Unknown and I4 //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || hit == null) { return; } SkillType skill = hit.m_skill; int num = (((int)skill == 9) ? Plugin.ElementalDamage.Value : (((int)skill == 10) ? Plugin.BloodDamage.Value : 0)); bool flag = (int)skill == 2 && Plugin.KnivesFlurry.Value > 0; bool flag2 = (int)skill == 11 && Plugin.UnarmedBrawler.Value > 0; ConfigEntry<int> val = (((int)skill == 3) ? Plugin.ClubsStagger : null); if (val != null && val.Value <= 0) { val = null; } bool flag3 = (int)skill == 1 && Time.time < Perks.RiposteUntil && !Combat.IsGreatsword(((Humanoid)localPlayer).GetCurrentWeapon()); bool flag4 = (int)skill == 7 && Plugin.AxesStaggered.Value > 0 && !Combat.IsBattleaxe(((Humanoid)localPlayer).GetCurrentWeapon()) && __instance.IsStaggering(); bool flag5 = (int)skill == 1 && Plugin.GreatswordBloodthirst.Value > 0 && Combat.IsGreatsword(((Humanoid)localPlayer).GetCurrentWeapon()); bool flag6 = (int)skill == 5 && Plugin.SpearPenetration.Value > 0 && !__instance.IsPlayer(); if ((num > 0 || flag || flag2 || val != null || flag3 || flag4 || flag5 || flag6) && Perks.FromLocal(hit, localPlayer)) { if (flag) { Flurry(hit, localPlayer); } if (flag2) { Brawler(localPlayer); } if (flag6) { Penetrate(__instance, hit, Perks.Bonus(Plugin.SpearPenetration, localPlayer, (SkillType)5)); } if (flag5) { Bloodthirst(__instance, hit, localPlayer); } if (flag4) { ((DamageTypes)(ref hit.m_damage)).Modify(1f + Perks.Bonus(Plugin.AxesStaggered, localPlayer, (SkillType)7)); } if (num > 0) { ((DamageTypes)(ref hit.m_damage)).Modify(1f + (float)num / 100f * ((Character)localPlayer).GetSkillFactor(skill)); } if (val != null) { hit.m_staggerMultiplier *= 1f + Perks.Bonus(val, localPlayer, skill); } if (flag3) { Perks.RiposteUntil = 0f; float num2 = Perks.Bonus(Plugin.SwordsRiposte, localPlayer, (SkillType)1); ((DamageTypes)(ref hit.m_damage)).Modify(1f + num2); Plugin.Debugf($"Riposte: +{num2 * 100f:0.#}% damage"); } } } private static void Flurry(HitData hit, Player p) { float num = Perks.Bonus(Plugin.KnivesFlurry, p, (SkillType)2) / 5f; if (!(num <= 0f)) { if (Time.time - _lastKnifeHit > 2f) { _flurry = 0; } if (_flurry > 0) { ((DamageTypes)(ref hit.m_damage)).Modify(1f + num * (float)_flurry); } _flurry = Mathf.Min(_flurry + 1, 5); _lastKnifeHit = Time.time; } } private static void Brawler(Player p) { Attack val = CurrentAttack.Invoke((Humanoid)(object)p); if (val == null || val != _brawled) { _brawled = val; float num = (float)Plugin.UnarmedBrawler.Value * Perks.Factor(p, (SkillType)11); if (num > 0f) { ((Character)p).AddStamina(num); } } } private static void Penetrate(Character target, HitData hit, float pen) { //IL_0013: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) if (pen <= 0f || !Object.op_Implicit((Object)(object)target)) { return; } DamageModifiers damageModifiers = target.GetDamageModifiers((WeakSpot)null); hit.m_damage.m_blunt *= Pierce(((DamageModifiers)(ref damageModifiers)).GetModifier((DamageType)1), pen); hit.m_damage.m_slash *= Pierce(((DamageModifiers)(ref damageModifiers)).GetModifier((DamageType)2), pen); hit.m_damage.m_pierce *= Pierce(((DamageModifiers)(ref damageModifiers)).GetModifier((DamageType)4), pen); hit.m_damage.m_fire *= Pierce(((DamageModifiers)(ref damageModifiers)).GetModifier((DamageType)32), pen); hit.m_damage.m_frost *= Pierce(((DamageModifiers)(ref damageModifiers)).GetModifier((DamageType)64), pen); hit.m_damage.m_lightning *= Pierce(((DamageModifiers)(ref damageModifiers)).GetModifier((DamageType)128), pen); hit.m_damage.m_poison *= Pierce(((DamageModifiers)(ref damageModifiers)).GetModifier((DamageType)256), pen); hit.m_damage.m_spirit *= Pierce(((DamageModifiers)(ref damageModifiers)).GetModifier((DamageType)512), pen); if (Game.m_worldLevel <= 0 || !Object.op_Implicit((Object)(object)Game.instance)) { return; } int num = Game.m_worldLevel * Game.instance.m_worldLevelEnemyBaseAC; if (num <= 0) { return; } HitData obj = hit.Clone(); DamageModifier val = default(DamageModifier); obj.ApplyResistance(damageModifiers, ref val); float totalDamage = ((DamageTypes)(ref obj.m_damage)).GetTotalDamage(); if (!(totalDamage <= 0f)) { float num2 = DamageTypes.ApplyArmor(totalDamage, (float)num * (1f - pen)); float num3 = ((num2 >= (float)num) ? (num2 + (float)num) : Mathf.Sqrt(4f * (float)num * num2)); if (num3 > totalDamage) { ((DamageTypes)(ref hit.m_damage)).Modify(num3 / totalDamage); } } } private static float Pierce(DamageModifier mod, float pen) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 float num; if ((int)mod != 1) { if ((int)mod != 5) { if ((int)mod != 7) { return 1f; } num = 0.75f; } else { num = 0.25f; } } else { num = 0.5f; } return (num + pen * (1f - num)) / num; } private static void Bloodthirst(Character target, HitData hit, Player p) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)target) || (Object)(object)target == (Object)(object)p || target.IsDead()) { return; } float num = Perks.Bonus(Plugin.GreatswordBloodthirst, p, (SkillType)1); if (!(num <= 0f)) { HitData obj = hit.Clone(); DamageModifier val = default(DamageModifier); obj.ApplyResistance(target.GetDamageModifiers((WeakSpot)null), ref val); float num2 = ((DamageTypes)(ref obj.m_damage)).GetTotalDamage() * num; if (!(num2 < 0.05f)) { ((Character)p).Heal(num2, true); Plugin.Debugf($"Bloodthirst: healed {num2:0.#}"); } } } } [HarmonyPatch] internal static class SummonScopePatch { internal static float Boost; internal static int Extra; private static SpawnAbility _raised; private static readonly FieldRef<SpawnAbility, Character> Owner = AccessTools.FieldRefAccess<SpawnAbility, Character>("m_owner"); private static readonly FieldRef<SpawnAbility, ItemData> Weapon = AccessTools.FieldRefAccess<SpawnAbility, ItemData>("m_weapon"); private static FieldInfo _this; private static MethodBase TargetMethod() { return AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.Method(typeof(SpawnAbility), "Spawn", (Type[])null, (Type[])null)); } private static void Prefix(object __instance) { //IL_009c: 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_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Invalid comparison between Unknown and I4 //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Invalid comparison between Unknown and I4 //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Invalid comparison between Unknown and I4 //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) Boost = 0f; Extra = 0; _raised = null; Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return; } if (_this == null) { _this = AccessTools.GetDeclaredFields(__instance.GetType()).Find((FieldInfo f) => f.FieldType == typeof(SpawnAbility)); } object? obj = _this?.GetValue(__instance); SpawnAbility val = (SpawnAbility)((obj is SpawnAbility) ? obj : null); if (!Object.op_Implicit((Object)(object)val) || (Object)(object)Owner.Invoke(val) != (Object)(object)localPlayer) { return; } SkillType val2 = val.m_copySkill; ItemData val3 = Weapon.Invoke(val); if ((int)val2 == 0 && val3?.m_shared != null) { val2 = val3.m_shared.m_skillType; } int value; if ((int)val2 == 10) { value = Plugin.BloodDamage.Value; } else { if ((int)val2 != 9) { return; } value = Plugin.ElementalDamage.Value; } if (value > 0) { Boost = 1f + (float)value / 100f * ((Character)localPlayer).GetSkillFactor(val2); } if ((int)val2 == 10 && Plugin.BloodExtraSummons.Value > 0 && Perks.Unlocked(localPlayer, val2)) { Extra = Plugin.BloodExtraSummons.Value; if (val.m_maxSpawned > 0) { val.m_maxSpawned += Extra; _raised = val; } } } private static void Finalizer() { if (Object.op_Implicit((Object)(object)_raised)) { SpawnAbility raised = _raised; raised.m_maxSpawned -= Extra; } _raised = null; Boost = 0f; Extra = 0; } } [HarmonyPatch(typeof(Tameable), "Command")] internal static class SummonCommandPatch { private static void Prefix(Tameable __instance) { if (SummonScopePatch.Boost <= 1f && SummonScopePatch.Extra <= 0) { return; } ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); ZDO val = (Object.op_Implicit((Object)(object)component) ? component.GetZDO() : null); if (val == null || !component.IsOwner()) { return; } if (SummonScopePatch.Boost > 1f) { float num = val.GetFloat(ZDOVars.s_randomSkillFactor, 1f); val.Set(ZDOVars.s_randomSkillFactor, num * SummonScopePatch.Boost); Plugin.Debugf($"Summon {((Object)__instance).name}: damage factor {num:0.##} -> {num * SummonScopePatch.Boost:0.##}"); } if (SummonScopePatch.Extra > 0) { int num2 = val.GetInt(ZDOVars.s_maxInstances, 0); if (num2 > 0) { val.Set(ZDOVars.s_maxInstances, num2 + SummonScopePatch.Extra, false); Plugin.Debugf($"Summon {((Object)__instance).name}: max summons {num2} -> {num2 + SummonScopePatch.Extra}"); } } } } [HarmonyPatch(typeof(Character), "RPC_Damage")] internal static class IncomingHitPatch { private static bool Prefix(Character __instance, HitData hit) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Invalid comparison between Unknown and I4 //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Invalid comparison between Unknown and I4 //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || (Object)(object)__instance != (Object)(object)localPlayer || hit == null || ((Character)localPlayer).IsDead()) { return true; } if ((int)hit.m_hitType == 3) { if (Plugin.FallDamage.Value > 0) { ((DamageTypes)(ref hit.m_damage)).Modify(Mathf.Clamp01(1f - (float)Plugin.FallDamage.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)100))); } return true; } if ((int)hit.m_hitType != 1 && (int)hit.m_hitType != 2) { return true; } if ((int)hit.m_hitType == 2 && !((Character)localPlayer).IsPVPEnabled()) { return true; } if (((Character)localPlayer).IsDodgeInvincible()) { return true; } if (Plugin.DodgeEvade.Value > 0 && !((Character)localPlayer).IsBlocking() && Random.value < (float)Plugin.DodgeEvade.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)108)) { Combat.ShowLocalText((TextType)3, ((Character)localPlayer).GetTopPoint(), "Evaded"); Plugin.Debugf($"Evaded a {hit.GetTotalDamage():0.#} damage hit"); return false; } if (Plugin.HeavyBlockChance.Value > 0 && !((Character)localPlayer).IsBlocking()) { ArmorSkills.Worn worn = ArmorSkills.GetWorn((Humanoid)(object)localPlayer); if (worn.Has(ArmorClass.Heavy) && worn.HeavyBlock > 0f && Random.value < (float)Plugin.HeavyBlockChance.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)1398400003)) { float num = Mathf.Min(hit.GetTotalBlockableDamage(), worn.HeavyBlock); if (num > 0f) { hit.BlockDamage(num); Combat.ShowLocalText((TextType)6, hit.m_point + Vector3.up * 0.5f, num.ToString("0.#", CultureInfo.InvariantCulture)); Plugin.Debugf($"Heavy armor blocked {num:0.#} of a hit (block power {worn.HeavyBlock:0.#})"); } } } return true; } } [HarmonyPatch(typeof(CharacterAnimEvent), "CustomFixedUpdate")] internal static class SwingSpeedPatch { private static float _base = 1f; private static float _applied = -1f; private static void Postfix(Character ___m_character, Animator ___m_animator) { Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || (Object)(object)___m_character != (Object)(object)localPlayer || !Object.op_Implicit((Object)(object)___m_animator)) { return; } float speed = ___m_animator.speed; if (speed < 0.01f) { return; } bool flag = _applied > 0f && Mathf.Abs(speed - _applied) < 0.0001f; float num = (flag ? _base : speed); float num2 = Combat.SwingMultiplier(localPlayer); if (num2 <= 1f) { if (flag) { ___m_animator.speed = num; } _applied = -1f; } else { _base = num; _applied = num * num2; ___m_animator.speed = _applied; } } } [HarmonyPatch(typeof(Attack), "UseAmmo")] internal static class UseAmmoPatch { internal static bool Keep; internal static bool KeepIsBait; internal static bool BaitKept; private static void Prefix(Humanoid ___m_character, ItemData ___m_weapon) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Invalid comparison between Unknown and I4 //IL_008a: 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_009c: Invalid comparison between Unknown and I4 Keep = false; KeepIsBait = false; Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || (Object)(object)___m_character != (Object)(object)localPlayer || ___m_weapon?.m_shared == null) { return; } SkillType skillType = ___m_weapon.m_shared.m_skillType; int value; if ((int)skillType != 8) { if ((int)skillType != 14) { if ((int)skillType != 104) { return; } value = Plugin.BaitSave.Value; BaitKept = false; } else { value = Plugin.CrossbowAmmo.Value; } } else { value = Plugin.BowAmmo.Value; } if (value > 0 && Random.value < (float)value / 100f * ((Character)localPlayer).GetSkillFactor(skillType)) { Keep = true; KeepIsBait = (int)skillType == 104; } } private static void Finalizer() { Keep = false; } } [HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[] { typeof(ItemData), typeof(int) })] internal static class KeepAmmoPatch { private static bool Prefix(ItemData item, int amount, ref bool __result) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 if (!UseAmmoPatch.Keep) { return true; } UseAmmoPatch.Keep = false; if (item?.m_shared == null || (int)item.m_shared.m_itemType == 2) { return true; } Plugin.Debugf($"Kept {item.m_shared.m_name} (would have used {amount})"); if (UseAmmoPatch.KeepIsBait) { UseAmmoPatch.BaitKept = true; } __result = true; return false; } } [HarmonyPatch(typeof(FishingFloat), "Setup")] internal static class LocalFloatPatch { internal static FishingFloat Local; private static void Postfix(FishingFloat __instance, Character owner, ref bool ___m_baitConsumed) { Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && !((Object)(object)owner != (Object)(object)localPlayer)) { Local = __instance; if (UseAmmoPatch.BaitKept) { UseAmmoPatch.BaitKept = false; ___m_baitConsumed = true; } } } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] internal static class ParryWindowPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown FieldInfo fieldInfo = AccessTools.Field(typeof(Humanoid), "m_blockTimer"); MethodInfo methodInfo = AccessTools.Method(typeof(Combat), "ParryWindow", (Type[])null, (Type[])null); List<CodeInstruction> list = new List<CodeInstruction>(instructions); Combat.ParryPatchCount = 0; for (int i = 0; i < list.Count - 1; i++) { if (CodeInstructionExtensions.LoadsField(list[i], fieldInfo, false) && !(list[i + 1].opcode != OpCodes.Ldc_R4) && list[i + 1].operand is float num && !(Mathf.Abs(num - 0.25f) > 1E-06f)) { list[i + 1].opcode = OpCodes.Ldarg_0; list[i + 1].operand = null; list.Insert(i + 2, new CodeInstruction(OpCodes.Call, (object)methodInfo)); Combat.ParryPatchCount++; } } return list; } } internal sealed class ConfigurationManagerAttributes { public string Category; public string DispName; public int? Order; } internal static class Descriptions { private sealed class Bonus { public string Name; public string Sign = "+"; public string Unit = "%"; public float Max; public float Now; public bool Perk; public bool Locked; public string Fixed; } private sealed class PerkDef { public SkillType Skill; public ConfigEntry<int> Entry; public string Name; public string Sign; public string Unit = "%"; } private const string Header = "<color=#FFD27F>Useful Skills</color>"; private const string ArmorIntro = "Trains and gives its bonus while"; private static readonly ConditionalWeakTable<SkillDef, string> Original = new ConditionalWeakTable<SkillDef, string>(); private static readonly FieldRef<Skills, Dictionary<SkillType, Skill>> SkillData = AccessTools.FieldRefAccess<Skills, Dictionary<SkillType, Skill>>("m_skillData"); private static List<PerkDef> _perks; internal static bool IsOurs(string text) { if (text != null) { if (!text.Contains("<color=#FFD27F>Useful Skills</color>")) { return text.Contains("Trains and gives its bonus while"); } return true; } return false; } private static string Title(string text) { return "<color=#FFD27F>" + text + "</color>"; } private static List<PerkDef> AllPerks() { object obj = _perks; if (obj == null) { obj = new List<PerkDef> { P((SkillType)1, Plugin.SwordsRiposte, "one-handed swords: riposte damage after a parry", "+"), P((SkillType)1, Plugin.GreatswordBloodthirst, "greatswords: damage dealt returned as health", ""), P((SkillType)2, Plugin.KnivesFlurry, "flurry: damage after 5 knife hits in a row", "+"), P((SkillType)3, Plugin.ClubsStagger, "stagger", "+"), P((SkillType)5, Plugin.SpearThrowSpeed, "thrown spear speed", "+"), P((SkillType)5, Plugin.SpearThrowDamage, "thrown spear damage", "+"), P((SkillType)5, Plugin.SpearPenetration, "armor penetration (resistances ignored)", ""), P((SkillType)8, Plugin.BowArrowSpeed, "arrow speed", "+"), P((SkillType)14, Plugin.CrossbowReload, "reload time", "-"), P((SkillType)9, Plugin.ElementalFreeCast, "chance a cast costs no eitr", ""), P((SkillType)6, Plugin.BlockAdrenaline, "adrenaline from parries", "+"), P((SkillType)108, Plugin.DodgeAdrenaline, "adrenaline from perfect dodges", "+"), P((SkillType)101, Plugin.SneakUnseen, "unseen: how visible you are while crouched", "-"), P((SkillType)103, Plugin.SwimStamina, "swim stamina cost", "-"), P((SkillType)100, Plugin.JumpStamina, "jump stamina cost", "-"), P((SkillType)13, Plugin.ChopStamina, "stamina back when a swing hits a tree", ""), P((SkillType)12, Plugin.OreDrops, "ore and scrap from rocks", "+"), P((SkillType)104, Plugin.FishingBiteTime, "bite time: how long fish near your float take to bite", "-"), P((SkillType)105, Plugin.RestedDuration, "rested duration", "+"), P((SkillType)107, Plugin.CraftThrifty, "thrifty upgrades: materials back when upgrading", ""), P((SkillType)1398400003, Plugin.HeavyStagger, "stagger resistance", "+"), P((SkillType)4, Plugin.PolearmsWhirlwind, "whirlwind: spin attack stamina cost", "-"), P((SkillType)7, Plugin.AxesStaggered, "one-handed and dual axes: damage to staggered enemies", "+"), P((SkillType)7, Plugin.BattleaxeBerserk, "battleaxes: swing speed when hurt (all of it at half health)", "+"), P((SkillType)11, Plugin.UnarmedBrawler, "brawler: stamina back per unarmed hit", "+", ""), P((SkillType)102, Plugin.RunRegenDelay, "stamina regen delay", "-"), P((SkillType)106, Plugin.HarvestChance, "bonus harvest chance", "+"), P((SkillType)1398400004, Plugin.SailShipDamage, "damage to the ship you steer", "-"), P((SkillType)1398400001, Plugin.LightMoveSpeed, "movement speed", "+") }; _perks = (List<PerkDef>)obj; } return (List<PerkDef>)obj; } private static PerkDef P(SkillType skill, ConfigEntry<int> entry, string name, string sign, string unit = "%") { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) return new PerkDef { Skill = skill, Entry = entry, Name = name, Sign = sign, Unit = unit }; } internal static void Apply(Skills skills, Player player) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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) if ((Object)(object)skills == (Object)null) { return; } foreach (SkillDef skill in skills.m_skills) { if (skill == null) { continue; } if (ArmorSkills.IsOurs(skill.m_skill)) { skill.m_description = OwnText(skill.m_skill, player); continue; } string value = Original.GetValue(skill, (SkillDef d) => d.m_description); string text = Blocks(skill.m_skill, player, MainBonuses(skill.m_skill, player), null); skill.m_description = ((text == null) ? value : (value + "\n\n" + text)); } } internal static string PerkNames(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) List<string> list = (from b in PerkBonuses(skill, null) select b.Fixed ?? b.Name).ToList(); if (list.Count != 0) { return string.Join(", ", list); } return null; } private static bool Has(Player p, SkillType skill) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) return (Object.op_Implicit((Object)(object)p) ? SkillData.Invoke(((Character)p).GetSkills()) : null)?.ContainsKey(skill) ?? false; } private static float BaseLevel(Player p, SkillType skill) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) Dictionary<SkillType, Skill> dictionary = (Object.op_Implicit((Object)(object)p) ? SkillData.Invoke(((Character)p).GetSkills()) : null); if (dictionary == null || !dictionary.TryGetValue(skill, out var value)) { return 0f; } return value.m_level; } private static float Factor(Player p, SkillType skill) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (!Has(p, skill)) { return 0f; } return ((Character)p).GetSkillFactor(skill); } private static bool PerkLocked(Player p, SkillType skill) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (Has(p, skill)) { return ((Character)p).GetSkillLevel(skill) < (float)Plugin.PerkLevel.Value; } return true; } private static Bonus Main(ConfigEntry<int> entry, string name, string sign, Player p, SkillType skill, string unit = "%") { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (entry.Value > 0) { return new Bonus { Name = name, Sign = sign, Unit = unit, Max = entry.Value, Now = (float)entry.Value * Factor(p, skill) }; } return null; } private static List<Bonus> PerkBonuses(SkillType skill, Player p) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Invalid comparison between Unknown and I4 //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Invalid comparison between Unknown and I4 bool locked = PerkLocked(p, skill); float factor = (locked ? 0f : Factor(p, skill)); List<Bonus> list = (from d in AllPerks() where d.Skill == skill && d.Entry.Value > 0 select new Bonus { Name = d.Name, Sign = d.Sign, Unit = d.Unit, Max = d.Entry.Value, Now = (float)d.Entry.Value * factor, Perk = true, Locked = locked }).ToList(); if ((int)skill == 10 && Plugin.BloodExtraSummons.Value > 0) { list.Add(new Bonus { Fixed = $"+{Plugin.BloodExtraSummons.Value} summon at once", Perk = true, Locked = locked }); } if ((int)skill == 1398400002 && Plugin.MediumCarryWeight.Value > 0) { list.Add(new Bonus { Name = "carry weight", Unit = "", Max = Plugin.MediumCarryWeight.Value, Now = (float)Plugin.MediumCarryWeight.Value * factor, Perk = true, Locked = locked }); } return list; } private static List<Bonus> MainBonuses(SkillType skill, Player p) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected I4, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_010e: 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_0246: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected I4, but got Unknown //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Invalid comparison between Unknown and I4 //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Invalid comparison between Unknown and I4 //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Invalid comparison between Unknown and I4 //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_03cf: Unknown result type (might be due to invalid IL or missing references) //IL_03d5: Invalid comparison between Unknown and I4 //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_03bd: Unknown result type (might be due to invalid IL or missing references) //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_0409: Unknown result type (might be due to invalid IL or missing references) List<Bonus> list = new List<Bonus>(); switch (skill - 1) { default: switch (skill - 100) { case 8: Add(Main(Plugin.DodgeEvade, "chance to evade a hit", "", p, skill)); break; case 2: Add(Main(Plugin.RunRegen, "stamina regen", "+", p, skill)); break; case 1: Add(Main(Plugin.SneakSpeed, "sneaking speed", "+", p, skill)); break; case 3: Add(Main(Plugin.SwimSpeed, "swimming speed", "+", p, skill)); break; case 0: Add(Main(Plugin.FallDamage, "fall damage", "-", p, skill)); break; case 6: Add(Main(Plugin.SeedSave, "chance not to use the seed when planting", "", p, skill)); break; case 4: Add(Main(Plugin.BaitSave, "chance not to use the bait", "", p, skill)); break; case 5: Add(Main(Plugin.FoodDuration, "food duration", "+", p, skill)); break; case 7: Add(Main(Plugin.Durability, "durability loss of your equipment", "-", p, skill)); break; } break; case 0: case 1: case 2: case 3: case 4: case 6: case 10: case 11: if (Combat.SwingApplies(skill)) { Add(Main(Plugin.SwingSpeed, "swing speed", "+", p, skill)); } break; case 7: Add(Main(Plugin.BowAmmo, "chance not to use the arrow", "", p, skill)); break; case 13: Add(Main(Plugin.CrossbowAmmo, "chance not to use the bolt", "", p, skill)); break; case 8: Add(Main(Plugin.ElementalDamage, "staff damage", "+", p, skill)); break; case 9: Add(Main(Plugin.BloodDamage, "staff and summon damage", "+", p, skill)); break; case 5: Add(Main(Plugin.ParryWindow, "parry window", "+", p, skill)); break; case 12: Add(Main(Plugin.WoodDrops, "wood from trees, logs and stumps", "+", p, skill)); break; } if ((int)skill == 1398400001) { Add(Main(Plugin.LightEitrRegen, "eitr regen", "+", p, skill)); } if ((int)skill == 1398400002) { Add(Main(Plugin.MediumAttackStamina, "attack stamina cost", "-", p, skill)); Add(Main(Plugin.MediumRunStamina, "sprint stamina cost", "-", p, skill)); Add(Main(Plugin.MediumPenalty, "of your medium pieces' speed penalty removed (if any)", "", p, skill)); } if ((int)skill == 1398400003) { Add(Main(Plugin.HeavyPenalty, "of your heavy pieces' speed penalty removed", "", p, skill)); Add(Main(Plugin.HeavyBlockChance, "chance to block a hit while no shield is up", "", p, skill)); } if ((int)skill == 1398400004) { Add(Main(Plugin.SailSpeed, "sail and paddle power", "+", p, skill)); Add(Main(Plugin.SailTurn, "turning", "+", p, skill)); } return list; void Add(Bonus b) { if (b != null) { list.Add(b); } } } private static string Describe(Bonus b) { if (b.Fixed == null) { return $"- {b.Name} {b.Sign}{b.Max:0.#}{b.Unit} at level 100"; } return "- " + b.Fixed; } private static string Current(Bonus b) { if (b.Locked) { return $"- {b.Fixed ?? b.Name}: unlocks at level {Plugin.PerkLevel.Value}"; } if (b.Fixed == null) { return $"- {b.Name} {b.Sign}{b.Now:0.#}{b.Unit}"; } return "- " + b.Fixed; } private static string Blocks(SkillType skill, Player p, List<Bonus> main, List<string> extraNow) { //IL_0000: 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) List<Bonus> list = PerkBonuses(skill, p); List<int> list2 = Checkpoints.Levels(); if (main.Count == 0 && list.Count == 0 && list2.Count == 0) { return null; } StringBuilder stringBuilder = new StringBuilder("<align=left>").Append("<color=#FFD27F>Useful Skills</color>"); foreach (Bonus item in main) { stringBuilder.Append('\n').Append(Describe(item)); } if (list.Count > 0) { stringBuilder.Append('\n').Append(Title($"Perks from level {Plugin.PerkLevel.Value}")); foreach (Bonus item2 in list) { stringBuilder.Append('\n').Append(Describe(item2)); } } if (list2.Count > 0) { stringBuilder.Append('\n').Append("- checkpoints at levels " + string.Join(", ", list2) + ": death can't take the skill below one it has reached"); } if (!Object.op_Implicit((Object)(object)p)) { return stringBuilder.ToString(); } float num = BaseLevel(p, skill); stringBuilder.Append("\n\n").Append(Title($"Now (level {(int)num})")); if (extraNow != null) { foreach (string item3 in extraNow) { stringBuilder.Append('\n').Append(item3); } } foreach (Bonus item4 in main) { stringBuilder.Append('\n').Append(Current(item4)); } foreach (Bonus item5 in list) { stringBuilder.Append('\n').Append(Current(item5)); } if (list2.Count > 0) { int num2 = Checkpoints.Floor(num); int num3 = Checkpoints.Next(num); string text = ((num2 > 0) ? $"- checkpoint {num2} reached" : "- no checkpoint reached"); if (num3 > 0) { text += $", next at {num3}"; } stringBuilder.Append('\n').Append(text); } return stringBuilder.ToString(); } internal static string OwnText(SkillType type, Player p) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Invalid comparison between Unknown and I4 //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Invalid comparison between Unknown and I4 //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Invalid comparison between Unknown and I4 List<string> list = null; string text; if ((int)type == 1398400004) { text = "Steering a ship. Experience: " + $"{Plugin.SailXpPerMeter.Value:0.###} per meter the ship " + "you steer travels."; } else { string text2 = (((int)type == 1398400001) ? "light" : (((int)type == 1398400002) ? "medium" : "heavy")); int value = Plugin.PiecesNeeded.Value; text = string.Format("{0} at least {1} of your helmet, chest and legs are {2} armor (capes ", "Trains and gives its bonus while", value, text2) + $"don't count). Experience: {Plugin.XpPerMeter.Value:0.###} per meter on foot, " + $"{Plugin.XpPerHit.Value:0.##} per hit taken."; if (Object.op_Implicit((Object)(object)p)) { ArmorSkills.Worn worn = ArmorSkills.GetWorn((Humanoid)(object)p); int num = (((int)type == 1398400001) ? worn.Light : (((int)type == 1398400002) ? worn.Medium : worn.Heavy)); list = new List<string> { string.Format("- wearing {0} {1} piece{2}: ", num, text2, (num == 1) ? "" : "s") + ((num >= value) ? "bonuses active" : $"inactive (needs {value})") }; if ((int)type == 1398400003 && Plugin.HeavyBlockChance.Value > 0 && worn.HeavyBlock > 0f) { list.Add($"- a blocked hit loses {worn.HeavyBlock:0.#} damage (your armor's buckler)"); } } } string text3 = Blocks(type, p, MainBonuses(type, p), list); text = "<align=left>" + text; if (text3 != null) { return text + "\n\n" + text3; } return text; } } internal enum DropKind { None, Wood, Ore } [HarmonyPatch] internal static class DropScopePatch { internal static DropKind Kind; private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(TreeBase), "RPC_Damage", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(TreeLog), "Destroy", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Destructible), "Destroy", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(MineRock5), "DamageArea", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(MineRock), "RPC_Hit", (Type[])null, (Type[])null); } private static void Prefix(object __instance, object[] __args, out DropKind __state) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 __state = Kind; Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return; } DropKind dropKind; if (__instance is TreeBase || __instance is TreeLog) { dropKind = DropKind.Wood; } else { Destructible val = (Destructible)((__instance is Destructible) ? __instance : null); dropKind = ((val == null) ? DropKind.Ore : (((int)val.GetDestructibleType() == 2) ? DropKind.Wood : DropKind.Ore)); } if ((dropKind == DropKind.Wood) ? (Plugin.WoodDrops.Value <= 0) : (Plugin.OreDrops.Value <= 0)) { return; } HitData hit = null; foreach (object obj in __args) { HitData val2 = (HitData)((obj is HitData) ? obj : null); if (val2 != null) { hit = val2; } } if (Perks.FromLocal(hit, localPlayer)) { Kind = dropKind; } } private static void Finalizer(DropKind __state) { Kind = __state; } } [HarmonyPatch(typeof(DropTable), "GetDropList", new Type[] { })] internal static class ExtraDropsPatch { private static void Postfix(List<GameObject> __result) { DropKind kind = DropScopePatch.Kind; if (kind == DropKind.None || __result == null) { return; } Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return; } float num = ((kind == DropKind.Wood) ? ((float)Plugin.WoodDrops.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)13)) : Perks.Bonus(Plugin.OreDrops, localPlayer, (SkillType)12)); if (num <= 0f) { return; } int count = __result.Count; int num2 = 0; for (int i = 0; i < count; i++) { GameObject val = __result[i]; if (Object.op_Implicit((Object)(object)val) && !(Random.value >= num) && !((kind == DropKind.Wood) ? (!ArmorClasses.IsWood(((Object)val).name)) : (!ArmorClasses.IsOre(((Object)val).name)))) { __result.Add(val); num2++; } } if (num2 > 0) { Plugin.Debugf(string.Format("{0}: +{1} ({2} dropped)", (kind == DropKind.Wood) ? "Woodcutting" : "Pickaxes", num2, count)); } } } [HarmonyPatch(typeof(Player), "UpdatePlacement")] internal static class SeedScopePatch { internal static bool Placing; private static void Prefix(Player __instance) { Placing = (Object)(object)__instance == (Object)(object)Player.m_localPlayer; } private static void Finalizer() { Placing = false; } } [HarmonyPatch(typeof(Player), "ConsumeResources")] internal static class SeedRefundPatch { private static void Postfix(Player __instance, Requirement[] requirements, int multiplier) { if (!SeedScopePatch.Placing || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || requirements == null || Plugin.SeedSave.Value <= 0) { return; } Piece selectedPiece = __instance.GetSelectedPiece(); if (!Object.op_Implicit((Object)(object)selectedPiece) || !Object.op_Implicit((Object)(object)((Component)selectedPiece).GetComponent<Plant>()) || Random.value >= (float)Plugin.SeedSave.Value / 100f * ((Character)__instance).GetSkillFactor((SkillType)106)) { return; } foreach (Requirement val in requirements) { if (!((Object)(object)val?.m_resItem == (Object)null) && val.m_amount > 0) { int num = val.m_amount * Mathf.Max(1, multiplier); Perks.Give(__instance, ((Component)val.m_resItem).gameObject, num); Plugin.Debugf($"Farming: gave back {num} {((Object)val.m_resItem).name}"); } } } } [HarmonyPatch(typeof(Player), "UpdateFood")] internal static class FoodDurationPatch { private static void Prefix(Player __instance, float dt, ref float ___m_foodUpdateTimer) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && Plugin.FoodDuration.Value > 0 && !(dt <= 0f)) { float num = 1f + (float)Plugin.FoodDuration.Value / 100f * ((Character)__instance).GetSkillFactor((SkillType)105); ___m_foodUpdateTimer -= dt * Game.m_foodRate * (1f - 1f / num); } } } [HarmonyPatch] internal static class DurabilityPatch { private static readonly FieldRef<Attack, Humanoid> AttackCharacter = AccessTools.FieldRefAccess<Attack, Humanoid>("m_character"); private static readonly FieldRef<Humanoid, ItemData>[] Slots = new FieldRef<Humanoid, ItemData>[8] { AccessTools.FieldRefAccess<Humanoid, ItemData>("m_rightItem"), AccessTools.FieldRefAccess<Humanoid, ItemData>("m_leftItem"), AccessTools.FieldRefAccess<Humanoid, ItemData>("m_chestItem"), AccessTools.FieldRefAccess<Humanoid, ItemData>("m_legItem"), AccessTools.FieldRefAccess<Humanoid, ItemData>("m_helmetItem"), AccessTools.FieldRefAccess<Humanoid, ItemData>("m_shoulderItem"), AccessTools.FieldRefAccess<Humanoid, ItemData>("m_utilityItem"), AccessTools.FieldRefAccess<Humanoid, ItemData>("m_trinketItem") }; private static readonly ItemData[] Items = (ItemData[])(object)new ItemData[8]; private static readonly float[] Before = new float[8]; private static bool _active; private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(Attack), "DoMeleeAttack", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Attack), "DoAreaAttack", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Attack), "ProjectileAttackTriggered", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Attack), "DoNonAttack", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Humanoid), "BlockAttack", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Humanoid), "DrainEquipedItemDurability", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Player), "DamageArmorDurability", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Player), "UpdatePlacement", (Type[])null, (Type[])null); } private static void Prefix(object __instance, out bool __state) { __state = false; if (_active || Plugin.Durability.Value <= 0) { return; } Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return; } Attack val = (Attack)((__instance is Attack) ? __instance : null); if (!((Object)((val != null) ? AttackCharacter.Invoke(val) : ((__instance is Humanoid) ? __instance : null)) != (Object)(object)localPlayer)) { for (int i = 0; i < Slots.Length; i++) { ItemData val2 = Slots[i].Invoke((Humanoid)(object)localPlayer); Items[i] = val2; Before[i] = val2?.m_durability ?? 0f; } _active = true; __state = true; } } private static void Finalizer(bool __state) { if (!__state) { return; } _active = false; Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return; } float num = (float)Plugin.Durability.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)107); for (int i = 0; i < Items.Length; i++) { ItemData val = Items[i]; Items[i] = null; if (val != null && val == Slots[i].Invoke((Humanoid)(object)localPlayer) && !(val.m_durability >= Before[i])) { val.m_durability += (Before[i] - val.m_durability) * num; } } } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] internal static class ArmorTooltipPatch { private static void Postfix(ItemData item, bool appending, ref string __result) { if (appending || item?.m_shared == null || !ArmorClasses.IsArmorSlot(item)) { return; } Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer)) { string text = Block(item, localPlayer); if (text != null) { __result += text; } } } private static string ShieldName(string prefab) { GameObject val = ((prefab != null && Object.op_Implicit((Object)(object)ObjectDB.instance)) ? ObjectDB.instance.GetItemPrefab(prefab) : null); string text = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<ItemDrop>() : null)?.m_itemData?.m_shared?.m_name; if (text != null) { return Localization.instance.Localize(text); } return null; } private static string Pct(float fraction) { return (fraction * 100f).ToString("+0.#;-0.#;0", CultureInfo.InvariantCulture) + "%"; } private static string Block(ItemData item, Player p) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) ArmorClass armorClass = ArmorClasses.Classify(item); StringBuilder stringBuilder = new StringBuilder("\n\n<color=#FFD27F>Useful Skills</color>"); object obj; switch (armorClass) { case ArmorClass.None: stringBuilder.Append(": doesn't count for the armor skills (no armor)."); return stringBuilder.ToString(); default: obj = "heavy"; break; case ArmorClass.Medium: obj = "medium"; break; case ArmorClass.Light: obj = "light"; break; } string text = (string)obj; SkillType val = ArmorSkills.SkillOf(armorClass); float skillFactor = ((Character)p).GetSkillFactor(val); ArmorSkills.Worn worn = ArmorSkills.GetWorn((Humanoid)(object)p); int num = armorClass switch { ArmorClass.Medium => worn.Medium, ArmorClass.Light => worn.Light, _ => worn.Heavy, }; int value = Plugin.PiecesNeeded.Value; bool flag = worn.Has(armorClass); stringBuilder.Append($": {text} armor, {ArmorSkills.DisplayName(val)} {(int)((Character)p).GetSkillLevel(val)}"); stringBuilder.Append($"\n{num} of {value} {text} pieces worn: bonuses ").Append(flag ? "<color=orange>active</color>" : "inactive"); float movementModifier = item.m_shared.m_movementModifier; int num2 = armorClass switch { ArmorClass.Medium => Plugin.MediumPenalty.Value, ArmorClass.Heavy => Plugin.HeavyPenalty.Value, _ => 0, }; if (movementModifier < 0f && num2 > 0) { float num3 = (float)num2 / 100f * skillFactor; float fraction = movementModifier * (1f - num3); if (flag) { stringBuilder.Append("\nMovement speed: <color=orange>" + Pct(fraction) + "</color> (vanilla " + Pct(movementModifier) + ")"); } else { stringBuilder.Append($"\nMovement speed: {Pct(movementModifier)}; {Pct(fraction)} once {value} {text} pieces are worn"); } } if (armorClass == ArmorClass.Light && Plugin.LightEitrRegen.Value > 0) { stringBuilder.Append("\nEitr regen " + Pct((float)Plugin.LightEitrRegen.Value / 100f * skillFactor)); } if (armorClass == ArmorClass.Medium) { if (Plugin.MediumAttackStamina.Value > 0) { stringBuilder.Append("\nAttack stamina " + Pct((float)(-Plugin.MediumAttackStamina.Value) / 100f * skillFactor)); } if (Plugin.MediumRunStamina.Value > 0) { stringBuilder.Append(", sprint stamina " + Pct((float)(-Plugin.MediumRunStamina.Value) / 100f * skillFactor)); } } if (armorClass == ArmorClass.Heavy && Plugin.HeavyBlockChance.Value > 0) { string prefab = ArmorClasses.PrefabName(item); string source; float num4 = ArmorClasses.BucklerPower(prefab, out source); if (num4 > 0f) { float fraction2 = (float)Plugin.HeavyBlockChance.Value / 100f * skillFactor; string text2 = ShieldName(ArmorClasses.BucklerShieldOf(prefab)); stringBuilder.Append($"\nArmor block: {Pct(fraction2).TrimStart(new char[1] { '+' })} chance, {num4:0.#} damage").Append((text2 != null) ? (" (like the " + text2 + ")") : ""); } else { stringBuilder.Append("\nArmor block: none for this piece"); } } return stringBuilder.ToString(); } } [HarmonyPatch(typeof(Player), "UpdateModifiers")] internal static class EquipmentModifiersPatch { private const int MovementIndex = 0; private const int JumpStaminaIndex = 3; private const int AttackStaminaIndex = 4; private const int SwimStaminaIndex = 7; private const int RunStaminaIndex = 9; private static void Postfix(Player __instance, float[] ___m_equipmentModifierValues) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || ___m_equipmentModifierValues == null || ___m_equipmentModifierValues.Length <= 9) { return; } ArmorSkills.Worn worn = ArmorSkills.GetWorn((Humanoid)(object)__instance); if (worn.Has(ArmorClass.Heavy) && worn.HeavyPenalty < 0f && Plugin.HeavyPenalty.Value > 0) { ___m_equipmentModifierValues[0] -= worn.HeavyPenalty * (float)Plugin.HeavyPenalty.Value / 100f * ((Character)__instance).GetSkillFactor((SkillType)1398400003); } if (worn.Has(ArmorClass.Medium)) { float skillFactor = ((Character)__instance).GetSkillFactor((SkillType)1398400002); ___m_equipmentModifierValues[4] -= (float)Plugin.MediumAttackStamina.Value / 100f * skillFactor; ___m_equipmentModifierValues[9] -= (float)Plugin.MediumRunStamina.Value / 100f * skillFactor; if (worn.MediumPenalty < 0f) { ___m_equipmentModifierValues[0] -= worn.MediumPenalty * (float)Plugin.MediumPenalty.Value / 100f * skillFactor; } } ___m_equipmentModifierValues[3] -= Perks.Bonus(Plugin.JumpStamina, __instance, (SkillType)100); ___m_equipmentModifierValues[7] -= Perks.Bonus(Plugin.SwimStamina, __instance, (SkillType)103); if (worn.Has(ArmorClass.Light)) { ___m_equipmentModifierValues[0] += Perks.Bonus(Plugin.LightMoveSpeed, __instance, (SkillType)1398400001); } } } [HarmonyPatch(typeof(Player), "GetEquipmentEitrRegenModifier")] internal static class LightArmorEitrPatch { private static void Postfix(Player __instance, ref float __result) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && Plugin.LightEitrRegen.Value > 0 && ArmorSkills.GetWorn((Humanoid)(object)__instance).Has(ArmorClass.Light)) { __result += (float)Plugin.LightEitrRegen.Value / 100f * ((Character)__instance).GetSkillFactor((SkillType)1398400001); } } } [HarmonyPatch(typeof(SEMan), "ModifyStaminaRegen")] internal static class RunRegenPatch { private static void Postfix(Character ___m_character, ref float staminaMultiplier) { Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && !((Object)(object)___m_character != (Object)(object)localPlayer) && Plugin.RunRegen.Value > 0) { staminaMultiplier += (float)Plugin.RunRegen.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)102); } } } [HarmonyPatch(typeof(Character), "UpdateSwimming")] internal static class SwimSpeedPatch { private static void Prefix(Character __instance, out float __state) { __state = -1f; Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && !((Object)(object)__instance != (Object)(object)localPlayer) && Plugin.SwimSpeed.Value > 0) { __state = __instance.m_swimSpeed; __instance.m_swimSpeed *= 1f + (float)Plugin.SwimSpeed.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)103); } } private static void Finalizer(Character __instance, float __state) { if (__state >= 0f) { __instance.m_swimSpeed = __state; } } } [HarmonyPatch(typeof(Character), "UpdateWalking")] internal static class SneakSpeedPatch { private static void Prefix(Character __instance, out float __state) { __state = -1f; Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && !((Object)(object)__instance != (Object)(object)localPlayer) && Plugin.SneakSpeed.Value > 0 && ((Character)localPlayer).IsCrouching() && !((Character)localPlayer).IsEncumbered()) { __state = __instance.m_crouchSpeed; __instance.m_crouchSpeed *= 1f + (float)Plugin.SneakSpeed.Value / 100f * ((Character)localPlayer).GetSkillFactor((SkillType)101); } } private static void Finalizer(Character __instance, float __state) { if (__state >= 0f) { __instance.m_crouchSpeed = __state; } } } [HarmonyPatch(typeof(Player), "FixedUpdate")] internal static class ArmorMoveXpPatch { private static void Postfix(Player __instance, Vector3 ___m_currentVel) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { ArmorSkills.OnMove(__instance, ___m_currentVel, Time.fixedDeltaTime); SailingXp.OnFixedUpdate(__instance, Time.fixedDeltaTime); } } } [HarmonyPatch(typeof(Character), "ApplyDamage")] internal static class ArmorHitXpPatch { private static void Postfix(Character __instance, HitData hit) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && !((Object)(object)__instance != (Object)(object)localPlayer) && hit != null && ((int)hit.m_hitType == 1 || (int)hit.m_hitType == 2) && !(hit.GetTotalDamage() <= 0f)) { ArmorSkills.OnHit(localPlayer, hit); } } } internal static class Perks { internal static float RiposteUntil; internal static bool Parrying; internal static float PerfectBlockAdrenaline; internal static bool ParryAdrenalineDone; internal static bool Dodging; internal static bool FreeCast; internal static float CraftThrifty; internal static bool Unlocked(Player p, SkillType skill) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)p)) { return ((Character)p).GetSkillLevel(skill) >= (float)Plugin.PerkLevel.Value; } return false; } internal static float Factor(Player p, SkillType skill) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (!Unlocked(p, skill)) { return 0f; } return ((Character)p).GetSkillFactor(skill); } internal static float Bonus(ConfigEntry<int> percent, Player p, SkillType skill) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (percent.Value > 0) { return (float)percent.Value / 100f * Factor(p, skill); } return 0f; } internal static bool FromLocal(HitData hit, Player p) { //IL_000c: 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) if (hit != null && Object.op_Implicit((Object)(object)p)) { return hit.m_attacker == ((Character)p).GetZDOID(); } return false; } internal static void Give(Player p, GameObject prefab, int amount) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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) if (Object.op_Implicit((Object)(object)prefab) && amount > 0 && !((Humanoid)p).GetInventory().AddItem(prefab, amount)) { Transform transform = ((Component)p).transform; GameObject obj = Object.Instantiate<GameObject>(prefab, transform.position + transform.forward + Vector3.up, Quaternion.identity); ItemDrop.OnCreateNew(obj, false); ItemDrop component = obj.GetComponent<ItemDrop>(); if (Object.op_Implicit((Object)(object)component)) { component.SetStack(amount); } Plugin.Debugf($"Inventory full: dropped {amount} {((Object)prefab).name}"); } } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] internal static class ParryPerksPatch { private static readonly FieldRef<Humanoid, float> BlockTimer = AccessTools.FieldRefAccess<Humanoid, float>("m_blockTimer"); private static readonly Func<Humanoid, ItemData> CurrentBlocker = AccessTools.MethodDelegate<Func<Humanoid, ItemData>>(AccessTools.Method(typeof(Humanoid), "GetCurrentBlocker", (Type[])null, (Type[])null), (object)null, true); private static void Prefix(Humanoid __instance, out bool __state) { __state = false; Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && !((Object)(object)__instance != (Object)(object)localPlayer)) { ItemData val = CurrentBlocker((Humanoid)(object)localPlayer); float num = BlockTimer.Invoke((Humanoid)(object)localPlayer); __state = val?.m_shared != null && val.m_shared.m_timedBlockBonus > 1f && num >= 0f && num < Combat.ParryWindow((Humanoid)(object)localPlayer); Perks.Parrying = __state; Perks.ParryAdrenalineDone = false; Perks.PerfectBlockAdrenaline = (__state ? val.m_shared.m_perfectBlockAdrenaline : 0f); } } private static void Postfix(bool __result, bool __state) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 if (__state && __result) { Player localPlayer = Player.m_localPlayer; ItemData currentWeapon = ((Humanoid)localPlayer).GetCurrentWeapon(); if (currentWeapon != null && (int)(currentWeapon.m_shared?.m_skillType).GetValueOrDefault() == 1 && !Combat.IsGreatsword(currentWeapon) && Perks.Bonus(Plugin.SwordsRiposte, localPlayer, (SkillType)1) > 0f) { Perks.RiposteUntil = Time.time + 2f; Plugin.Debugf("Parry: riposte ready for 2 s"); } } } private static void Finalizer() { Perks.Parrying = false; } } [HarmonyPatch(typeof(Player), "RPC_HitWhileDodging")] internal static class PerfectDodgePatch { private static void Prefix(Player __instance) { Perks.Dodging = (Object)(object)__instance == (Object)(object)Player.m_localPlayer; } private static void Finalizer() { Perks.Dodging = false; } } [HarmonyPatch(typeof(Player), "AddAdrenaline")] internal static class AdrenalinePatch { private static void Prefix(Player __instance, ref float v) { if (!(v <= 0f) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { if (Perks.Parrying && !Perks.ParryAdrenalineDone && Mathf.Abs(v - Perks.PerfectBlockAdrenaline) < 0.001f) { Perks.ParryAdrenalineDone = true; v *= 1f + Perks.Bonus(Plugin.BlockAdrenaline, __instance, (SkillType)6); } if (Perks.Dodging) { v *= 1f + Perks.Bonus(Plugin.DodgeAdrenaline, __instance, (SkillType)108); } } } } [HarmonyPatch(typeof(Projectile), "Setup")] internal static class ProjectilePerksPatch { private static void Prefix(Character owner, ref Vector3 velocity, HitData hitData, ItemData item) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || (Object)(object)owner != (Object)(object)localPlayer || item?.m_shared == null) { return; } SkillType skillType = item.m_shared.m_skillType; if ((int)skillType != 5) { if ((int)skillType == 8) { velocity *= 1f + Perks.Bonus(Plugin.BowArrowSpeed, localPlayer, (SkillType)8); } return; } velocity *= 1f + Perks.Bonus(Plugin.SpearThrowSpeed, localPlayer, (SkillType)5); if (hitData != null) { ((DamageTypes)(ref hitData.m_damage)).Modify(1f + Perks.Bonus(Plugin.SpearThrowDamage, localPlayer, (SkillType)5)); } } } [HarmonyPatch(typeof(ItemData), "GetWeaponLoadingTime")] internal static class CrossbowReloadPatch { private static void Postfix(ItemData __instance, ref float __result) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && __instance.m_shared != null && (int)__instance.m_shared.m_skillType == 14 && __instance.m_shared.m_attack != null && __instance.m_shared.m_attack.m_requiresReload) { __result *= 1f - Perks.Bonus(Plugin.CrossbowReload, localPlayer, (SkillType)14); } } } [HarmonyPatch(typeof(Attack), "Start")] internal static class FreeCastRollPatch { private static void Prefix(Humanoid character, ItemData weapon) { //IL_0038: 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_004c: Invalid comparison between Unknown and I4 Perks.FreeCast = false; Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && !((Object)(object)character != (Object)(object)localPlayer) && weapon != null && (int)(weapon.m_shared?.m_skillType).GetValueOrDefault() == 9) { Perks.FreeCast = Random.value < Perks.Bonus(Plugin.ElementalFreeCast, localPlayer, (SkillType)9); } } private static void Finalizer() { Perks.FreeCast = false; } } [HarmonyPatch(typeof(Character), "TryUseEitr")] internal static class FreeCastPatch { private static bool Prefix(Character __instance, float eitrUse, ref bool __result) { if (!Perks.FreeCast || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || eitrUse <= 0f) { return true; } Perks.FreeCast = false; if (!__instance.HaveEitr(eitrUse)) { return true; } Plugin.Debugf($"Free cast: kept {eitrUse:0.#} eitr"); __result = true; return false; } } [HarmonyPatch] internal static class ChopStaminaPatch { private static readonly FieldRef<Humanoid, Attack> CurrentAttack = AccessTools.FieldRefAccess<Humanoid, Attack>("m_currentAttack"); private static readonly Func<Attack, float> AttackStamina = AccessTools.MethodDelegate<Func<Attack, float>>(AccessTools.Method(typeof(Attack), "GetAttackStamina", (Type[])null, (Type[])null), (object)null, true); private static Attack _refunded; private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(TreeBase), "Damage", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(TreeLog), "Damage", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Destructible), "Da