Decompiled source of OdinsEssentials v0.1.0

OdinsEssentials.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;
using Valheim.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("OdinsEssentials")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+ce1fe04c1e0d3a2923c660dc5676ceccf87de5a5")]
[assembly: AssemblyProduct("OdinsEssentials")]
[assembly: AssemblyTitle("OdinsEssentials")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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;
		}
	}
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace OdinsMissingPatch
{
	[BepInPlugin("rauschekuh.odinsessentials", "Odin's Essentials", "0.1.0")]
	public class OdinsEssentialsPlugin : BaseUnityPlugin
	{
		public const string GUID = "rauschekuh.odinsessentials";

		public const string NAME = "Odin's Essentials";

		public const string VERSION = "0.1.0";

		private static readonly Tweak[] Tweaks = new Tweak[13]
		{
			Ranges.Instance,
			EndlessFuel.Instance,
			CombatStamina.Instance,
			Resting.Instance,
			FastPortals.Instance,
			KeepGearOnDeath.Instance,
			AreaRepair.Instance,
			AutoRepair.Instance,
			PowerPicker.Instance,
			EquipWhileRunning.Instance,
			BuildInWater.Instance,
			AutoShield.Instance,
			PocketUpgrades.Instance
		};

		private void Awake()
		{
			TweakHost.Start((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config, "rauschekuh.odinsessentials", Tweaks, delegate(ConfigFile config)
			{
				Danger.Bind(config, "stamina costs return (Combat Stamina).");
			});
		}
	}
	internal sealed class AreaRepair : Tweak
	{
		[HarmonyPatch(typeof(Player), "Repair")]
		private static class RepairEverythingAround
		{
			private static void Postfix(Player __instance, ItemData toolItem)
			{
				//IL_0041: 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_0047: 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_00f6: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
				//IL_0198: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
				//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
				if (!Instance.On || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || toolItem == null || toolItem.m_shared == null || !((Character)__instance).InPlaceMode())
				{
					return;
				}
				KeyCode value = Instance.singleRepairKey.Value;
				if ((int)value != 0 && ZInput.GetKey(value, false))
				{
					return;
				}
				bool placementCostDisabled = __instance.PlacementCostDisabled;
				bool stationFree = placementCostDisabled || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)27));
				float buildStamina = __instance.GetBuildStamina();
				float attackEitr = toolItem.m_shared.m_attack.m_attackEitr;
				float num = (toolItem.m_shared.m_useDurability ? (toolItem.m_shared.m_useDurabilityDrain * Game.m_durabilityRate) : 0f);
				if (!placementCostDisabled && !CanPayFor(__instance, toolItem, buildStamina, attackEitr, num))
				{
					return;
				}
				Piece hoveringPiece = __instance.GetHoveringPiece();
				Vector3 centre = (((Object)(object)hoveringPiece != (Object)null) ? ((Component)hoveringPiece).transform.position : ((Component)__instance).transform.position);
				int num2 = 0;
				List<KeyValuePair<Piece, float>> list = PiecesInRadius(centre, Instance.radius.Value);
				for (int i = 0; i < list.Count; i++)
				{
					Piece key = list[i].Key;
					WearNTear component = ((Component)key).GetComponent<WearNTear>();
					if (!((Object)(object)component == (Object)null) && CanRepair(__instance, key, stationFree) && component.Repair())
					{
						num2++;
						if (!placementCostDisabled)
						{
							((Character)__instance).UseStamina(buildStamina);
							((Character)__instance).UseEitr(attackEitr);
							toolItem.m_durability -= num;
						}
						if (key.m_placeEffect != null)
						{
							key.m_placeEffect.Create(((Component)key).transform.position, ((Component)key).transform.rotation, (Transform)null, 1f, -1, ((Character)__instance).GetZDOID());
						}
						if (!placementCostDisabled && !CanPayFor(__instance, toolItem, buildStamina, attackEitr, num))
						{
							break;
						}
					}
				}
				StationsInRange.Clear();
				Nearby.Clear();
				if (num2 > 0)
				{
					__instance.FaceLookDirection();
					((Character)__instance).m_zanim.SetTrigger(toolItem.m_shared.m_attack.m_attackAnimation);
					((Character)__instance).Message((MessageType)1, Localization.instance.Localize("$msg_repaired", new string[1] { num2.ToString() }), 0, (Sprite)null, false);
				}
			}

			private static bool CanPayFor(Player player, ItemData toolItem, float stamina, float eitr, float wear)
			{
				if (((Character)player).HaveStamina(stamina) && (eitr <= 0f || ((Character)player).HaveEitr(eitr)))
				{
					return toolItem.m_durability >= wear;
				}
				return false;
			}
		}

		internal static readonly AreaRepair Instance = new AreaRepair();

		private ConfigEntry<float> radius;

		private ConfigEntry<KeyCode> singleRepairKey;

		private static readonly Dictionary<string, bool> StationsInRange = new Dictionary<string, bool>();

		private static readonly List<KeyValuePair<Piece, float>> Nearby = new List<KeyValuePair<Piece, float>>();

		internal override string Section => "Area Repair";

		protected override string Summary => "One swing of the hammer repairs every damaged piece around the one you aim at, instead of that one piece alone.";

		private AreaRepair()
		{
		}

		protected override void Bind(ConfigFile config)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			radius = config.Bind<float>(Section, "Radius", 10f, new ConfigDescription("Metres around the piece you aim at that one swing reaches. Everything inside it is repaired closest first, so a short swing mends what is nearest.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 64f), Array.Empty<object>()));
			singleRepairKey = config.Bind<KeyCode>(Section, "SingleRepairKey", (KeyCode)308, "Hold this while you swing to repair only the piece you aim at, the way the game does. None to always repair the area.");
		}

		private static List<KeyValuePair<Piece, float>> PiecesInRadius(Vector3 centre, float radius)
		{
			//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)
			Nearby.Clear();
			foreach (Piece s_allPiece in Piece.s_allPieces)
			{
				if (!((Object)(object)s_allPiece == (Object)null) && ((Component)s_allPiece).gameObject.layer != Piece.s_ghostLayer)
				{
					float num = Vector3.Distance(centre, ((Component)s_allPiece).transform.position);
					if (num <= radius)
					{
						Nearby.Add(new KeyValuePair<Piece, float>(s_allPiece, num));
					}
				}
			}
			Nearby.Sort((KeyValuePair<Piece, float> a, KeyValuePair<Piece, float> b) => a.Value.CompareTo(b.Value));
			return Nearby;
		}

		private static bool CanRepair(Player player, Piece piece, bool stationFree)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			if (!stationFree && (Object)(object)piece.m_craftingStation != (Object)null)
			{
				string name = piece.m_craftingStation.m_name;
				if (!StationsInRange.TryGetValue(name, out var value))
				{
					value = Object.op_Implicit((Object)(object)CraftingStation.HaveBuildStationInRange(name, ((Component)player).transform.position));
					StationsInRange[name] = value;
				}
				if (!value)
				{
					return false;
				}
			}
			return PrivateArea.CheckAccess(((Component)piece).transform.position, 0f, true, false);
		}
	}
	internal sealed class AutoRepair : Tweak
	{
		[HarmonyPatch(typeof(CraftingStation), "Interact")]
		private static class RepairOnOpen
		{
			private static void Postfix(CraftingStation __instance, Humanoid user, bool repeat)
			{
				if (!(!Instance.On || repeat))
				{
					Player val = (Player)(object)((user is Player) ? user : null);
					if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !((Object)(object)val.GetCurrentCraftingStation() != (Object)(object)__instance))
					{
						Instance.RepairEverything(val, __instance);
					}
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "CanRepair")]
		private static class GroupRepair
		{
			private static void Postfix(ItemData item, ref bool __result)
			{
				if (!__result && Instance.On && Instance.groupRepair.Value && item?.m_shared != null && item.m_shared.m_canBeReparied)
				{
					Player localPlayer = Player.m_localPlayer;
					CraftingStation val = (((Object)(object)localPlayer != (Object)null) ? localPlayer.GetCurrentCraftingStation() : null);
					if (!((Object)(object)val == (Object)null))
					{
						__result = Instance.GroupMayRepair(localPlayer, val, item);
					}
				}
			}
		}

		internal static readonly AutoRepair Instance = new AutoRepair();

		private const string Workbench = "$piece_workbench";

		private const string Forge = "$piece_forge";

		private const int MaxCheckedLevel = 4;

		private ConfigEntry<bool> groupRepair;

		private ConfigEntry<bool> requireRealStation;

		private Dictionary<string, int> groups = new Dictionary<string, int>();

		private List<string> forgeGroup = new List<string>();

		private List<string> workbenchGroup = new List<string>();

		private static readonly List<ItemData> Worn = new List<ItemData>();

		internal override string Section => "Auto Repair";

		protected override string Summary => "Opening a crafting station repairs everything you carry that it can repair, instead of one item per click of the repair button.";

		private AutoRepair()
		{
		}

		protected override void Bind(ConfigFile config)
		{
			groupRepair = config.Bind<bool>(Section, "GroupRepair", true, "A station also repairs the items of the other stations in its group (ForgeGroup, WorkbenchGroup), at the repair button and when it is opened.");
			requireRealStation = config.Bind<bool>(Section, "RequireRealStation", true, "With GroupRepair: the item's own station has to stand within its build range of you, upgraded far enough for the item. Off: the workbench and the forge repair every item, with no other station needed and no station level checked.");
			BindList(config, "ForgeGroup", "$piece_forge, $piece_blackforge", "Comma separated station names (the game's $piece_ tokens) that repair each other's items.", delegate(List<string> names)
			{
				forgeGroup = names;
				Regroup();
			});
			BindList(config, "WorkbenchGroup", "$piece_workbench, $piece_magetable, $piece_artisanstation, $piece_stonecutter, $piece_cauldron", "Comma separated station names (the game's $piece_ tokens) that repair each other's items.", delegate(List<string> names)
			{
				workbenchGroup = names;
				Regroup();
			});
		}

		private void Regroup()
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (string item in forgeGroup)
			{
				dictionary[item] = 0;
			}
			foreach (string item2 in workbenchGroup)
			{
				dictionary[item2] = 1;
			}
			groups = dictionary;
		}

		private bool GroupMayRepair(Player player, CraftingStation current, ItemData item)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			if (!requireRealStation.Value)
			{
				if (!(current.m_name == "$piece_workbench"))
				{
					return current.m_name == "$piece_forge";
				}
				return true;
			}
			if (!groups.TryGetValue(current.m_name, out var value))
			{
				return false;
			}
			ObjectDB instance = ObjectDB.instance;
			Recipe val = ((instance != null) ? instance.GetRecipe(item) : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			int num = Mathf.Max(BestLevelInRange(val.m_craftingStation, value, ((Component)player).transform.position), BestLevelInRange(val.m_repairStation, value, ((Component)player).transform.position));
			if (num > 0)
			{
				return Mathf.Min(num, 4) >= val.m_minStationLevel;
			}
			return false;
		}

		private int BestLevelInRange(CraftingStation kind, int group, Vector3 point)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)kind == (Object)null || !groups.TryGetValue(kind.m_name, out var value) || value != group)
			{
				return 0;
			}
			int num = 0;
			foreach (CraftingStation allStation in CraftingStation.m_allStations)
			{
				if (!((Object)(object)allStation == (Object)null) && !(allStation.m_name != kind.m_name))
				{
					Vector3 position = ((Component)allStation).transform.position;
					point.y = position.y;
					if (Vector3.Distance(position, point) < allStation.GetStationBuildRange())
					{
						num = Mathf.Max(num, allStation.GetLevel(true));
					}
				}
			}
			return num;
		}

		private void RepairEverything(Player player, CraftingStation station)
		{
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance == (Object)null || !station.m_canRepair)
			{
				return;
			}
			Worn.Clear();
			((Humanoid)player).GetInventory().GetWornItems(Worn);
			int num = 0;
			foreach (ItemData item in Worn)
			{
				if (instance.CanRepair(item))
				{
					((Character)player).RaiseSkill((SkillType)107, 1f - item.m_durability / item.GetMaxDurability());
					item.m_durability = item.GetMaxDurability();
					num++;
				}
			}
			Worn.Clear();
			if (num != 0)
			{
				station.m_repairItemDoneEffects.Create(((Component)station).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
				((Character)player).Message((MessageType)2, Localization.instance.Localize("$msg_repaired", new string[1] { num.ToString() }), 0, (Sprite)null, false);
			}
		}
	}
	internal sealed class AutoShield : Tweak
	{
		[HarmonyPatch(typeof(Player), "ToggleEquipped")]
		private static class NoticeWeaponPress
		{
			private static void Prefix(Player __instance, ItemData item)
			{
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Invalid comparison between Unknown and I4
				if (!Instance.On || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || item == null || (int)item.m_shared.m_itemType != 3)
				{
					return;
				}
				if (((Humanoid)__instance).IsItemEquiped(item) || __instance.IsEquipActionQueued(item))
				{
					if (pending == item)
					{
						pending = null;
					}
				}
				else
				{
					pending = item;
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "EquipItem")]
		private static class RaiseShield
		{
			private static void Postfix(Humanoid __instance, ItemData item, bool __result)
			{
				if (!__result || item == null || item != pending)
				{
					return;
				}
				pending = null;
				Player val = (Player)(object)((__instance is Player) ? __instance : null);
				if (Instance.On && !((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && ((Humanoid)val).GetLeftItem() == null)
				{
					ItemData val2 = FindShield(val);
					if (val2 != null)
					{
						((Humanoid)val).ToggleEquipped(val2);
					}
				}
			}
		}

		internal static readonly AutoShield Instance = new AutoShield();

		private static ItemData pending;

		internal override string Section => "Auto Shield";

		protected override string Summary => "Equipping a one handed weapon raises a shield with it, if your off hand is empty and you carry one.";

		private AutoShield()
		{
		}

		private static ItemData FindShield(Player player)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Invalid comparison between Unknown and I4
			ItemData val = null;
			int num = int.MaxValue;
			foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
			{
				if ((int)allItem.m_shared.m_itemType == 5 && !((Humanoid)player).IsItemEquiped(allItem) && (!allItem.m_shared.m_useDurability || !(allItem.m_durability <= 0f)))
				{
					int num2 = ((!FavoriteMark.IsSet(allItem)) ? ((allItem.m_gridPos.y == 0) ? 1 : 2) : 0);
					if (val == null || (num2 <= num && (num2 != num || Before(allItem, val))))
					{
						val = allItem;
						num = num2;
					}
				}
			}
			return val;
		}

		private static bool Before(ItemData item, ItemData other)
		{
			if (item.m_gridPos.y == other.m_gridPos.y)
			{
				return item.m_gridPos.x < other.m_gridPos.x;
			}
			return item.m_gridPos.y < other.m_gridPos.y;
		}
	}
	internal sealed class BuildInWater : Tweak
	{
		[HarmonyPatch(typeof(Humanoid), "UpdateEquipment")]
		private static class NoticeSwimHide
		{
			private static void Prefix(Humanoid __instance)
			{
				keepTool = Applies(__instance, __instance.m_rightItem);
			}

			private static void Postfix()
			{
				keepTool = false;
			}
		}

		[HarmonyPatch(typeof(Humanoid), "HideHandItems")]
		private static class KeepToolOut
		{
			private static bool Prefix(Humanoid __instance, ref bool __result)
			{
				if (!keepTool)
				{
					return true;
				}
				ItemData leftItem = __instance.m_leftItem;
				if (leftItem != null)
				{
					__instance.UnequipItem(leftItem, true);
					__instance.m_hiddenLeftItem = leftItem;
					__instance.SetupVisEquipment(__instance.m_visEquipment, false);
				}
				__result = leftItem != null;
				return false;
			}
		}

		[HarmonyPatch(typeof(Humanoid), "EquipItem")]
		private static class DrawToolInWater
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: Expected O, but got Unknown
				//IL_0052: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Expected O, but got Unknown
				//IL_0078: Unknown result type (might be due to invalid IL or missing references)
				//IL_0082: Expected O, but got Unknown
				return new CodeMatcher(instructions, (ILGenerator)null).MatchStartForward((CodeMatch[])(object)new CodeMatch[1]
				{
					new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction code) => CodeInstructionExtensions.Calls(code, AccessTools.Method(typeof(Character), "IsSwimming", (Type[])null, (Type[])null))), (string)null)
				}).ThrowIfInvalid("Humanoid.EquipItem no longer asks Character.IsSwimming").InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1]
				{
					new CodeInstruction(OpCodes.Ldarg_1, (object)null)
				})
					.SetInstruction(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(BuildInWater), "SwimmingBlocks", (Type[])null, (Type[])null)))
					.InstructionEnumeration();
			}
		}

		internal static readonly BuildInWater Instance = new BuildInWater();

		private static bool keepTool;

		internal override string Section => "Build In Water";

		protected override string Summary => "Keep the hammer and the pickaxe in hand while swimming, and draw them in water, so you can build and mine without going ashore. Other items are still put away.";

		private BuildInWater()
		{
		}

		private static bool IsAllowed(ItemData item)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			if (item != null && item.m_shared != null)
			{
				if (!((Object)(object)item.m_shared.m_buildPieces != (Object)null))
				{
					return (int)item.m_shared.m_skillType == 12;
				}
				return true;
			}
			return false;
		}

		private static bool Applies(Humanoid humanoid, ItemData item)
		{
			if (Instance.On && (Object)(object)humanoid != (Object)null && (Object)(object)humanoid == (Object)(object)Player.m_localPlayer)
			{
				return IsAllowed(item);
			}
			return false;
		}

		private static bool SwimmingBlocks(Humanoid humanoid, ItemData item)
		{
			if (((Character)humanoid).IsSwimming())
			{
				return !Applies(humanoid, item);
			}
			return false;
		}
	}
	internal sealed class CombatStamina : Tweak
	{
		[HarmonyPatch(typeof(Player), "CheckRun")]
		private static class FreeSprinting
		{
			private static void Prefix(Player __instance)
			{
				waiving = Instance.Waives(Instance.freeSprint, (Character)(object)__instance);
			}

			private static void Postfix()
			{
				waiving = false;
			}
		}

		[HarmonyPatch(typeof(Player), "OnJump")]
		private static class FreeJumping
		{
			private static void Prefix(Player __instance)
			{
				waiving = Instance.Waives(Instance.freeJump, (Character)(object)__instance);
			}

			private static void Postfix()
			{
				waiving = false;
			}
		}

		[HarmonyPatch(typeof(Player), "OnSwimming")]
		private static class FreeSwimming
		{
			private static void Prefix(Player __instance)
			{
				waiving = Instance.Waives(Instance.freeSwim, (Character)(object)__instance);
			}

			private static void Postfix()
			{
				waiving = false;
			}
		}

		[HarmonyPatch(typeof(Player), "OnSneaking")]
		private static class FreeSneaking
		{
			private static void Prefix(Player __instance)
			{
				waiving = Instance.Waives(Instance.freeSneak, (Character)(object)__instance);
			}

			private static void Postfix()
			{
				waiving = false;
			}
		}

		[HarmonyPatch(typeof(Player), "UseStamina")]
		private static class DropWaivedSpend
		{
			private static bool Prefix()
			{
				return !waiving;
			}
		}

		[HarmonyPatch(typeof(Player), "GetBuildStamina")]
		private static class FreeBuilding
		{
			private static void Postfix(Player __instance, ref float __result)
			{
				if (__result > 0f && Instance.Waives(Instance.freeBuild, (Character)(object)__instance))
				{
					__result = 0f;
				}
			}
		}

		[HarmonyPatch(typeof(Attack), "GetAttackStamina")]
		private static class FreeAttacking
		{
			private static void Postfix(Attack __instance, ref float __result)
			{
				if (__result > 0f && Instance.Waives(Instance.freeAttacks, (Character)(object)__instance.m_character))
				{
					__result = 0f;
				}
			}
		}

		[HarmonyPatch(typeof(Player), "UpdateStats", new Type[] { typeof(float) })]
		private static class RefillWhileFree
		{
			private static void Postfix(Player __instance, float dt)
			{
				if (!Instance.On || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || __instance.m_staminaRegenTimer > 0f)
				{
					return;
				}
				ConfigEntry<bool> val = Instance.RegenBlockedBy(__instance);
				if (val == null || !Instance.Waives(val, (Character)(object)__instance))
				{
					return;
				}
				float maxStamina = ((Character)__instance).GetMaxStamina();
				float stamina = __instance.m_stamina;
				if (!(stamina >= maxStamina))
				{
					float num = __instance.m_staminaRegen + (1f - stamina / maxStamina) * __instance.m_staminaRegen * __instance.m_staminaRegenTimeMultiplier;
					if (((Character)__instance).IsBlocking())
					{
						num *= 0.8f;
					}
					float num2 = 1f;
					((Character)__instance).GetSEMan().ModifyStaminaRegen(ref num2);
					__instance.m_stamina = Mathf.Min(maxStamina, stamina + num * num2 * dt * Game.m_staminaRegenRate);
				}
			}
		}

		internal static readonly CombatStamina Instance = new CombatStamina();

		private ConfigEntry<bool> freeSprint;

		private ConfigEntry<bool> freeJump;

		private ConfigEntry<bool> freeSwim;

		private ConfigEntry<bool> freeSneak;

		private ConfigEntry<bool> freeBuild;

		private ConfigEntry<bool> freeAttacks;

		private static bool waiving;

		internal override string Section => "Combat Stamina";

		internal override Type[] Uses => new Type[1] { typeof(Danger) };

		protected override string Summary => "Stamina only drains in combat: sprinting, jumping, swimming, sneaking, building, chopping, mining and weapon swings cost nothing while nothing hostile is close by or after you.";

		private CombatStamina()
		{
		}

		protected override void Bind(ConfigFile config)
		{
			freeSprint = config.Bind<bool>(Section, "FreeSprint", true, "Sprinting costs nothing out of combat.");
			freeJump = config.Bind<bool>(Section, "FreeJump", true, "Jumping costs nothing out of combat.");
			freeSwim = config.Bind<bool>(Section, "FreeSwim", true, "Swimming costs nothing out of combat, and stamina refills while you swim. That also means you cannot drown out of combat: drowning only starts once stamina is empty.");
			freeSneak = config.Bind<bool>(Section, "FreeSneak", true, "Sneaking costs nothing out of combat.");
			freeBuild = config.Bind<bool>(Section, "FreeBuild", true, "Building, repairing and removing pieces cost nothing out of combat.");
			freeAttacks = config.Bind<bool>(Section, "FreeAttacks", true, "Chopping, mining and weapon swings cost nothing out of combat, and stamina refills mid swing.");
		}

		private bool Waives(ConfigEntry<bool> cost, Character character)
		{
			Player localPlayer = Player.m_localPlayer;
			if (base.On && cost.Value && (Object)(object)localPlayer != (Object)null && (Object)(object)character == (Object)(object)localPlayer)
			{
				return !Danger.Near(localPlayer);
			}
			return false;
		}

		private ConfigEntry<bool> RegenBlockedBy(Player player)
		{
			if (((Character)player).IsWallRunning() || ((Character)player).IsEncumbered())
			{
				return null;
			}
			if (((Character)player).IsSwimming() && !((Character)player).IsOnGround())
			{
				return freeSwim;
			}
			if (((Character)player).InAttack() || ((Character)player).InDodge())
			{
				return freeAttacks;
			}
			return null;
		}
	}
	internal sealed class EndlessFuel : Tweak
	{
		[HarmonyPatch(typeof(Fireplace), "UpdateFireplace")]
		private static class KeepFuelled
		{
			private static void Postfix(Fireplace __instance)
			{
				if (!Instance.On || __instance.m_infiniteFuel || __instance.m_secPerFuel <= 0f)
				{
					return;
				}
				ZNetView nview = __instance.m_nview;
				if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner())
				{
					ZDO zDO = nview.GetZDO();
					if (zDO.GetFloat(ZDOVars.s_fuel, 0f) < __instance.m_maxFuel)
					{
						zDO.Set(ZDOVars.s_fuel, __instance.m_maxFuel);
						__instance.UpdateState();
					}
				}
			}
		}

		internal static readonly EndlessFuel Instance = new EndlessFuel();

		internal override string Section => "Endless Fuel";

		protected override string Summary => "Fires, torches, braziers and the hot tub never run out of fuel: whatever burns fuel to give light stays lit once it is lit.";

		private EndlessFuel()
		{
		}
	}
	internal sealed class EquipWhileRunning : Tweak
	{
		[HarmonyPatch(typeof(Player), "CheckRun")]
		private static class NoticeSprint
		{
			private static void Prefix()
			{
				sprinting = Instance.On;
			}

			private static void Postfix()
			{
				sprinting = false;
			}
		}

		[HarmonyPatch(typeof(Player), "ClearActionQueue")]
		private static class KeepEquipsWhileSprinting
		{
			private static bool Prefix(Player __instance)
			{
				if (!sprinting)
				{
					return true;
				}
				__instance.m_actionQueue.RemoveAll((MinorActionData action) => (int)action.m_type == 2);
				return false;
			}
		}

		internal static readonly EquipWhileRunning Instance = new EquipWhileRunning();

		private static bool sprinting;

		internal override string Section => "Equip While Running";

		protected override string Summary => "Equip and unequip weapons, shields and armour while sprinting, instead of having to slow down first.";

		private EquipWhileRunning()
		{
		}
	}
	internal sealed class FastPortals : Tweak
	{
		[HarmonyPatch(typeof(Player), "UpdateTeleport")]
		private static class SkipTheWait
		{
			private static bool waitedForFloor;

			private static void Prefix(Player __instance)
			{
				if (!__instance.m_teleporting)
				{
					waitedForFloor = false;
				}
				if (!Instance.On || !__instance.m_teleporting || __instance.m_teleportTimer >= 8f)
				{
					return;
				}
				if (IsInstantDoorTrip(__instance))
				{
					if (waitedForFloor)
					{
						TweakHost.Log.LogInfo((object)(Instance.Section + ": the floor inside the door appeared after " + __instance.m_teleportTimer.ToString("0.00") + "s"));
						waitedForFloor = false;
					}
					if (__instance.m_teleportTimer < 2f)
					{
						__instance.m_teleportTimer = 2f;
					}
				}
				else if (IsDoorTrip(__instance) && !HasFloor(__instance))
				{
					waitedForFloor = true;
				}
				else
				{
					Hud instance = Hud.instance;
					if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.m_loadingScreen == (Object)null) && !(instance.m_loadingScreen.alpha < 1f))
					{
						__instance.m_teleportTimer = 8f;
					}
				}
			}
		}

		[HarmonyPatch(typeof(Hud), "GetFadeDuration")]
		private static class FadeFaster
		{
			private static bool fadingForTeleport;

			private static void Postfix(Hud __instance, Player player, ref float __result)
			{
				if (!((Object)(object)player == (Object)null))
				{
					if (((Character)player).IsTeleporting())
					{
						fadingForTeleport = true;
					}
					else if (((Character)player).IsDead() || player.IsSleeping() || (Object)(object)__instance.m_loadingScreen == (Object)null || __instance.m_loadingScreen.alpha <= 0f)
					{
						fadingForTeleport = false;
					}
					if (IsInstantDoorTrip(player))
					{
						__result = float.MaxValue;
					}
					else if (Instance.On && fadingForTeleport)
					{
						__result = Instance.fadeSeconds.Value;
					}
				}
			}
		}

		internal static readonly FastPortals Instance = new FastPortals();

		private const float VanillaMinimumTrip = 8f;

		private const float VanillaMoveDelay = 2f;

		private ConfigEntry<float> fadeSeconds;

		private ConfigEntry<bool> instantDungeonDoors;

		internal override string Section => "Fast Portals";

		protected override string Summary => "Go through a portal as soon as the screen is black and the other side has loaded, instead of after the game's fixed eight second wait.";

		private FastPortals()
		{
		}

		protected override void Bind(ConfigFile config)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			fadeSeconds = config.Bind<float>(Section, "FadeSeconds", 0.5f, new ConfigDescription("Seconds the screen takes to fade to black when you step into a portal, and to fade back in on the other side. 1 is vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			instantDungeonDoors = config.Bind<bool>(Section, "InstantDungeonDoors", true, "Going through a dungeon or cave entrance is instant, without the black screen, whenever the inside is already loaded (it nearly always is). Off means the door fades like a portal, over FadeSeconds.");
		}

		private static bool IsInstantDoorTrip(Player player)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if (Instance.On && Instance.instantDungeonDoors.Value && IsDoorTrip(player) && (Object)(object)ZNetScene.instance != (Object)null && ZNetScene.instance.IsAreaReady(player.m_teleportTargetPos))
			{
				return HasFloor(player);
			}
			return false;
		}

		private static bool IsDoorTrip(Player player)
		{
			if (player.m_teleporting)
			{
				return !player.m_distantTeleport;
			}
			return false;
		}

		private static bool HasFloor(Player player)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			float num = default(float);
			if ((Object)(object)ZoneSystem.instance != (Object)null)
			{
				return ZoneSystem.instance.FindFloor(player.m_teleportTargetPos, ref num);
			}
			return false;
		}
	}
	internal sealed class KeepGearOnDeath : Tweak
	{
		[HarmonyPatch(typeof(Player), "CreateTombStone")]
		private static class DeathScope
		{
			private static void Prefix(Player __instance)
			{
				dying = (Instance.Active ? __instance : null);
			}

			private static void Postfix()
			{
				dying = null;
			}
		}

		[HarmonyPatch(typeof(Humanoid), "UnequipItem", new Type[]
		{
			typeof(ItemData),
			typeof(bool)
		})]
		private static class KeepEquipped
		{
			private static bool Prefix(Humanoid __instance, ItemData item)
			{
				if ((Object)(object)dying == (Object)null || (Object)(object)__instance != (Object)(object)dying || !Instance.Active)
				{
					return true;
				}
				return !Instance.Keeps(item);
			}
		}

		[HarmonyPatch(typeof(Inventory), "MoveInventoryToGrave")]
		private static class FillGrave
		{
			private static bool Prefix(Inventory __instance, Inventory original)
			{
				if (!Instance.Active || original == null)
				{
					return true;
				}
				__instance.m_inventory.Clear();
				__instance.m_width = original.m_width;
				__instance.m_height = original.m_height;
				foreach (ItemData item in original.m_inventory)
				{
					if (Instance.Drops(item))
					{
						__instance.m_inventory.Add(item);
					}
				}
				original.m_inventory.RemoveAll(Instance.Drops);
				original.Changed(false, false);
				__instance.Changed(false, false);
				return false;
			}
		}

		[HarmonyPatch(typeof(Inventory), "RemoveUnequipped")]
		private static class DeleteUnkept
		{
			private static bool Prefix(Inventory __instance)
			{
				if (!Instance.Active)
				{
					return true;
				}
				__instance.m_inventory.RemoveAll(Instance.Drops);
				__instance.Changed(false, false);
				return false;
			}
		}

		internal static readonly KeepGearOnDeath Instance = new KeepGearOnDeath();

		private const string DefaultKeepTypes = "OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Helmet, Chest, Legs, Shoulder, Hands, Utility, Trinket, Ammo, AmmoNonEquipable, Consumable";

		private HashSet<ItemType> kept = new HashSet<ItemType>();

		private static Player dying;

		internal override string Section => "Keep Gear On Death";

		protected override string Summary => "Dying keeps the item types listed in KeepTypes - weapons, armour, ammunition, tools and food by default - in your inventory and equipped; only the rest goes to the grave. Applies on worlds whose death penalty is set to Casual, the lowest setting - on a harsher world it does nothing.";

		private bool Active
		{
			get
			{
				ZoneSystem instance = ZoneSystem.instance;
				if (base.On && (Object)(object)instance != (Object)null)
				{
					return instance.GetGlobalKey((GlobalKeys)19);
				}
				return false;
			}
		}

		private KeepGearOnDeath()
		{
		}

		protected override void Bind(ConfigFile config)
		{
			string text = string.Join(", ", (from name in Enum.GetNames(typeof(ItemType))
				where name != "None"
				select name).ToArray());
			BindList(config, "KeepTypes", "OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Torch, Tool, Helmet, Chest, Legs, Shoulder, Hands, Utility, Trinket, Ammo, AmmoNonEquipable, Consumable", "Comma separated item types that stay with you when you die. Everything else goes to the grave. The types the game knows: " + text + ".", Parse);
		}

		private void Parse(List<string> names)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			HashSet<ItemType> hashSet = new HashSet<ItemType>();
			foreach (string name in names)
			{
				try
				{
					hashSet.Add((ItemType)Enum.Parse(typeof(ItemType), name, ignoreCase: true));
				}
				catch (ArgumentException)
				{
					TweakHost.Log.LogWarning((object)(Section + ": '" + name + "' is not an item type and is ignored"));
				}
			}
			kept = hashSet;
		}

		private bool Keeps(ItemData item)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if (item != null && item.m_shared != null)
			{
				if (!item.m_shared.m_questItem)
				{
					return kept.Contains(item.m_shared.m_itemType);
				}
				return true;
			}
			return false;
		}

		private bool Drops(ItemData item)
		{
			if (item != null && !item.m_equipped)
			{
				return !Keeps(item);
			}
			return false;
		}
	}
	internal sealed class PocketUpgrades : Tweak
	{
		internal enum Boss
		{
			None,
			Eikthyr,
			TheElder,
			Bonemass,
			Moder,
			Yagluth,
			TheQueen,
			Fader
		}

		[HarmonyPatch(typeof(Trader), "GetAvailableItems")]
		private static class Gates
		{
			private static void Prefix(Trader __instance)
			{
				Instance.ApplyGates(__instance);
			}
		}

		internal static readonly PocketUpgrades Instance = new PocketUpgrades();

		private static readonly Dictionary<Boss, string> BossKeys = new Dictionary<Boss, string>
		{
			{
				Boss.None,
				""
			},
			{
				Boss.Eikthyr,
				"defeated_eikthyr"
			},
			{
				Boss.TheElder,
				"defeated_gdking"
			},
			{
				Boss.Bonemass,
				"defeated_bonemass"
			},
			{
				Boss.Moder,
				"defeated_dragon"
			},
			{
				Boss.Yagluth,
				"defeated_goblinking"
			},
			{
				Boss.TheQueen,
				"defeated_queen"
			},
			{
				Boss.Fader,
				"defeated_fader"
			}
		};

		private const string WiderPocketsKey = "invslot1";

		private const string DeeperPocketsKey = "invslot2";

		private ConfigEntry<Boss> widerPocketsBoss;

		private ConfigEntry<Boss> deeperPocketsBoss;

		private readonly ConditionalWeakTable<TradeItem, string> vanilla = new ConditionalWeakTable<TradeItem, string>();

		internal override string Section => "Pocket Upgrades";

		protected override string Summary => "Choose which boss each of Haldor's two extra inventory rows waits for. Wider Pockets comes with the Elder instead of Moder by default, so the early game is less of a backpack shuffle.";

		private PocketUpgrades()
		{
		}

		protected override void Bind(ConfigFile config)
		{
			widerPocketsBoss = config.Bind<Boss>(Section, "WiderPocketsBoss", Boss.TheElder, "The boss this world has to have beaten before Haldor sells Wider Pockets, the first extra inventory row. Moder in vanilla. None offers it from the start.");
			deeperPocketsBoss = config.Bind<Boss>(Section, "DeeperPocketsBoss", Boss.TheQueen, "The boss this world has to have beaten before Haldor sells Deeper Pockets, the second extra inventory row. The Queen in vanilla, which is also the default here. None offers it from the start.");
		}

		private ConfigEntry<Boss> Setting(string buyKey)
		{
			if (buyKey == "invslot1")
			{
				return widerPocketsBoss;
			}
			if (!(buyKey == "invslot2"))
			{
				return null;
			}
			return deeperPocketsBoss;
		}

		private void ApplyGates(Trader trader)
		{
			if ((Object)(object)trader == (Object)null || trader.m_items == null)
			{
				return;
			}
			foreach (TradeItem item in trader.m_items)
			{
				ConfigEntry<Boss> val = ((item != null) ? Setting(item.m_buyKey) : null);
				if (val != null)
				{
					string value = vanilla.GetValue(item, (TradeItem i) => i.m_requiredGlobalKey ?? "");
					if (!BossKeys.TryGetValue(val.Value, out var value2))
					{
						value2 = value;
					}
					item.m_requiredGlobalKey = (base.On ? value2 : value);
				}
			}
		}
	}
	internal sealed class PowerPicker : Tweak
	{
		[HarmonyPatch(typeof(RadialBase), "ConstructRadial")]
		private static class TopCategory
		{
			private static void Prefix(RadialBase __instance, List<RadialMenuElement> elements)
			{
				if (Instance.On && elements != null && __instance.CurrentConfig is ValheimRadialConfig && !((Object)(object)RadialData.SO == (Object)null) && !((Object)(object)RadialData.SO.GroupElement == (Object)null) && Unlocked().Count != 0)
				{
					GroupElement val = Object.Instantiate<GroupElement>(RadialData.SO.GroupElement);
					val.Init((IRadialConfig)(object)new PowerConfig(), __instance.CurrentConfig, __instance);
					elements.Add((RadialMenuElement)(object)val);
				}
			}
		}

		private sealed class PowerConfig : IRadialConfig
		{
			public string LocalizedName => Localization.instance.Localize("$omp_forsaken_powers");

			public Sprite Sprite
			{
				get
				{
					List<StatusEffect> list = Unlocked();
					string text = Current();
					foreach (StatusEffect item in list)
					{
						if (((Object)item).name == text)
						{
							return item.m_icon;
						}
					}
					if (list.Count <= 0)
					{
						return null;
					}
					return list[0].m_icon;
				}
			}

			public void InitRadialConfig(RadialBase radial)
			{
				List<RadialMenuElement> list = new List<RadialMenuElement>();
				string text = Current();
				foreach (StatusEffect item in Unlocked())
				{
					RadialMenuElement val = Element(item, ((Object)item).name == text);
					if ((Object)(object)val != (Object)null)
					{
						list.Add(val);
					}
				}
				radial.ConstructRadial(list);
			}

			private static RadialMenuElement Element(StatusEffect power, bool active)
			{
				EmoteElement element = (((Object)(object)RadialData.SO.EmoteElement != (Object)null) ? Object.Instantiate<EmoteElement>(RadialData.SO.EmoteElement) : null);
				if ((Object)(object)element == (Object)null)
				{
					return null;
				}
				((RadialMenuElement)element).SubTitle = Localization.instance.Localize(power.GetTooltipString());
				((RadialMenuElement)element).CloseOnInteract = () => true;
				((RadialMenuElement)element).Interact = delegate
				{
					Pick(power);
					Mark((RadialMenuElement)(object)element, power, active: true);
					return true;
				};
				Mark((RadialMenuElement)(object)element, power, active);
				return (RadialMenuElement)(object)element;
			}

			private static void Mark(RadialMenuElement element, StatusEffect power, bool active)
			{
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_005f: Unknown result type (might be due to invalid IL or missing references)
				string text = Localization.instance.Localize(power.m_name);
				element.Name = (active ? (text + " (active)") : text);
				Image icon = element.Icon;
				if ((Object)(object)icon != (Object)null)
				{
					((Component)icon).gameObject.SetActive((Object)(object)power.m_icon != (Object)null);
					icon.sprite = power.m_icon;
					((Graphic)icon).color = (active ? Palette.Gold : Color.white);
				}
			}
		}

		internal static readonly PowerPicker Instance = new PowerPicker();

		private static readonly Dictionary<string, string> BossKeys = new Dictionary<string, string>
		{
			{ "GP_Eikthyr", "defeated_eikthyr" },
			{ "GP_TheElder", "defeated_gdking" },
			{ "GP_Bonemass", "defeated_bonemass" },
			{ "GP_Moder", "defeated_dragon" },
			{ "GP_Yagluth", "defeated_goblinking" },
			{ "GP_Queen", "defeated_queen" },
			{ "GP_Fader", "defeated_fader" }
		};

		internal override string Section => "Power Picker";

		protected override string Summary => "A Forsaken powers category in the radial menu, holding every boss power you have unlocked; picking one makes it yours, without the walk back to the stones.";

		private PowerPicker()
		{
		}

		private static List<StatusEffect> Unlocked()
		{
			List<StatusEffect> list = new List<StatusEffect>();
			Player localPlayer = Player.m_localPlayer;
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)instance == (Object)null)
			{
				return list;
			}
			string guardianPowerName = localPlayer.GetGuardianPowerName();
			foreach (StatusEffect statusEffect in instance.m_StatusEffects)
			{
				if ((Object)(object)statusEffect != (Object)null && statusEffect.m_cooldown > 0f && (((Object)statusEffect).name == guardianPowerName || ((Humanoid)localPlayer).HaveUniqueKey(((Object)statusEffect).name) || BossDefeated(localPlayer, ((Object)statusEffect).name)))
				{
					list.Add(statusEffect);
				}
			}
			return list;
		}

		private static bool BossDefeated(Player player, string power)
		{
			if (!BossKeys.TryGetValue(power, out var value))
			{
				return false;
			}
			ZoneSystem instance = ZoneSystem.instance;
			if (!((Object)(object)instance != (Object)null) || !instance.GetGlobalKey(value))
			{
				return ((Humanoid)player).HaveUniqueKey(value);
			}
			return true;
		}

		private static string Current()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer != (Object)null))
			{
				return "";
			}
			return localPlayer.GetGuardianPowerName();
		}

		private static void Pick(StatusEffect power)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)power == (Object)null) && !(localPlayer.GetGuardianPowerName() == ((Object)power).name))
			{
				localPlayer.SetGuardianPower(((Object)power).name);
				((Character)localPlayer).Message((MessageType)2, Localization.instance.Localize(power.m_name), 0, (Sprite)null, false);
			}
		}
	}
	internal sealed class Ranges : Tweak
	{
		private sealed class StationVanilla
		{
			internal float RangeBuild;

			internal float ExtraRangePerLevel;

			internal int Segments;
		}

		[HarmonyPatch(typeof(CraftingStation), "Start")]
		private static class ScaleStation
		{
			private static void Postfix(CraftingStation __instance)
			{
				Instance.Apply(__instance);
			}
		}

		[HarmonyPatch(typeof(StationExtension), "Awake")]
		private static class ScaleExtension
		{
			private static void Postfix(StationExtension __instance)
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				if ((Object)(object)component != (Object)null && component.GetZDO() != null)
				{
					Instance.Apply(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(Demister), "OnEnable")]
		private static class ScaleDemister
		{
			private static void Postfix(Demister __instance)
			{
				Instance.Apply(__instance);
			}
		}

		[HarmonyPatch(typeof(Piece), "GetAllComfortPiecesInRadius")]
		private static class WidenComfortRadius
		{
			private static void Prefix(ref float radius)
			{
				if (Instance.On)
				{
					radius *= Instance.comfortRadius.Value;
				}
			}
		}

		internal static readonly Ranges Instance = new Ranges();

		private const int MinSegments = 3;

		private ConfigEntry<float> buildRange;

		private ConfigEntry<float> extensionRange;

		private ConfigEntry<float> comfortRadius;

		private ConfigEntry<float> mistClearRadius;

		private readonly ConditionalWeakTable<CraftingStation, StationVanilla> vanillaStations = new ConditionalWeakTable<CraftingStation, StationVanilla>();

		private readonly ConditionalWeakTable<StationExtension, StrongBox<float>> vanillaExtensions = new ConditionalWeakTable<StationExtension, StrongBox<float>>();

		private readonly ConditionalWeakTable<Demister, StrongBox<float>> vanillaDemisters = new ConditionalWeakTable<Demister, StrongBox<float>>();

		internal override string Section => "Ranges";

		protected override string Summary => "Widen the area a crafting station covers, how far its extensions may stand from it, the radius comfort is counted in and the circle kept clear of the Mistlands mist.";

		private float BuildScale
		{
			get
			{
				if (!base.On)
				{
					return 1f;
				}
				return buildRange.Value;
			}
		}

		private float ExtensionScale
		{
			get
			{
				if (!base.On)
				{
					return 1f;
				}
				return extensionRange.Value;
			}
		}

		private float MistClearScale
		{
			get
			{
				if (!base.On)
				{
					return 1f;
				}
				return mistClearRadius.Value;
			}
		}

		private Ranges()
		{
		}

		protected override void Bind(ConfigFile config)
		{
			buildRange = BindMultiplier(config, "BuildRangeMultiplier", 2f, "Multiplier on the radius in which a station lets you build, craft and repair. 1 is vanilla - 10m for the workbench, 20m for the forge.");
			extensionRange = BindMultiplier(config, "ExtensionRangeMultiplier", 2f, "Multiplier on how far an extension (the forge cooler, the tanning rack, ...) may stand from its station and still raise its level. 1 is vanilla, usually 5m.");
			comfortRadius = BindMultiplier(config, "ComfortRadiusMultiplier", 2f, "Multiplier on the radius around you in which comfort furniture counts towards Rested. 1 is vanilla (10m).");
			mistClearRadius = BindMultiplier(config, "MistClearRadiusMultiplier", 2f, "Multiplier on the radius a wisplight, a wisp torch and everything else that clears the Mistlands mist keeps clear. 1 is vanilla.");
			OnSettingChanged(config, Rescale);
		}

		private void Rescale()
		{
			foreach (CraftingStation allStation in CraftingStation.m_allStations)
			{
				Apply(allStation);
			}
			foreach (StationExtension allExtension in StationExtension.m_allExtensions)
			{
				Apply(allExtension);
			}
			foreach (Demister demister in Demister.GetDemisters())
			{
				Apply(demister);
			}
		}

		private void Apply(CraftingStation station)
		{
			if (!((Object)(object)station == (Object)null))
			{
				StationVanilla value = vanillaStations.GetValue(station, (CraftingStation s) => new StationVanilla
				{
					RangeBuild = s.m_rangeBuild,
					ExtraRangePerLevel = s.m_extraRangePerLevel,
					Segments = (((Object)(object)s.m_areaMarkerCircle != (Object)null) ? s.m_areaMarkerCircle.m_nrOfSegments : (-1))
				});
				float buildScale = BuildScale;
				station.m_rangeBuild = value.RangeBuild * buildScale;
				station.m_extraRangePerLevel = value.ExtraRangePerLevel * buildScale;
				CircleProjector areaMarkerCircle = station.m_areaMarkerCircle;
				if ((Object)(object)areaMarkerCircle != (Object)null && value.Segments >= 0)
				{
					areaMarkerCircle.m_nrOfSegments = Mathf.Max(3, Mathf.RoundToInt((float)value.Segments * buildScale));
				}
			}
		}

		private void Apply(StationExtension ext)
		{
			if (!((Object)(object)ext == (Object)null))
			{
				StrongBox<float> value = vanillaExtensions.GetValue(ext, (StationExtension e) => new StrongBox<float>(e.m_maxStationDistance));
				ext.m_maxStationDistance = value.Value * ExtensionScale;
			}
		}

		private void Apply(Demister demister)
		{
			if (!((Object)(object)demister == (Object)null) && !((Object)(object)demister.m_forceField == (Object)null))
			{
				StrongBox<float> value = vanillaDemisters.GetValue(demister, (Demister d) => new StrongBox<float>(d.m_forceField.endRange));
				demister.m_forceField.endRange = value.Value * MistClearScale;
			}
		}
	}
	internal sealed class Resting : Tweak
	{
		[HarmonyPatch(typeof(SE_Cozy), "UpdateStatusEffect")]
		private static class GrantWhileSitting
		{
			private static void Postfix(SE_Cozy __instance)
			{
				if (!Instance.On || !Instance.instantRested.Value || ((StatusEffect)__instance).m_time > __instance.m_delay)
				{
					return;
				}
				Character character = ((StatusEffect)__instance).m_character;
				Player val = (Player)(object)((character is Player) ? character : null);
				if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer || !((Character)val).IsSitting())
				{
					return;
				}
				int statusEffectHash = __instance.m_statusEffectHash;
				if (statusEffectHash == 0)
				{
					return;
				}
				SEMan sEMan = ((Character)val).GetSEMan();
				if (sEMan != null)
				{
					if (!sEMan.HaveStatusEffect(statusEffectHash))
					{
						val.m_comfortLevel = SE_Rested.CalculateComfortLevel(val);
					}
					sEMan.AddStatusEffect(statusEffectHash, true, 0, 0f, (short)(-1));
				}
			}
		}

		[HarmonyPatch(typeof(Player), "UpdateFood")]
		private static class HealForComfort
		{
			private static void Prefix(Player __instance, float dt, bool forceUpdate, out float __state)
			{
				__state = __instance.m_foodRegenTimer;
				pending = ((forceUpdate || __instance.m_foodRegenTimer + dt < 10f) ? 0f : Instance.ComfortHealth(__instance));
			}

			private static void Finalizer(Player __instance, float __state)
			{
				float pending = Resting.pending;
				Resting.pending = 0f;
				if (pending > 0f && __instance.m_foodRegenTimer < __state)
				{
					((Character)__instance).Heal(pending, true);
				}
			}
		}

		[HarmonyPatch(typeof(Character), "Heal")]
		private static class FoldIntoFoodHeal
		{
			private static void Prefix(Character __instance, ref float hp)
			{
				if (pending > 0f && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					hp += pending;
					pending = 0f;
				}
			}
		}

		internal static readonly Resting Instance = new Resting();

		private const float RegenInterval = 10f;

		private ConfigEntry<bool> instantRested;

		private ConfigEntry<float> healthPerComfortLevel;

		private ConfigEntry<bool> requireSitting;

		private static float pending;

		internal override string Section => "Resting";

		protected override string Summary => "Sitting down by a fire grants Rested at once, and resting by a fire heals you for the comfort of the spot, on top of what your food heals.";

		private Resting()
		{
		}

		protected override void Bind(ConfigFile config)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			instantRested = config.Bind<bool>(Section, "InstantRested", true, "Sitting down by a fire grants Rested at once, at the comfort of where you sit, instead of after ten seconds of Resting. Standing by the fire waits as in vanilla.");
			healthPerComfortLevel = config.Bind<float>(Section, "HealthPerComfortLevel", 2f, new ConfigDescription("Health healed per comfort level every ten seconds while you rest by a fire, on the same beat as your food heals you. A lone campfire is comfort 1, a furnished hall reaches ten and more, so 2 is about 12 health a minute at a campfire and 120 in a good hall. 0 is vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			requireSitting = config.Bind<bool>(Section, "RequireSitting", true, "Only heal for comfort while you are actually sitting - a chair, a bench or the sit emote. Off heals whenever you are Resting, which includes standing by the fire under your own roof.");
		}

		private float ComfortHealth(Player player)
		{
			if (!base.On || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return 0f;
			}
			if (requireSitting.Value && !((Character)player).IsSitting())
			{
				return 0f;
			}
			SEMan sEMan = ((Character)player).GetSEMan();
			if (sEMan == null || !sEMan.HaveStatusEffect(SEMan.s_statusEffectResting))
			{
				return 0f;
			}
			int comfortLevel = player.GetComfortLevel();
			if (comfortLevel <= 0)
			{
				return 0f;
			}
			return (float)comfortLevel * healthPerComfortLevel.Value;
		}
	}
	internal static class Danger
	{
		[HarmonyPatch(typeof(Player), "RPC_OnTargeted")]
		[Serves(new Type[] { typeof(Danger) })]
		private static class NoticeEnragedEnemy
		{
			private static void Postfix(Player __instance, bool alerted)
			{
				if (alerted && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					lastEnraged = Time.time;
				}
			}
		}

		private const float CheckInterval = 0.25f;

		private const float EnragedMemory = 1.5f;

		internal const string Section = "General";

		internal static ConfigEntry<float> ThreatRadius;

		internal static ConfigEntry<bool> EnragedEnemies;

		internal static ConfigEntry<bool> BossFights;

		private static float lastCheck = float.NegativeInfinity;

		private static bool inDanger;

		private static float lastEnraged = float.NegativeInfinity;

		internal static void Bind(ConfigFile config, string effect)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			ThreatRadius = config.Bind<float>("General", "ThreatRadius", 25f, new ConfigDescription("Metres. A hostile creature closer than this puts you in combat, whether it has noticed you or not: " + effect, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>()));
			EnragedEnemies = config.Bind<bool>("General", "EnragedEnemies", true, "An enemy that has noticed you and is coming for you puts you in combat at any distance, not only inside ThreatRadius. Off means only the radius counts.");
			BossFights = config.Bind<bool>("General", "BossFights", true, "A boss health bar on screen puts you in combat, whoever the boss is after and however far away it is.");
		}

		internal static bool Near(Player player)
		{
			if (Time.time - lastCheck < 0.25f)
			{
				return inDanger;
			}
			lastCheck = Time.time;
			inDanger = Enraged() || BossBarShowing() || HostileWithin(player, ThreatRadius.Value);
			return inDanger;
		}

		private static bool Enraged()
		{
			if (EnragedEnemies.Value)
			{
				return Time.time - lastEnraged < 1.5f;
			}
			return false;
		}

		private static bool BossBarShowing()
		{
			if (BossFights.Value && (Object)(object)EnemyHud.instance != (Object)null)
			{
				return EnemyHud.instance.ShowingBossHud();
			}
			return false;
		}

		private static bool HostileWithin(Player player, float radius)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: 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)
			Vector3 position = ((Component)player).transform.position;
			float num = radius * radius;
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if (!((Object)(object)allCharacter == (Object)null) && !((Object)(object)allCharacter == (Object)(object)player) && !allCharacter.IsDead() && BaseAI.IsEnemy((Character)(object)player, allCharacter))
				{
					Vector3 val = ((Component)allCharacter).transform.position - position;
					if (((Vector3)(ref val)).sqrMagnitude <= num)
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	internal static class FavoriteMark
	{
		internal const string Key = "OMP_Favorite";

		internal static bool IsSet(ItemData item)
		{
			if (item != null && item.m_customData != null && item.m_customData.TryGetValue("OMP_Favorite", out var value))
			{
				return value == "1";
			}
			return false;
		}
	}
	internal static class Hotkeys
	{
		internal static bool Pressed(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: 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)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, false))
			{
				return false;
			}
			return ModifiersHeld(shortcut);
		}

		internal static bool Held(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: 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)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !ZInput.GetKey(((KeyboardShortcut)(ref shortcut)).MainKey, false))
			{
				return false;
			}
			return ModifiersHeld(shortcut);
		}

		internal static string Describe(params KeyboardShortcut[] chord)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			for (int i = 0; i < chord.Length; i++)
			{
				KeyboardShortcut val = chord[i];
				if ((int)((KeyboardShortcut)(ref val)).MainKey == 0)
				{
					continue;
				}
				foreach (KeyCode modifier in ((KeyboardShortcut)(ref val)).Modifiers)
				{
					list.Add(KeyName(modifier));
				}
				list.Add(KeyName(((KeyboardShortcut)(ref val)).MainKey));
			}
			return string.Join(" + ", list.ToArray());
		}

		private unsafe static string KeyName(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			string text = ZInput.KeyCodeToDisplayName(key);
			if (!string.IsNullOrEmpty(text) && !text.StartsWith("$"))
			{
				return text;
			}
			return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString();
		}

		private static bool ModifiersHeld(KeyboardShortcut shortcut)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				if (!ZInput.GetKey(modifier, false))
				{
					return false;
				}
			}
			return true;
		}
	}
	internal static class LegacyConfig
	{
		private const string OldFile = "rauschekuh.odinsmissingpatch.cfg";

		internal static HashSet<string> Sections(string path)
		{
			HashSet<string> hashSet = new HashSet<string>();
			try
			{
				if (File.Exists(path))
				{
					foreach (ConfigDefinition key in Read(path).Keys)
					{
						hashSet.Add(key.Section);
					}
				}
			}
			catch (Exception ex)
			{
				TweakHost.Log.LogWarning((object)("could not read " + path + ": " + ex.Message));
			}
			return hashSet;
		}

		internal static void Import(ConfigFile config, HashSet<string> known)
		{
			try
			{
				string path = Path.Combine(Paths.ConfigPath, "rauschekuh.odinsmissingpatch.cfg");
				if (!File.Exists(path))
				{
					return;
				}
				Dictionary<ConfigDefinition, string> dictionary = Read(path);
				int num = 0;
				foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in config)
				{
					if (!known.Contains(item.Key.Section) && dictionary.TryGetValue(item.Key, out var value))
					{
						item.Value.SetSerializedValue(value);
						num++;
					}
				}
				if (num > 0)
				{
					config.Save();
					TweakHost.Log.LogInfo((object)("took " + num + " setting(s) over from rauschekuh.odinsmissingpatch.cfg"));
				}
			}
			catch (Exception ex)
			{
				TweakHost.Log.LogWarning((object)("could not read the settings of rauschekuh.odinsmissingpatch.cfg: " + ex.Message));
			}
		}

		private static Dictionary<ConfigDefinition, string> Read(string path)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			Dictionary<ConfigDefinition, string> dictionary = new Dictionary<ConfigDefinition, string>();
			string text = "";
			string[] array = File.ReadAllLines(path);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0 || text2.StartsWith("#"))
				{
					continue;
				}
				if (text2.StartsWith("[") && text2.EndsWith("]"))
				{
					text = text2.Substring(1, text2.Length - 2);
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0 && text.Length > 0)
				{
					dictionary[new ConfigDefinition(text, text2.Substring(0, num).Trim())] = text2.Substring(num + 1).Trim();
				}
			}
			return dictionary;
		}
	}
	internal static class Palette
	{
		internal static readonly Color Gold = new Color(1f, 0.8f, 0.3f, 1f);

		internal static readonly Color ChestYellow = new Color(1f, 0.84f, 0.3f, 1f);

		internal const string ChestYellowTag = "<color=#ffd64d>";
	}
	[AttributeUsage(AttributeTargets.Class)]
	internal sealed class ServesAttribute : Attribute
	{
		internal readonly Type[] Tweaks;

		public bool Optional { get; set; }

		public ServesAttribute(params Type[] tweaks)
		{
			Tweaks = tweaks;
		}
	}
	[AttributeUsage(AttributeTargets.Class)]
	internal sealed class AlwaysAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Class)]
	internal sealed class LoadHookAttribute : Attribute
	{
	}
	internal static class Patcher
	{
		private sealed class PatchClass
		{
			internal Type Type;

			internal Tweak[] Serves;

			internal bool Optional;

			internal bool LoadHook;

			internal bool Applied;

			internal bool Failed;
		}

		private static readonly List<PatchClass> classes = new List<PatchClass>();

		private static Harmony harmony;

		private static ManualLogSource log;

		internal static void Map(IList<Tweak> tweaks, ManualLogSource logSource)
		{
			log = logSource;
			Dictionary<Type, Tweak> byType = tweaks.ToDictionary((Tweak t) => t.GetType());
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly());
			foreach (Type type in typesFromAssembly)
			{
				if (!type.IsDefined(typeof(HarmonyPatch), inherit: false))
				{
					continue;
				}
				Tweak[] array = Owners(type, byType);
				if (array != null)
				{
					classes.Add(new PatchClass
					{
						Type = type,
						Serves = array,
						Optional = type.GetCustomAttributes(typeof(ServesAttribute), inherit: false).Cast<ServesAttribute>().Any((ServesAttribute s) => s.Optional),
						LoadHook = type.IsDefined(typeof(LoadHookAttribute), inherit: false)
					});
				}
			}
		}

		private static Tweak[] Owners(Type type, Dictionary<Type, Tweak> byType)
		{
			if (type.IsDefined(typeof(AlwaysAttribute), inherit: false))
			{
				return new Tweak[0];
			}
			ServesAttribute servesAttribute = (ServesAttribute)type.GetCustomAttributes(typeof(ServesAttribute), inherit: false).FirstOrDefault();
			if (servesAttribute != null)
			{
				List<Tweak> list = new List<Tweak>();
				Type[] tweaks = servesAttribute.Tweaks;
				foreach (Type tweak in tweaks)
				{
					if (byType.TryGetValue(tweak, out var value))
					{
						list.Add(value);
						continue;
					}
					if (typeof(Tweak).IsAssignableFrom(tweak))
					{
						log.LogWarning((object)(type.FullName + " serves " + tweak.Name + ", which is not in the Tweaks list"));
						continue;
					}
					list.AddRange(byType.Values.Where((Tweak t) => t.Uses.Contains(tweak)));
				}
				if (list.Count <= 0)
				{
					return null;
				}
				return list.Distinct().ToArray();
			}
			Type declaringType = type.DeclaringType;
			while (declaringType != null)
			{
				if (byType.TryGetValue(declaringType, out var value2))
				{
					return new Tweak[1] { value2 };
				}
				declaringType = declaringType.DeclaringType;
			}
			log.LogWarning((object)(type.FullName + " belongs to no tweak and names none, so it is always applied"));
			return new Tweak[0];
		}

		internal static bool AnyServedOn(Type patchClass)
		{
			foreach (PatchClass @class in classes)
			{
				if (@class.Type != patchClass)
				{
					continue;
				}
				Tweak[] serves = @class.Serves;
				for (int i = 0; i < serves.Length; i++)
				{
					if (serves[i].On)
					{
						return true;
					}
				}
				return false;
			}
			return false;
		}

		internal static bool MayNeedRestart(Tweak tweak)
		{
			return classes.Any((PatchClass c) => c.LoadHook && c.Serves.Contains(tweak));
		}

		internal static void Apply(Harmony instance, IList<Tweak> tweaks)
		{
			harmony = instance;
			foreach (PatchClass @class in classes)
			{
				if (@class.Serves.Length == 0 || @class.Serves.Any((Tweak t) => t.Wanted && !t.Broken))
				{
					TryApply(@class);
				}
			}
			foreach (Tweak tweak in tweaks)
			{
				if (tweak.Wanted && !tweak.Broken)
				{
					GoLive(tweak);
				}
				tweak.Enabled.SettingChanged += delegate
				{
					SwitchedOn(tweak);
				};
			}
		}

		private static void SwitchedOn(Tweak tweak)
		{
			if (!tweak.Wanted || tweak.Patched || tweak.Broken)
			{
				return;
			}
			List<PatchClass> list = classes.Where((PatchClass c) => c.Serves.Contains(tweak) && !c.Applied && !c.Failed).ToList();
			if (list.Any((PatchClass c) => c.LoadHook))
			{
				log.LogInfo((object)(tweak.Section + " was off at launch and takes effect at the next start of the game"));
				return;
			}
			foreach (PatchClass item in list)
			{
				TryApply(item);
			}
			if (!tweak.Broken)
			{
				GoLive(tweak);
				log.LogInfo((object)(tweak.Section + " switched on"));
			}
		}

		private static void GoLive(Tweak tweak)
		{
			tweak.Patched = true;
			tweak.Refresh();
		}

		private static void TryApply(PatchClass patch)
		{
			try
			{
				harmony.CreateClassProcessor(patch.Type).Patch();
				patch.Applied = true;
			}
			catch (Exception ex)
			{
				patch.Failed = true;
				Unpatch(patch);
				string message = (ex.InnerException ?? ex).Message;
				if (patch.Serves.Length == 0 || patch.Optional)
				{
					log.LogError((object)(patch.Type.FullName + " could not be patched, carrying on without it: " + message));
					return;
				}
				Tweak[] serves = patch.Serves;
				for (int i = 0; i < serves.Length; i++)
				{
					Break(serves[i], patch.Type, message);
				}
			}
		}

		private static void Break(Tweak tweak, Type failed, string cause)
		{
			if (tweak.Broken)
			{
				return;
			}
			tweak.Broken = true;
			tweak.Patched = false;
			log.LogError((object)(tweak.Section + " is switched off: " + failed.FullName + " could not be patched, most likely because a game update changed it. " + cause));
			foreach (PatchClass @class in classes)
			{
				if (@class.Applied && @class.Serves.Contains(tweak) && !@class.Serves.Any((Tweak t) => !t.Broken && (t.Patched || t.Wanted)))
				{
					Unpatch(@class);
					@class.Applied = false;
				}
			}
			tweak.Refresh();
		}

		private static void Unpatch(PatchClass patch)
		{
			try
			{
				foreach (MethodBase item in harmony.GetPatchedMethods().ToList())
				{
					Patches patchInfo = Harmony.GetPatchInfo(item);
					if (patchInfo == null)
					{
						continue;
					}
					foreach (Patch item2 in patchInfo.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Transpilers).Concat(patchInfo.Finalizers)
						.Concat(patchInfo.ILManipulators)
						.ToList())
					{
						if (item2.owner == harmony.Id && item2.PatchMethod.DeclaringType == patch.Type)
						{
							harmony.Unpatch(item, item2.PatchMethod);
						}
					}
				}
			}
			catch (Exception ex)
			{
				log.LogError((object)("could not take out " + patch.Type.FullName + ": " + ex.Message));
			}
		}
	}
	internal static class Translations
	{
		[HarmonyPatch(typeof(Localization), "SetupLanguage")]
		[Always]
		private static class Load
		{
			private static void Postfix(Localization __instance, string language)
			{
				if (__instance == null || !Read() || !Reader())
				{
					return;
				}
				object obj = null;
				try
				{
					obj = textAsset.Invoke(new object[1] { csv });
					loadCsv.Invoke(__instance, new object[2]
					{
						obj,
						Has(language) ? language : "English"
					});
					Revision++;
				}
				catch (Exception ex)
				{
					TweakHost.Log.LogWarning((object)("could not load translations.csv: " + ex.Message));
				}
				finally
				{
					Object val = (Object)((obj is Object) ? obj : null);
					if (val != (Object)null)
					{
						Object.Destroy(val);
					}
				}
			}
		}

		private const string FileName = "translations.csv";

		private const string Fallback = "English";

		private static string csv;

		private static string[] languages;

		private static ConstructorInfo textAsset;

		private static MethodInfo loadCsv;

		private static bool looked;

		internal static int Revision { get; private set; }

		private static bool Read()
		{
			if (languages != null)
			{
				return csv != null;
			}
			languages = new string[0];
			try
			{
				string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "translations.csv");
				if (!File.Exists(text))
				{
					TweakHost.Log.LogDebug((object)("no translations.csv beside the DLL: " + text));
					return false;
				}
				csv = File.ReadAllText(text);
				using (StringReader stringReader = new StringReader(csv))
				{
					string text2 = stringReader.ReadLine();
					languages = ((text2 != null) ? text2.Split(new char[1] { ',' }) : new string[0]);
				}
				for (int i = 0; i < languages.Length; i++)
				{
					languages[i] = languages[i].Trim().Trim(new char[1] { '"' });
				}
			}
			catch (Exception ex)
			{
				csv = null;
				TweakHost.Log.LogWarning((object)("could not read translations.csv: " + ex.Message));
			}
			return csv != null;
		}

		private static bool Has(string language)
		{
			string[] array = languages;
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i] == language)
				{
					return true;
				}
			}
			return false;
		}

		private static bool Reader()
		{
			if (looked)
			{
				if (loadCsv != null)
				{
					return textAsset != null;
				}
				return false;
			}
			looked = true;
			Type type = Type.GetType("UnityEngine.TextAsset, UnityEngine.CoreModule");
			if (type != null)
			{
				textAsset = type.GetConstructor(new Type[1] { typeof(string) });
				loadCsv = typeof(Localization).GetMethod("LoadCSV", new Type[2]
				{
					type,
					typeof(string)
				});
			}
			if (loadCsv == null || textAsset == null)
			{
				TweakHost.Log.LogWarning((object)"Localization.LoadCSV not found; the mod's words stay untranslated");
				return false;
			}
			return true;
		}
	}
	internal abstract class Tweak
	{
		private const float MinMultiplier = 0.1f;

		private const float MaxMultiplier = 20f;

		private const string RestartNote = " If it was off when the game started, switching it on may only take effect after a restart.";

		private ConfigEntry<bool> enabled;

		private readonly List<Action> settingHandlers = new List<Action>();

		internal abstract string Section { get; }

		protected abstract string Summary { get; }

		internal virtual Type[] Uses => Type.EmptyTypes;

		internal bool On
		{
			get
			{
				if (Patched)
				{
					return Wanted;
				}
				return false;
			}
		}

		internal bool Wanted
		{
			get
			{
				if (enabled != null)
				{
					return enabled.Value;
				}
				return false;
			}
		}

		internal ConfigEntry<bool> Enabled => enabled;

		internal bool Patched { get; set; }

		internal bool Broken { get; set; }

		protected virtual void Bind(ConfigFile config)
		{
		}

		internal void Setup(ConfigFile config, bool mayNeedRestart)
		{
			enabled = config.Bind<bool>(Section, "Enabled", true, Summary + (mayNeedRestart ? " If it was off when the game started, switching it on may only take effect after a restart." : ""));
			Bind(config);
		}

		internal void Refresh()
		{
			foreach (Action settingHandler in settingHandlers)
			{
				settingHandler();
			}
		}

		protected ConfigEntry<float> BindMultiplier(ConfigFile config, string key, float value, string description)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			return config.Bind<float>(Section, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 20f), Array.Empty<object>()));
		}

		protected ConfigEntry<string> BindList(ConfigFile config, string key, string value, string description, Action<List<string>> apply)
		{
			ConfigEntry<string> entry = config.Bind<string>(Section, key, value, description);
			entry.SettingChanged += delegate
			{
				apply(Items(entry.Value));
			};
			apply(Items(entry.Value));
			return entry;
		}

		internal static List<string> Items(string list)
		{
			List<string> list2 = new List<string>();
			string[] array = (list ?? "").Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					list2.Add(text);
				}
			}
			return list2;
		}

		protected void OnSettingChanged(ConfigFile config, Action handler)
		{
			settingHandlers.Add(handler);
			config.SettingChanged += delegate(object sender, SettingChangedEventArgs args)
			{
				if (args.ChangedSetting.Definition.Section == Section)
				{
					handler();
				}
			};
		}
	}
	internal static class TweakHost
	{
		internal static ManualLogSource Log;

		internal static BaseUnityPlugin Plugin;

		internal static void Start(BaseUnityPlugin plugin, ManualLogSource logger, ConfigFile config, string guid, Tweak[] tweaks, Action<ConfigFile> bindShared = null)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			Plugin = plugin;
			Log = logger;
			HashSet<string> known = LegacyConfig.Sections(config.ConfigFilePath);
			bindShared?.Invoke(config);
			Patcher.Map(tweaks, logger);
			foreach (Tweak tweak in tweaks)
			{
				tweak.Setup(config, Patcher.MayNeedRestart(tweak));
			}
			LegacyConfig.Import(config, known);
			Patcher.Apply(new Harmony(guid), tweaks);
			string text = string.Join(", ", (from t in tweaks
				where t.On
				select t.Section).ToArray());
			logger.LogInfo((object)((text.Length > 0) ? ("tweaks on: " + text) : "every tweak is switched off"));
		}
	}
	internal static class UiAssets
	{
		private static readonly Dictionary<string, Sprite> icons = new Dictionary<string, Sprite>();

		private static MethodInfo loadImage;

		internal static Sprite Icon(string name)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			if (icons.TryGetValue(name, out var value))
			{
				return value;
			}
			value = null;
			try
			{
				string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
				string text = IconPath(directoryName, name);
				if (text != null)
				{
					Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
					if (LoadPng(val, File.ReadAllBytes(text)))
					{
						((Texture)val).filterMode = (FilterMode)1;
						value = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
					}
				}
				else
				{
					TweakHost.Log.LogWarning((object)("icon missing: " + name + ".png, looked in " + Path.Combine(directoryName, "icons") + " and " + directoryName));
				}
			}
			catch (Exception ex)
			{
				TweakHost.Log.LogWarning((object)("could not load icon " + name + ": " + ex.Message));
			}
			icons[name] = value;
			return value;
		}

		private static string IconPath(string dir, string name)
		{
			string path = name + ".png";
			string[] array = new string[2]
			{
				Path.Combine(Path.Combine(dir, "icons"), path),
				Path.Combine(dir, path)
			};
			foreach (string text in array)
			{
				if (File.Exists(text))
				{
					return text;
				}
			}
			return null;
		}

		private static bool LoadPng(Texture2D texture, byte[] png)
		{
			if (loadImage == null)
			{
				Type type = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule");
				loadImage = ((type != null) ? type.GetMethod("LoadImage", new Type[2]
				{
					typeof(Texture2D),
					typeof(byte[])
				}) : null);
				if (loadImage == null)
				{
					TweakHost.Log.LogWarning((object)"ImageConversion.LoadImage not found; icons stay blank");
					return false;
				}
			}
			return (bool)loadImage.Invoke(null, new object[2] { texture, png });
		}

		internal static GameObject TooltipPrefab(InventoryGui gui)
		{
			InventoryGrid playerGrid = gui.m_playerGrid;
			GameObject val = (((Object)(object)playerGrid != (Object)null) ? playerGrid.m_elementPrefab : null);
			InventoryElement val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<InventoryElement>() : null);
			if ((Object)(object)val2 != (Object)null && (Object)(object)val2.m_tooltip != (Object)null && (Object)(object)val2.m_tooltip.m_tooltipPrefab != (Object)null)
			{
				return val2.m_tooltip.m_tooltipPrefab;
			}
			UITooltip[] componentsInChildren = ((Component)gui).GetComponentsInChildren<UITooltip>(true);
			foreach (UITooltip val3 in componentsInChildren)
			{
				if ((Object)(object)val3.m_tooltipPrefab != (Object)null)
				{
					return val3.m_tooltipPrefab;
				}
			}
			return null;
		}
	}
}