Decompiled source of UsefulSkills v1.1.2

UsefulSkills.dll

Decompiled a week ago
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