Decompiled source of TweakStats v1.0.0

BepInEx/plugins/TweakStats/TweakStats.dll

Decompiled 5 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Kriona.Shared;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Kriona")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Kriona Hagen - MIT License")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Tweak Stats")]
[assembly: AssemblyTitle("Tweak Stats")]
[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;
		}
	}
}
namespace TweakStats
{
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	public static class ObjectDBAwakePatch
	{
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			Tweaks.Apply();
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	public static class ObjectDBCopyOtherDBPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			Tweaks.Apply();
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	public static class ZNetSceneAwakePatch
	{
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			Tweaks.Apply();
		}
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	public static class ZNetAwakePatch
	{
		private static void Postfix(ZNet __instance)
		{
			if (__instance.IsServer())
			{
				Tweaks.CurrentWorld = ZNet.World;
				Tweaks.Apply();
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	public static class ZNetPeerInfoPatch
	{
		private static void Postfix(ZNet __instance)
		{
			if (!__instance.IsServer() && ZNet.World != Tweaks.CurrentWorld)
			{
				Tweaks.CurrentWorld = ZNet.World;
				Tweaks.Apply();
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	public static class ZNetOnDestroyPatch
	{
		private static void Postfix()
		{
			Tweaks.CurrentWorld = null;
			Tweaks.ClearServerConfig();
			ServerSync.Clear();
			Tweaks.Apply();
		}
	}
	public static class Commands
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Run;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleOptionsFetcher <>9__2_0;

			public static Converter<string, string> <>9__7_0;

			public static Converter<string, string> <>9__13_1;

			public static Comparison<ItemDrop> <>9__14_0;

			public static Comparison<Recipe> <>9__14_1;

			public static Converter<GameObject, (string name, string m_name)> <>9__14_2;

			public static Converter<GameObject, (string name, string m_name)> <>9__14_3;

			public static Converter<StatusEffect, (string name, string m_name)> <>9__14_4;

			public static Comparison<(string prefabName, string gameName)> <>9__15_0;

			internal List<string> <Register>b__2_0()
			{
				return new List<string> { "find", "inventory", "world", "dump", "list", "reload" };
			}

			internal string <World>b__7_0(string entry)
			{
				return "[Worlds: " + entry + "]";
			}

			internal string <Dump>b__13_1(string selector)
			{
				return selector.Trim();
			}

			internal int <List>b__14_0(ItemDrop a, ItemDrop b)
			{
				return string.Compare(((Object)((Component)a).gameObject).name, ((Object)((Component)b).gameObject).name, StringComparison.OrdinalIgnoreCase);
			}

			internal int <List>b__14_1(Recipe a, Recipe b)
			{
				return string.Compare(((Object)((Component)a.m_item).gameObject).name, ((Object)((Component)b.m_item).gameObject).name, StringComparison.OrdinalIgnoreCase);
			}

			internal (string name, string m_name) <List>b__14_2(GameObject creature)
			{
				return (name: ((Object)creature).name, m_name: creature.GetComponent<Character>().m_name);
			}

			internal (string name, string m_name) <List>b__14_3(GameObject piece)
			{
				return (name: ((Object)piece).name, m_name: piece.GetComponent<Piece>().m_name);
			}

			internal (string name, string m_name) <List>b__14_4(StatusEffect effect)
			{
				return (name: ((Object)effect).name, m_name: effect.m_name);
			}

			internal int <AppendNames>b__15_0((string prefabName, string gameName) a, (string prefabName, string gameName) b)
			{
				return string.Compare(a.prefabName, b.prefabName, StringComparison.OrdinalIgnoreCase);
			}
		}

		private const int MaxFound = 30;

		private const int MaxDumpNameLength = 100;

		public static void Register()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			object obj = <>O.<0>__Run;
			if (obj == null)
			{
				ConsoleEvent val = Run;
				<>O.<0>__Run = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__2_0;
			if (obj2 == null)
			{
				ConsoleOptionsFetcher val2 = () => new List<string> { "find", "inventory", "world", "dump", "list", "reload" };
				<>c.<>9__2_0 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("tweakstats", "find <text>, inventory, world, dump <section>, list or reload - Tweak Stats config tools", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false);
		}

		private static void Run(ConsoleEventArgs args)
		{
			string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
			string text2 = ((args.Length > 2) ? args.ArgsAll.Substring(args.ArgsAll.IndexOf(' ') + 1).Trim() : "");
			if (text == "find" && text2 != "")
			{
				Find(args.Context, text2);
				return;
			}
			switch (text)
			{
			case "inventory":
			case "inv":
				Inventory(args.Context);
				return;
			case "world":
				World(args.Context);
				return;
			case "dump":
				if (text2 != "")
				{
					Dump(args.Context, text2);
					return;
				}
				break;
			}
			if (text == "list")
			{
				List(args.Context);
				return;
			}
			if (text == "reload")
			{
				Tweaks.Reload();
				args.Context.AddString("Reloaded " + Path.GetFileName(Plugin.ConfigPath));
				return;
			}
			args.Context.AddString("tweakstats find <text> - finds the sections for items, creatures, build pieces and status effects whose name contains the text, e.g. tweakstats find troll");
			args.Context.AddString("tweakstats inventory - lists the items you're carrying with their section names and main stats");
			args.Context.AddString("tweakstats world - shows the world's name and ID and the server you're on, for [Worlds: ...] groups");
			args.Context.AddString("tweakstats dump <section> - writes every stat of what a section names to " + DumpFileName("SwordIron") + ", e.g. tweakstats dump SwordIron");
			args.Context.AddString("tweakstats list - writes every item, recipe, creature, build piece and status effect name to " + ListFileName());
			args.Context.AddString("tweakstats reload - reads " + Path.GetFileName(Plugin.ConfigPath) + " again");
		}

		private static void Find(Terminal terminal, string text)
		{
			List<string> list = new List<string>();
			foreach (ItemDrop item in Prefabs.Items())
			{
				AddIfFound(list, text, "", ((Object)((Component)item).gameObject).name, item.m_itemData.m_shared.m_name);
			}
			foreach (GameObject item2 in Prefabs.Creatures())
			{
				AddIfFound(list, text, "Creature:", ((Object)item2).name, item2.GetComponent<Character>().m_name);
			}
			foreach (GameObject item3 in Prefabs.Pieces())
			{
				AddIfFound(list, text, "Piece:", ((Object)item3).name, item3.GetComponent<Piece>().m_name);
			}
			foreach (StatusEffect item4 in Prefabs.Effects())
			{
				AddIfFound(list, text, "Effect:", ((Object)item4).name, item4.m_name);
			}
			list.Sort(StringComparer.OrdinalIgnoreCase);
			if (list.Count == 0)
			{
				terminal.AddString("Nothing matches \"" + text + "\"" + (((Object)(object)ZNetScene.instance == (Object)null) ? " - join a world to search creatures and build pieces too" : ""));
				return;
			}
			for (int i = 0; i < list.Count && i < 30; i++)
			{
				terminal.AddString(list[i]);
			}
			if (list.Count > 30)
			{
				terminal.AddString("...and " + (list.Count - 30) + " more - search for more of the name to narrow it down");
			}
		}

		private static void AddIfFound(List<string> found, string text, string kind, string prefabName, string gameName)
		{
			string text2 = Prefabs.Localize(gameName);
			if (prefabName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				found.Add("[" + kind + prefabName + "]" + ((text2 != "") ? (" - " + text2) : ""));
			}
		}

		private static void Inventory(Terminal terminal)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				terminal.AddString("Join a world to list the items you're carrying");
				return;
			}
			List<ItemData> allItemsInGridOrder = ((Humanoid)localPlayer).GetInventory().GetAllItemsInGridOrder();
			if (allItemsInGridOrder.Count == 0)
			{
				terminal.AddString("You aren't carrying anything");
				return;
			}
			terminal.AddString("You're carrying " + allItemsInGridOrder.Count + ((allItemsInGridOrder.Count == 1) ? " item" : " items") + " - section, name and stats:");
			foreach (ItemData item in allItemsInGridOrder)
			{
				terminal.AddString(DescribeItem(item));
			}
		}

		private static void World(Terminal terminal)
		{
			World currentWorld = Tweaks.CurrentWorld;
			if ((Object)(object)ZNet.instance == (Object)null || currentWorld == null)
			{
				terminal.AddString("Join a world to see its name and ID");
				return;
			}
			terminal.AddString("World: " + currentWorld.m_name + " (ID " + currentWorld.m_uid + ")");
			terminal.AddString("Playing: " + DescribeConnection());
			List<string> list = new List<string>
			{
				currentWorld.m_name,
				currentWorld.m_uid.ToString()
			};
			string text = ServerEntry();
			if (text != null)
			{
				list.Add(text);
			}
			if (ZNet.IsSinglePlayer)
			{
				list.Add("SinglePlayer");
			}
			terminal.AddString("Sections in " + string.Join(" or ", list.ConvertAll((string entry) => "[Worlds: " + entry + "]")) + " apply here - use the ID or server when other worlds share the name");
		}

		private static string ServerEntry()
		{
			if (ZNet.instance.IsServer())
			{
				return null;
			}
			if (!string.IsNullOrEmpty(ZNet.m_serverHost))
			{
				return ZNet.m_serverHost;
			}
			if (ZNet.m_serverSteamID != 0L)
			{
				return ZNet.m_serverSteamID.ToString();
			}
			if (!string.IsNullOrEmpty(ZNet.m_serverPlayFabPlayerId))
			{
				return ZNet.m_serverPlayFabPlayerId;
			}
			return null;
		}

		private static string DescribeConnection()
		{
			if (ZNet.instance.IsServer())
			{
				if (ZNet.IsSinglePlayer)
				{
					return "single player";
				}
				return "hosting" + ((ZNet.m_ServerName != "") ? (" the server \"" + ZNet.m_ServerName + "\"") : " for other players");
			}
			if (!string.IsNullOrEmpty(ZNet.m_serverHost))
			{
				return "on a server at " + ZNet.m_serverHost + ":" + ZNet.m_serverHostPort;
			}
			if (ZNet.m_serverSteamID != 0L)
			{
				return "on a server with Steam ID " + ZNet.m_serverSteamID;
			}
			if (!string.IsNullOrEmpty(ZNet.m_serverPlayFabPlayerId))
			{
				return "on a crossplay server with PlayFab ID " + ZNet.m_serverPlayFabPlayerId;
			}
			return "on a server";
		}

		private static string DescribeItem(ItemData item)
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Invalid comparison between Unknown and I4
			SharedData shared = item.m_shared;
			string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : shared.m_name);
			string text2 = "[" + text + "] " + Prefabs.Localize(shared.m_name);
			if (item.m_stack > 1)
			{
				text2 = text2 + " x" + item.m_stack;
			}
			List<string> list = new List<string>();
			if (shared.m_maxQuality > 1)
			{
				list.Add("level " + item.m_quality);
			}
			if (item.m_equipped)
			{
				list.Add("equipped");
			}
			string text3 = DescribeDamages(item.GetDamage());
			if (text3 != "")
			{
				list.Add("damages " + text3);
			}
			float armor = item.GetArmor();
			if (armor > 0f)
			{
				list.Add("armor " + TweakValue.Format(armor));
			}
			if ((int)shared.m_itemType == 5)
			{
				list.Add("block " + TweakValue.Format(item.GetBaseBlockPower()));
			}
			List<string> list2 = new List<string>();
			AddFood(list2, shared.m_food, "health");
			AddFood(list2, shared.m_foodStamina, "stamina");
			AddFood(list2, shared.m_foodEitr, "eitr");
			if (list2.Count > 0)
			{
				list.Add("food " + string.Join(" ", list2) + " for " + TweakValue.Format(shared.m_foodBurnTime) + "s");
			}
			if (shared.m_useDurability)
			{
				list.Add("durability " + TweakValue.Format(Math.Round(item.m_durability)) + "/" + TweakValue.Format(item.GetMaxDurability()));
			}
			list.Add("weight " + TweakValue.Format(item.GetWeight(-1)));
			return text2 + " - " + string.Join(", ", list);
		}

		private static void AddFood(List<string> food, float amount, string name)
		{
			if (amount > 0f)
			{
				food.Add(TweakValue.Format(amount) + " " + name);
			}
		}

		private static string DescribeDamages(DamageTypes damages)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			FieldInfo[] fields = typeof(DamageTypes).GetFields();
			foreach (FieldInfo fieldInfo in fields)
			{
				if (fieldInfo.FieldType == typeof(float) && (float)fieldInfo.GetValue(damages) > 0f)
				{
					list.Add(fieldInfo.Name.Substring(2) + " " + TweakValue.Format(fieldInfo.GetValue(damages)));
				}
			}
			return string.Join(" ", list);
		}

		private static void Dump(Terminal terminal, string header)
		{
			header = header.Trim().TrimStart('[').TrimEnd(']');
			List<string> problems = new List<string>();
			StringBuilder text = new StringBuilder();
			int count = 0;
			string fileHeader = header;
			Tweaks.WithoutTweaks(delegate
			{
				foreach (Target item in Targets.Resolve(new List<string>(header.Split(',')).ConvertAll((string selector) => selector.Trim()), problems, null))
				{
					List<string> list = new List<string>();
					item.List(list);
					text.Append("\n[" + item.Header + "]\n");
					foreach (string item2 in list)
					{
						text.Append("# " + item2 + "\n");
					}
					count++;
					fileHeader = ((count == 1) ? item.Header : header);
				}
			});
			foreach (string item3 in problems)
			{
				terminal.AddString(item3);
			}
			if (count != 0)
			{
				string text2 = "# Tweak Stats dump of [" + header + "] from Valheim " + Version.GetVersionString(false) + " on " + DateTime.Now.ToString("yyyy-MM-dd HH:mm") + "\n# These are the game's values before any tweaks. Copy the lines you want into " + Path.GetFileName(Plugin.ConfigPath) + " and remove the # to use them\n";
				string text3 = DumpFileName(fileHeader);
				if (WriteFile(terminal, text3, text2 + text))
				{
					terminal.AddString("Wrote " + count + ((count == 1) ? " section" : " sections") + " to " + Path.Combine(Paths.ConfigPath, text3));
				}
			}
		}

		private static void List(Terminal terminal)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("# Tweak Stats - Names\n\n");
			stringBuilder.Append("Generated by Tweak Stats 1.0.0 from Valheim " + Version.GetVersionString(false) + " on " + DateTime.Now.ToString("yyyy-MM-dd") + ".\n\n");
			stringBuilder.Append("## Items\n\n");
			stringBuilder.Append("| Section | Name | Type | Skill |\n");
			stringBuilder.Append("| --- | --- | --- | --- |\n");
			List<ItemDrop> list = Prefabs.Items();
			list.Sort((ItemDrop a, ItemDrop b) => string.Compare(((Object)((Component)a).gameObject).name, ((Object)((Component)b).gameObject).name, StringComparison.OrdinalIgnoreCase));
			foreach (ItemDrop item in list)
			{
				SharedData shared = item.m_itemData.m_shared;
				stringBuilder.Append("| `[" + ((Object)((Component)item).gameObject).name + "]` | " + Prefabs.DisplayName(item) + " | " + ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() + " | " + ((object)Unsafe.As<SkillType, SkillType>(ref shared.m_skillType)/*cast due to .constrained prefix*/).ToString() + " |\n");
			}
			stringBuilder.Append("\n## Recipes\n\n");
			stringBuilder.Append("| Section | Makes | Station |\n");
			stringBuilder.Append("| --- | --- | --- |\n");
			List<Recipe> list2 = Prefabs.Recipes();
			list2.Sort((Recipe a, Recipe b) => string.Compare(((Object)((Component)a.m_item).gameObject).name, ((Object)((Component)b.m_item).gameObject).name, StringComparison.OrdinalIgnoreCase));
			foreach (Recipe item2 in list2)
			{
				string text = (((Object)(object)item2.m_craftingStation != (Object)null) ? ((Object)((Component)item2.m_craftingStation).gameObject).name : "none");
				stringBuilder.Append("| `[Recipe:" + ((Object)((Component)item2.m_item).gameObject).name + "]` | " + Prefabs.DisplayName(item2.m_item) + " x" + item2.m_amount + " | " + text + " |\n");
			}
			List<GameObject> list3 = Prefabs.Creatures();
			AppendNames(stringBuilder, "Creatures", "Creature:", list3.ConvertAll((GameObject creature) => (name: ((Object)creature).name, m_name: creature.GetComponent<Character>().m_name)));
			List<GameObject> list4 = Prefabs.Pieces();
			AppendNames(stringBuilder, "Build Pieces", "Piece:", list4.ConvertAll((GameObject piece) => (name: ((Object)piece).name, m_name: piece.GetComponent<Piece>().m_name)));
			List<StatusEffect> list5 = Prefabs.Effects();
			AppendNames(stringBuilder, "Status Effects", "Effect:", list5.ConvertAll((StatusEffect effect) => (name: ((Object)effect).name, m_name: effect.m_name)));
			if (WriteFile(terminal, ListFileName(), stringBuilder.ToString()))
			{
				terminal.AddString("Wrote " + list.Count + " items, " + list2.Count + " recipes, " + list3.Count + " creatures, " + list4.Count + " build pieces and " + list5.Count + " status effects to " + Path.Combine(Paths.ConfigPath, ListFileName()));
				if ((Object)(object)ZNetScene.instance == (Object)null)
				{
					terminal.AddString("Join a world to list creatures and build pieces too");
				}
			}
		}

		private static void AppendNames(StringBuilder text, string heading, string kind, List<(string prefabName, string gameName)> entries)
		{
			entries.Sort(((string prefabName, string gameName) a, (string prefabName, string gameName) b) => string.Compare(a.prefabName, b.prefabName, StringComparison.OrdinalIgnoreCase));
			text.Append("\n## " + heading + "\n\n");
			text.Append("| Section | Name |\n");
			text.Append("| --- | --- |\n");
			foreach (var (text2, text3) in entries)
			{
				text.Append("| `[" + kind + text2 + "]` | " + Prefabs.Localize(text3) + " |\n");
			}
		}

		private static bool WriteFile(Terminal terminal, string fileName, string text)
		{
			string text2 = Path.Combine(Paths.ConfigPath, fileName);
			try
			{
				File.WriteAllText(text2, text);
				return true;
			}
			catch (Exception ex)
			{
				terminal.AddString("Couldn't write " + text2 + ": " + ex.Message);
				return false;
			}
		}

		private static string DumpFileName(string header)
		{
			StringBuilder stringBuilder = new StringBuilder();
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			foreach (char c in header)
			{
				if (Array.IndexOf(invalidFileNameChars, c) < 0 && c != ',' && !char.IsWhiteSpace(c))
				{
					stringBuilder.Append(c);
				}
				else if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '_')
				{
					stringBuilder.Append('_');
				}
			}
			string text = stringBuilder.ToString().TrimEnd('_');
			if (text == "")
			{
				text = "all";
			}
			if (text.Length > 100)
			{
				text = text.Substring(0, 100);
			}
			return "kriona.TweakStats.dump." + text + ".txt";
		}

		private static string ListFileName()
		{
			return "kriona.TweakStats.names.md";
		}
	}
	[HarmonyPatch(typeof(Container), "Load")]
	public static class ContainerLoadPatch
	{
		private const int LoadingWidth = 1000;

		private static void Prefix(Container __instance, out int __state)
		{
			__state = -1;
			if (__instance.m_inventory != null && !Instances.IsGrave(__instance))
			{
				__state = __instance.m_inventory.m_width;
				__instance.m_inventory.m_width = 1000;
			}
		}

		private static void Postfix(Container __instance, bool __result, int __state)
		{
			if (__state < 0)
			{
				return;
			}
			__instance.m_inventory.m_width = __state;
			if (__result)
			{
				Instances.MarkResized(__instance);
				if (Instances.KeepsSize(__instance))
				{
					__instance.m_inventory.m_height = __instance.m_height;
				}
				Instances.DropItemsOutside(__instance);
			}
		}
	}
	public static class Instances
	{
		private static readonly FieldInfo characterHealth = typeof(Character).GetField("m_health");

		private static readonly FieldInfo pieceHealth = typeof(WearNTear).GetField("m_health");

		private static readonly FieldInfo containerWidth = typeof(Container).GetField("m_width");

		private static readonly FieldInfo containerHeight = typeof(Container).GetField("m_height");

		private static readonly int resizedKey = StringExtensionMethods.GetStableHashCode("kriona.TweakStats.resized");

		private static Dictionary<object, HashSet<FieldInfo>> touched = new Dictionary<object, HashSet<FieldInfo>>(ReferenceComparer.Instance);

		private static Dictionary<object, HashSet<FieldInfo>> previous = new Dictionary<object, HashSet<FieldInfo>>(ReferenceComparer.Instance);

		public static void Touch(Component component, FieldInfo field)
		{
			if (!touched.TryGetValue(component, out var value))
			{
				value = new HashSet<FieldInfo>();
				touched.Add(component, value);
			}
			value.Add(field);
		}

		public static void StartApply()
		{
			previous = touched;
			touched = new Dictionary<object, HashSet<FieldInfo>>(ReferenceComparer.Instance);
		}

		public static void Sync()
		{
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected O, but got Unknown
			Dictionary<object, HashSet<FieldInfo>> dictionary = new Dictionary<object, HashSet<FieldInfo>>(ReferenceComparer.Instance);
			Merge(dictionary, previous);
			Merge(dictionary, touched);
			if (dictionary.Count == 0 || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return;
			}
			Dictionary<Type, Dictionary<string, Component>> dictionary2 = new Dictionary<Type, Dictionary<string, Component>>();
			foreach (object key in dictionary.Keys)
			{
				Component val = (Component)((key is Component) ? key : null);
				if (!((Object)(object)val == (Object)null))
				{
					Type type = ((object)val).GetType();
					if (!dictionary2.TryGetValue(type, out var value))
					{
						value = new Dictionary<string, Component>();
						dictionary2.Add(type, value);
					}
					value[((Object)val.gameObject).name] = val;
				}
			}
			foreach (KeyValuePair<Type, Dictionary<string, Component>> item in dictionary2)
			{
				Object[] array = Object.FindObjectsByType(item.Key, (FindObjectsSortMode)0);
				for (int i = 0; i < array.Length; i++)
				{
					Component val2 = (Component)array[i];
					if (item.Value.TryGetValue(Utils.GetPrefabName(((Object)val2.gameObject).name), out var value2) && (Object)(object)value2 != (Object)(object)val2)
					{
						Copy(value2, val2, dictionary[value2]);
					}
				}
			}
		}

		public static void RefreshEquipmentEffects()
		{
			foreach (Character s_character in Character.s_characters)
			{
				Humanoid val = (Humanoid)(object)((s_character is Humanoid) ? s_character : null);
				if ((Object)(object)val == (Object)null || (Object)(object)((Character)val).m_nview == (Object)null || !((Character)val).m_nview.IsValid() || !((Character)val).m_nview.IsOwner())
				{
					continue;
				}
				foreach (StatusEffect equipmentStatusEffect in val.m_equipmentStatusEffects)
				{
					((Character)val).m_seman.RemoveStatusEffect(equipmentStatusEffect, true);
				}
				val.m_equipmentStatusEffects.Clear();
				val.UpdateEquipmentStatusEffects();
			}
		}

		private static void Merge(Dictionary<object, HashSet<FieldInfo>> into, Dictionary<object, HashSet<FieldInfo>> from)
		{
			foreach (KeyValuePair<object, HashSet<FieldInfo>> item in from)
			{
				if (!into.TryGetValue(item.Key, out var value))
				{
					value = new HashSet<FieldInfo>();
					into.Add(item.Key, value);
				}
				value.UnionWith(item.Value);
			}
		}

		private static void Copy(Component prefabComponent, Component instance, HashSet<FieldInfo> fields)
		{
			foreach (FieldInfo field in fields)
			{
				field.SetValue(instance, field.GetValue(prefabComponent));
			}
			if (ContainsField(fields, characterHealth))
			{
				Character val = (Character)(object)((instance is Character) ? instance : null);
				if (val != null)
				{
					ResetMaxHealth(val);
				}
			}
			if (ContainsField(fields, pieceHealth))
			{
				WearNTear val2 = (WearNTear)(object)((instance is WearNTear) ? instance : null);
				if (val2 != null)
				{
					ScalePieceHealth(val2);
				}
			}
			if (ContainsField(fields, containerWidth) || ContainsField(fields, containerHeight))
			{
				Container val3 = (Container)(object)((instance is Container) ? instance : null);
				if (val3 != null)
				{
					ResizeInventory(val3);
				}
			}
		}

		private static bool ContainsField(HashSet<FieldInfo> fields, FieldInfo field)
		{
			foreach (FieldInfo field2 in fields)
			{
				if (field2.DeclaringType == field.DeclaringType && field2.Name == field.Name)
				{
					return true;
				}
			}
			return false;
		}

		private static void ResetMaxHealth(Character character)
		{
			if (!((Object)(object)character.m_nview == (Object)null) && character.m_nview.IsValid() && character.m_nview.IsOwner())
			{
				bool num = character.GetHealth() >= character.GetMaxHealth();
				character.SetupMaxHealth();
				if (num)
				{
					character.SetHealth(character.GetMaxHealth());
				}
			}
		}

		private static void ScalePieceHealth(WearNTear piece)
		{
			if (Game.m_worldLevel > 0 && (Object)(object)Game.instance != (Object)null)
			{
				piece.m_health += (float)Game.m_worldLevel * Game.instance.m_worldLevelPieceHPMultiplier * piece.m_health;
			}
			if ((Object)(object)piece.m_nview != (Object)null && piece.m_nview.IsValid())
			{
				piece.m_healthPercentage = Mathf.Clamp01(piece.m_nview.GetZDO().GetFloat(ZDOVars.s_health, piece.m_health) / piece.m_health);
			}
		}

		private static void ResizeInventory(Container container)
		{
			if (container.m_inventory != null && !IsGrave(container))
			{
				container.m_inventory.m_width = container.m_width;
				container.m_inventory.m_height = container.m_height;
				MarkResized(container);
				DropItemsOutside(container);
			}
		}

		public static bool KeepsSize(Container container)
		{
			if (IsGrave(container))
			{
				return false;
			}
			if (!IsResized(container))
			{
				if ((Object)(object)container.m_nview != (Object)null && container.m_nview.IsValid())
				{
					return container.m_nview.GetZDO().GetBool(resizedKey, false);
				}
				return false;
			}
			return true;
		}

		public static void MarkResized(Container container)
		{
			if (!IsGrave(container) && !((Object)(object)container.m_nview == (Object)null) && container.m_nview.IsValid() && container.m_nview.IsOwner() && IsResized(container))
			{
				ZDO zDO = container.m_nview.GetZDO();
				if (!zDO.GetBool(resizedKey, false))
				{
					zDO.Set(resizedKey, true);
					container.m_lastRevision = zDO.DataRevision;
				}
			}
		}

		private static bool IsResized(Container container)
		{
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return false;
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(Utils.GetPrefabName(((Component)container).gameObject));
			Container val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Container>() : null);
			if ((Object)(object)val == (Object)null || !touched.TryGetValue(val, out var value))
			{
				return false;
			}
			if (!ContainsField(value, containerWidth))
			{
				return ContainsField(value, containerHeight);
			}
			return true;
		}

		public static bool IsGrave(Container container)
		{
			if (!((Object)(object)((Component)container).GetComponentInParent<TombStone>() != (Object)null))
			{
				if ((Object)(object)container.m_nview != (Object)null)
				{
					return (Object)(object)((Component)container.m_nview).GetComponent<TombStone>() != (Object)null;
				}
				return false;
			}
			return true;
		}

		public static void DropItemsOutside(Container container)
		{
			//IL_0075: 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)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: 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)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: 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)
			if (IsGrave(container) || (Object)(object)container.m_nview == (Object)null || !container.m_nview.IsValid() || !container.m_nview.IsOwner())
			{
				return;
			}
			Inventory inventory = container.m_inventory;
			foreach (ItemData item in inventory.GetAllItems().FindAll((ItemData item) => item.m_gridPos.x >= inventory.GetWidth() || item.m_gridPos.y >= inventory.GetHeight()))
			{
				Vector3 val = ((Component)container).transform.position + Vector3.up * 0.5f + Random.insideUnitSphere * 0.3f;
				Quaternion val2 = Quaternion.Euler(0f, (float)Random.Range(0, 360), 0f);
				ItemDrop.DropItem(item, 0, val, val2);
				inventory.RemoveItem(item);
			}
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "Awake")]
	public static class ItemDropAwakePatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			if (!((Object)(object)__instance.m_itemData.m_dropPrefab == (Object)null))
			{
				ItemDrop component = __instance.m_itemData.m_dropPrefab.GetComponent<ItemDrop>();
				if ((Object)(object)component != (Object)null && (Object)(object)component != (Object)(object)__instance)
				{
					__instance.m_itemData.m_shared = component.m_itemData.m_shared;
				}
			}
		}
	}
	[HarmonyPatch(typeof(ItemData), "Save", new Type[] { typeof(ZPackage) })]
	public static class ItemDataSavePatch
	{
		private const float MaxSavedDurability = 21000000f;

		private static void Prefix(ItemData __instance)
		{
			if (__instance.m_durability > 21000000f)
			{
				__instance.m_durability = 21000000f;
			}
		}
	}
	public static class Limits
	{
		private class Limit
		{
			public FieldInfo Field;

			public double Min;

			public double Max;

			public string Reason;

			public Limit(Type type, string name, double min, double max, string reason)
			{
				Field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				Min = min;
				Max = max;
				Reason = reason;
			}
		}

		private const double MaxMagnitude = 1000000000.0;

		private static readonly List<Limit> limits = new List<Limit>
		{
			new Limit(typeof(SharedData), "m_maxStackSize", 1.0, 65535.0, "the game can't add items to a stack of 0, and saves stacks as numbers up to 65535"),
			new Limit(typeof(Recipe), "m_amount", 0.0, 1000.0, "the game makes each crafted stack separately, all at once"),
			new Limit(typeof(Attack), "m_projectiles", 0.0, 100.0, "the game makes every projectile of a shot at once"),
			new Limit(typeof(Attack), "m_attackAngle", -360.0, 360.0, "the game checks for hits every 4 degrees of a swing"),
			new Limit(typeof(Container), "m_width", 1.0, 100.0, "the game makes a slot in the window for each slot in the chest"),
			new Limit(typeof(Container), "m_height", 1.0, 100.0, "the game makes a slot in the window for each slot in the chest"),
			new Limit(typeof(Beehive), "m_maxHoney", 0.0, 1000.0, "the game drops each honey separately, all at once"),
			new Limit(typeof(SapCollector), "m_maxLevel", 0.0, 1000.0, "the game drops each sap separately, all at once")
		};

		private static readonly List<string> notes = new List<string>();

		public static object Clamp(FieldInfo field, object result)
		{
			if (result == null || !TweakValue.IsNumeric(field.FieldType))
			{
				return result;
			}
			double num = Convert.ToDouble(result, CultureInfo.InvariantCulture);
			double num2 = num;
			string text = null;
			foreach (Limit limit in limits)
			{
				if (!(limit.Field == null) && !(limit.Field.DeclaringType != field.DeclaringType) && !(limit.Field.Name != field.Name))
				{
					if (num2 > limit.Max)
					{
						num2 = limit.Max;
						text = limit.Reason;
					}
					else if (num2 < limit.Min)
					{
						num2 = limit.Min;
						text = limit.Reason;
					}
				}
			}
			if (text == null)
			{
				return result;
			}
			AddNote(num, num2, text);
			return Convert.ChangeType(num2, field.FieldType, CultureInfo.InvariantCulture);
		}

		public static double ClampMagnitude(double value, double original)
		{
			if (Math.Abs(value) <= 1000000000.0 || Math.Abs(value) <= Math.Abs(original))
			{
				return value;
			}
			double num = (double)Math.Sign(value) * Math.Max(1000000000.0, Math.Abs(original));
			AddNote(value, num, "larger numbers can overflow the game's math");
			return num;
		}

		private static void AddNote(double value, double clamped, string reason)
		{
			notes.Add(Describe(value) + " is outside what the game can handle, so it's " + Describe(clamped) + " - " + reason);
		}

		private static string Describe(double number)
		{
			string text = ((Math.Abs(number) < 1000000000000000.0) ? "0.####" : "0.###e+0");
			return number.ToString(text, CultureInfo.InvariantCulture);
		}

		public static List<string> TakeNotes()
		{
			List<string> list = new List<string>();
			foreach (string note in notes)
			{
				if (!list.Contains(note))
				{
					list.Add(note);
				}
			}
			notes.Clear();
			return list;
		}
	}
	public static class Originals
	{
		private static readonly Dictionary<object, Dictionary<FieldInfo, object>> values = new Dictionary<object, Dictionary<FieldInfo, object>>(ReferenceComparer.Instance);

		public static void Set(object owner, FieldInfo field, object value)
		{
			if (!values.TryGetValue(owner, out var value2))
			{
				value2 = new Dictionary<FieldInfo, object>();
				values.Add(owner, value2);
			}
			if (!value2.ContainsKey(field))
			{
				value2.Add(field, field.GetValue(owner));
			}
			field.SetValue(owner, value);
		}

		public static void Restore()
		{
			foreach (KeyValuePair<object, Dictionary<FieldInfo, object>> value in values)
			{
				foreach (KeyValuePair<FieldInfo, object> item in value.Value)
				{
					item.Key.SetValue(value.Key, item.Value);
				}
			}
			values.Clear();
		}
	}
	[BepInPlugin("kriona.TweakStats", "Tweak Stats", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "kriona.TweakStats";

		public const string PluginName = "Tweak Stats";

		public const string PluginVersion = "1.0.0";

		public static ManualLogSource Log;

		public static string ConfigPath;

		private Harmony harmony;

		private ConfigWatcher watcher;

		private void Awake()
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigPath = Path.Combine(Paths.ConfigPath, "kriona.TweakStats.cfg");
			if (!File.Exists(ConfigPath))
			{
				WriteDefaultConfig();
			}
			Tweaks.Load();
			watcher = new ConfigWatcher(ConfigPath, Tweaks.Reload, ((BaseUnityPlugin)this).Logger, null);
			harmony = new Harmony("kriona.TweakStats");
			harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Tweak Stats 1.0.0 loaded");
		}

		private void Update()
		{
			Tweaks.ShowPendingMessage();
			watcher.Update();
		}

		private void WriteDefaultConfig()
		{
			try
			{
				using Stream stream = typeof(Plugin).Assembly.GetManifestResourceStream("DefaultConfig.cfg");
				using StreamReader streamReader = new StreamReader(stream);
				File.WriteAllText(ConfigPath, streamReader.ReadToEnd());
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Couldn't write " + ConfigPath + ": " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			watcher?.Dispose();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}
	}
	public static class Prefabs
	{
		public static List<ItemDrop> Items()
		{
			List<ItemDrop> list = new List<ItemDrop>();
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return list;
			}
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				if (!((Object)(object)item == (Object)null))
				{
					ItemDrop component = item.GetComponent<ItemDrop>();
					if ((Object)(object)component != (Object)null && component.m_itemData.m_shared.m_icons != null && component.m_itemData.m_shared.m_icons.Length != 0)
					{
						list.Add(component);
					}
				}
			}
			return list;
		}

		public static List<Recipe> Recipes()
		{
			List<Recipe> list = new List<Recipe>();
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return list;
			}
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				if ((Object)(object)recipe != (Object)null && (Object)(object)recipe.m_item != (Object)null)
				{
					list.Add(recipe);
				}
			}
			return list;
		}

		public static List<GameObject> Creatures()
		{
			List<GameObject> list = new List<GameObject>();
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return list;
			}
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<Character>() != (Object)null && (Object)(object)prefab.GetComponent<Player>() == (Object)null)
				{
					list.Add(prefab);
				}
			}
			return list;
		}

		public static List<GameObject> Pieces()
		{
			List<GameObject> list = new List<GameObject>();
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return list;
			}
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent<Piece>() != (Object)null && (Object)(object)prefab.GetComponentInChildren<TombStone>(true) == (Object)null)
				{
					list.Add(prefab);
				}
			}
			return list;
		}

		public static List<StatusEffect> Effects()
		{
			List<StatusEffect> list = new List<StatusEffect>();
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return list;
			}
			foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects)
			{
				if ((Object)(object)statusEffect != (Object)null)
				{
					list.Add(statusEffect);
				}
			}
			return list;
		}

		public static HashSet<StatusEffect> HitEffects()
		{
			HashSet<StatusEffect> hashSet = new HashSet<StatusEffect>();
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return hashSet;
			}
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val != (Object)null && (Object)(object)val.m_itemData.m_shared.m_attackStatusEffect != (Object)null)
				{
					hashSet.Add(val.m_itemData.m_shared.m_attackStatusEffect);
				}
			}
			foreach (StatusEffect item2 in Effects())
			{
				if (item2 is SE_Burning || item2 is SE_Poison || item2 is SE_Frost || item2 is SE_Harpooned)
				{
					hashSet.Add(item2);
				}
			}
			return hashSet;
		}

		public static string NameOf(Object unityObject)
		{
			Component val = (Component)(object)((unityObject is Component) ? unityObject : null);
			if (val != null)
			{
				return ((Object)val.gameObject).name;
			}
			return unityObject.name;
		}

		public static string DisplayName(ItemDrop item)
		{
			return Localize(item.m_itemData.m_shared.m_name);
		}

		public static string Localize(string text)
		{
			if (Localization.instance == null)
			{
				return text;
			}
			return Localization.instance.Localize(text);
		}

		public static bool CanFind(Type type)
		{
			if (!(type == typeof(ItemDrop)) && !(type == typeof(GameObject)) && !typeof(StatusEffect).IsAssignableFrom(type))
			{
				return type == typeof(CraftingStation);
			}
			return true;
		}

		public static string Describe(Type type)
		{
			if (type == typeof(ItemDrop))
			{
				return "item";
			}
			if (typeof(StatusEffect).IsAssignableFrom(type))
			{
				return "status effect";
			}
			if (type == typeof(CraftingStation))
			{
				return "crafting station";
			}
			return "prefab";
		}

		public static Object Find(string name, Type type)
		{
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return null;
			}
			if (type == typeof(ItemDrop))
			{
				return (Object)(object)FindItem(name);
			}
			if (type == typeof(GameObject))
			{
				return (Object)(object)FindPrefab(name);
			}
			if (typeof(StatusEffect).IsAssignableFrom(type))
			{
				foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects)
				{
					if ((Object)(object)statusEffect != (Object)null && type.IsInstanceOfType(statusEffect) && ((Object)statusEffect).name.Equals(name, StringComparison.OrdinalIgnoreCase))
					{
						return (Object)(object)statusEffect;
					}
				}
				return null;
			}
			if (type == typeof(CraftingStation))
			{
				return (Object)(object)FindStation(name);
			}
			return null;
		}

		public static ItemDrop FindItem(string name)
		{
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name);
			if ((Object)(object)itemPrefab != (Object)null)
			{
				return itemPrefab.GetComponent<ItemDrop>();
			}
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				if ((Object)(object)item != (Object)null && ((Object)item).name.Equals(name, StringComparison.OrdinalIgnoreCase))
				{
					return item.GetComponent<ItemDrop>();
				}
			}
			return null;
		}

		private static GameObject FindPrefab(string name)
		{
			if ((Object)(object)ZNetScene.instance != (Object)null)
			{
				GameObject prefab = ZNetScene.instance.GetPrefab(name);
				if ((Object)(object)prefab != (Object)null)
				{
					return prefab;
				}
				foreach (GameObject prefab2 in ZNetScene.instance.m_prefabs)
				{
					if ((Object)(object)prefab2 != (Object)null && ((Object)prefab2).name.Equals(name, StringComparison.OrdinalIgnoreCase))
					{
						return prefab2;
					}
				}
			}
			ItemDrop val = FindItem(name);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return ((Component)val).gameObject;
		}

		private static CraftingStation FindStation(string name)
		{
			string value = name.TrimStart('$');
			foreach (CraftingStation item in Stations())
			{
				if (((Object)((Component)item).gameObject).name.Equals(name, StringComparison.OrdinalIgnoreCase) || item.m_name.TrimStart('$').Equals(value, StringComparison.OrdinalIgnoreCase))
				{
					return item;
				}
			}
			return null;
		}

		private static List<CraftingStation> Stations()
		{
			List<CraftingStation> list = new List<CraftingStation>();
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				if ((Object)(object)recipe != (Object)null)
				{
					AddStation(list, recipe.m_craftingStation);
					AddStation(list, recipe.m_repairStation);
				}
			}
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				PieceTable val = ((!((Object)(object)item != (Object)null)) ? null : item.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_buildPieces);
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				foreach (GameObject piece in val.m_pieces)
				{
					if ((Object)(object)piece != (Object)null)
					{
						AddStation(list, piece.GetComponent<CraftingStation>());
					}
				}
			}
			return list;
		}

		private static void AddStation(List<CraftingStation> stations, CraftingStation station)
		{
			if ((Object)(object)station != (Object)null && !stations.Contains(station))
			{
				stations.Add(station);
			}
		}
	}
	public class PrefabTarget : Target
	{
		private static readonly Type[] mainTypes = new Type[6]
		{
			typeof(Character),
			typeof(Piece),
			typeof(WearNTear),
			typeof(BaseAI),
			typeof(CharacterDrop),
			typeof(Tameable)
		};

		private static readonly ItemType[] attackTypes;

		private readonly List<Component> components;

		private readonly List<ItemDrop> attacks;

		public PrefabTarget(GameObject prefab, string header)
		{
			Root = prefab;
			Header = header;
			NeedsServer = true;
			components = Components(prefab);
			attacks = Attacks(prefab);
		}

		public override bool TryApply(string path, TweakValue value, out string error)
		{
			string text = FirstPart(path);
			string text2 = RestOfPath(path);
			if (attacks != null && text.Equals("attacks", StringComparison.OrdinalIgnoreCase))
			{
				return TryApplyToAttacks(text2, value, out error);
			}
			Component val = FindComponent(text);
			if ((Object)(object)val != (Object)null)
			{
				if (text2 == "")
				{
					error = text + " is a part of the prefab, so it needs the name of one of its stats, e.g. " + text + ".<stat>";
					return false;
				}
				return TryApplyToComponent(val, text2, value, out error);
			}
			foreach (Component component in components)
			{
				if (StatPath.FindField(((object)component).GetType(), text) != null)
				{
					return TryApplyToComponent(component, path, value, out error);
				}
			}
			error = "there's no stat \"" + text + "\"" + StatPath.Suggest(components.ConvertAll((Component component) => ((object)component).GetType()), text);
			return false;
		}

		public override void List(List<string> lines)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (Component component in components)
			{
				List<string> list = new List<string>();
				StatPath.List(component, list);
				HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				foreach (string item2 in list)
				{
					string item = FirstPart(item2.Substring(0, item2.IndexOf(" = ", StringComparison.Ordinal)));
					hashSet2.Add(item);
					lines.Add((hashSet.Contains(item) ? (((object)component).GetType().Name + ".") : "") + item2);
				}
				hashSet.UnionWith(hashSet2);
			}
			if (attacks == null)
			{
				return;
			}
			foreach (ItemDrop attack in attacks)
			{
				List<string> list2 = new List<string>();
				StatPath.List(attack.m_itemData.m_shared, list2);
				foreach (string item3 in list2)
				{
					lines.Add("attacks." + ((Object)((Component)attack).gameObject).name + "." + item3);
				}
			}
		}

		private static bool TryApplyToComponent(Component component, string path, TweakValue value, out string error)
		{
			if (!StatPath.TryApply(component, path, value, out error))
			{
				return false;
			}
			FieldInfo fieldInfo = StatPath.FindField(((object)component).GetType(), FirstPart(path));
			if (fieldInfo != null)
			{
				Instances.Touch(component, fieldInfo);
			}
			return true;
		}

		private bool TryApplyToAttacks(string path, TweakValue value, out string error)
		{
			error = null;
			List<ItemDrop> list = attacks;
			string value2 = FirstPart(path);
			foreach (ItemDrop attack in attacks)
			{
				if (((Object)((Component)attack).gameObject).name.Equals(value2, StringComparison.OrdinalIgnoreCase))
				{
					list = new List<ItemDrop> { attack };
					path = RestOfPath(path);
					break;
				}
			}
			if (path == "")
			{
				error = "attacks needs the name of a stat, e.g. attacks.damages, or an attack and a stat, e.g. attacks." + ((attacks.Count > 0) ? ((Object)((Component)attacks[0]).gameObject).name : "<attack>") + ".damages";
				return false;
			}
			foreach (ItemDrop item in list)
			{
				if (!StatPath.TryApply(item.m_itemData.m_shared, path, value, out error))
				{
					return false;
				}
			}
			return true;
		}

		private Component FindComponent(string name)
		{
			foreach (Component component in components)
			{
				Type type = ((object)component).GetType();
				while (type != null && type != typeof(MonoBehaviour))
				{
					if (type.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
					{
						return component;
					}
					type = type.BaseType;
				}
			}
			return null;
		}

		private static List<Component> Components(GameObject prefab)
		{
			List<Component> list = new List<Component>();
			MonoBehaviour[] array = prefab.GetComponents<MonoBehaviour>();
			foreach (MonoBehaviour val in array)
			{
				if (!((Object)(object)val == (Object)null) && !(val is ZNetView) && !((object)val).GetType().Name.StartsWith("ZSync", StringComparison.Ordinal))
				{
					list.Add((Component)(object)val);
				}
			}
			List<Component> list2 = new List<Component>();
			Type[] array2 = mainTypes;
			foreach (Type type in array2)
			{
				foreach (Component item in list)
				{
					if (type.IsInstanceOfType(item) && !list2.Contains(item))
					{
						list2.Add(item);
					}
				}
			}
			foreach (Component item2 in list)
			{
				if (!list2.Contains(item2))
				{
					list2.Add(item2);
				}
			}
			return list2;
		}

		private static List<ItemDrop> Attacks(GameObject prefab)
		{
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			Humanoid component = prefab.GetComponent<Humanoid>();
			if ((Object)(object)component == (Object)null)
			{
				return null;
			}
			List<GameObject> list = new List<GameObject>();
			AddRange(list, component.m_defaultItems);
			AddRange(list, component.m_randomWeapon);
			if (component.m_randomSets != null)
			{
				ItemSet[] randomSets = component.m_randomSets;
				foreach (ItemSet val in randomSets)
				{
					AddRange(list, val.m_items);
				}
			}
			if (component.m_randomItems != null)
			{
				RandomItem[] randomItems = component.m_randomItems;
				foreach (RandomItem val2 in randomItems)
				{
					list.Add(val2.m_prefab);
				}
			}
			List<ItemDrop> list2 = new List<ItemDrop>();
			foreach (GameObject item in list)
			{
				ItemDrop val3 = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val3 != (Object)null && !list2.Contains(val3) && Array.IndexOf(attackTypes, val3.m_itemData.m_shared.m_itemType) >= 0)
				{
					list2.Add(val3);
				}
			}
			return list2;
		}

		private static void AddRange(List<GameObject> list, GameObject[] prefabs)
		{
			if (prefabs != null)
			{
				list.AddRange(prefabs);
			}
		}

		private static string FirstPart(string path)
		{
			int num = path.IndexOf('.');
			return ((num >= 0) ? path.Substring(0, num) : path).Trim();
		}

		private static string RestOfPath(string path)
		{
			int num = path.IndexOf('.');
			if (num < 0)
			{
				return "";
			}
			return path.Substring(num + 1).Trim();
		}

		static PrefabTarget()
		{
			ItemType[] array = new ItemType[6];
			RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
			attackTypes = (ItemType[])(object)array;
		}
	}
	public class ReferenceComparer : IEqualityComparer<object>
	{
		public static readonly ReferenceComparer Instance = new ReferenceComparer();

		public new bool Equals(object a, object b)
		{
			return a == b;
		}

		public int GetHashCode(object obj)
		{
			return RuntimeHelpers.GetHashCode(obj);
		}
	}
	public static class ServerSync
	{
		private const string VersionRpc = "kriona.TweakStats.Version";

		private const string ConfigRpc = "kriona.TweakStats.Config";

		private const string RefusedRpc = "kriona.TweakStats.Refused";

		private static readonly Dictionary<ZRpc, string> clientVersions = new Dictionary<ZRpc, string>();

		public static void OnNewConnection(ZNetPeer peer)
		{
			if (ZNet.instance.IsServer())
			{
				peer.m_rpc.Register<string>("kriona.TweakStats.Version", (Action<ZRpc, string>)RPC_Version);
				return;
			}
			peer.m_rpc.Register<ZPackage>("kriona.TweakStats.Config", (Action<ZRpc, ZPackage>)RPC_Config);
			peer.m_rpc.Register<string>("kriona.TweakStats.Refused", (Action<ZRpc, string>)RPC_Refused);
			peer.m_rpc.Invoke("kriona.TweakStats.Version", new object[1] { "1.0.0" });
		}

		public static bool Allow(ZRpc rpc)
		{
			if (!Tweaks.RequireMod)
			{
				return true;
			}
			if (!clientVersions.TryGetValue(rpc, out var value))
			{
				Plugin.Log.LogInfo((object)"Refused a player without Tweak Stats, which this server requires");
				rpc.Invoke("Error", new object[1] { 3 });
				return false;
			}
			if (!SameMinorVersion(value, "1.0.0"))
			{
				Plugin.Log.LogInfo((object)("Refused a player with Tweak Stats " + value + ", since this server has 1.0.0"));
				rpc.Invoke("kriona.TweakStats.Refused", new object[1] { "This server requires Tweak Stats " + MinorVersion("1.0.0") + ", and you have " + value });
				rpc.Invoke("Error", new object[1] { 3 });
				return false;
			}
			return true;
		}

		public static void SendConfig(ZRpc rpc)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (clientVersions.ContainsKey(rpc))
			{
				ZPackage val = new ZPackage();
				val.Write(Tweaks.LocalText);
				rpc.Invoke("kriona.TweakStats.Config", new object[1] { val });
			}
		}

		public static void SendConfigToAll()
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer.m_rpc != null && peer.m_uid != 0L)
				{
					SendConfig(peer.m_rpc);
				}
			}
		}

		public static void Clear()
		{
			clientVersions.Clear();
		}

		private static void RPC_Version(ZRpc rpc, string version)
		{
			ForgetDisconnected();
			clientVersions[rpc] = version;
		}

		private static void ForgetDisconnected()
		{
			HashSet<ZRpc> hashSet = new HashSet<ZRpc>();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				hashSet.Add(peer.m_rpc);
			}
			List<ZRpc> list = new List<ZRpc>();
			foreach (ZRpc key in clientVersions.Keys)
			{
				if (!hashSet.Contains(key))
				{
					list.Add(key);
				}
			}
			foreach (ZRpc item in list)
			{
				clientVersions.Remove(item);
			}
		}

		private static void RPC_Config(ZRpc rpc, ZPackage package)
		{
			Tweaks.UseServerConfig(package.ReadString());
		}

		private static void RPC_Refused(ZRpc rpc, string reason)
		{
			Plugin.Log.LogWarning((object)reason);
		}

		private static bool SameMinorVersion(string a, string b)
		{
			return MinorVersion(a) == MinorVersion(b);
		}

		private static string MinorVersion(string version)
		{
			string[] array = version.Split('.');
			if (array.Length < 2)
			{
				return version;
			}
			return array[0] + "." + array[1];
		}
	}
	public static class StatPath
	{
		private class ListEntry
		{
			public Type ElementType;

			public string KeyField;

			public string DefaultField;

			public bool RemoveWhenZero;

			public Func<object, string> Key;

			public Func<string, (object entry, string error)> Create;
		}

		private const int MaxDepth = 5;

		private unsafe static readonly List<ListEntry> listEntries = new List<ListEntry>
		{
			new ListEntry
			{
				ElementType = typeof(Requirement),
				KeyField = "m_resItem",
				DefaultField = "m_amount",
				RemoveWhenZero = true,
				Key = delegate(object entry)
				{
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					ItemDrop resItem = ((Requirement)entry).m_resItem;
					return (resItem == null) ? null : ((Object)((Component)resItem).gameObject).name;
				},
				Create = CreateRequirement
			},
			new ListEntry
			{
				ElementType = typeof(DamageModPair),
				KeyField = "m_type",
				DefaultField = "m_modifier",
				RemoveWhenZero = false,
				Key = delegate(object entry)
				{
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					DamageModPair val = (DamageModPair)entry;
					return ((object)(*(DamageType*)(&val.m_type))/*cast due to .constrained prefix*/).ToString();
				},
				Create = CreateDamageModPair
			},
			new ListEntry
			{
				ElementType = typeof(Drop),
				KeyField = "m_prefab",
				DefaultField = "m_chance",
				RemoveWhenZero = true,
				Key = delegate(object entry)
				{
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					GameObject prefab = ((Drop)entry).m_prefab;
					return (prefab == null) ? null : ((Object)prefab).name;
				},
				Create = CreateDrop
			}
		};

		private static readonly Dictionary<Type, List<FieldInfo>> fieldCache = new Dictionary<Type, List<FieldInfo>>();

		public static bool TryApply(object root, string path, TweakValue value, out string error)
		{
			string[] array = path.Split('.');
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = array[i].Trim();
				if (array[i] == "")
				{
					error = "\"" + path + "\" has an empty part between its dots";
					return false;
				}
			}
			return TryApplyToField(root, array, 0, value, out error);
		}

		public static void List(object root, List<string> lines)
		{
			ListFields(root, "", lines, 0, null);
		}

		private static List<FieldInfo> GetFields(Type type)
		{
			if (fieldCache.TryGetValue(type, out var value))
			{
				return value;
			}
			value = new List<FieldInfo>();
			FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (!fieldInfo.IsNotSerialized && !fieldInfo.Name.Contains("<") && (fieldInfo.IsPublic || fieldInfo.IsDefined(typeof(SerializeField), inherit: true)))
				{
					value.Add(fieldInfo);
				}
			}
			fieldCache.Add(type, value);
			return value;
		}

		private static string PathName(FieldInfo field)
		{
			if (!field.Name.StartsWith("m_"))
			{
				return field.Name;
			}
			return field.Name.Substring(2);
		}

		public static FieldInfo FindField(Type type, string name)
		{
			foreach (FieldInfo field in GetFields(type))
			{
				if (PathName(field).Equals(name, StringComparison.OrdinalIgnoreCase) || field.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
				{
					return field;
				}
			}
			return null;
		}

		private static bool TryApplyToField(object owner, string[] parts, int index, TweakValue value, out string error)
		{
			error = null;
			FieldInfo fieldInfo = FindField(owner.GetType(), parts[index]);
			if (fieldInfo == null)
			{
				error = "there's no stat \"" + JoinPath(parts, index + 1) + "\"" + Suggest(owner.GetType(), parts[index]);
				return false;
			}
			Type fieldType = fieldInfo.FieldType;
			object value2 = fieldInfo.GetValue(owner);
			bool flag = index == parts.Length - 1;
			object result;
			if (IsList(fieldType))
			{
				if (flag)
				{
					error = JoinPath(parts, index + 1) + " is a list, so it needs the name of an entry, e.g. " + JoinPath(parts, index + 1) + "." + ExampleKey(fieldType);
					return false;
				}
				if (!TryApplyToList(value2, fieldType, parts, index + 1, value, out result, out error))
				{
					return false;
				}
			}
			else if (flag)
			{
				if (TweakValue.CanSet(fieldType))
				{
					if (!value.TryApply(value2, fieldType, out result, out error))
					{
						return false;
					}
					result = Limits.Clamp(fieldInfo, result);
				}
				else
				{
					if (!IsUniformGroup(fieldType))
					{
						error = JoinPath(parts, index + 1) + " is a group of stats, so it needs the name of one" + ExampleField(fieldType, JoinPath(parts, index + 1));
						return false;
					}
					result = value2;
					foreach (FieldInfo field in GetFields(fieldType))
					{
						if (!value.TryApply(field.GetValue(result), field.FieldType, out var result2, out error))
						{
							return false;
						}
						field.SetValue(result, Limits.Clamp(field, result2));
					}
				}
			}
			else
			{
				if (typeof(Object).IsAssignableFrom(fieldType))
				{
					error = JoinPath(parts, index + 1) + " points to another object, which can't be changed from here";
					return false;
				}
				if (!IsGroup(fieldType))
				{
					error = JoinPath(parts, index + 1) + " is a single stat, so it can't be followed by ." + parts[index + 1];
					return false;
				}
				if (value2 == null)
				{
					return true;
				}
				if (!TryApplyToField(value2, parts, index + 1, value, out error))
				{
					return false;
				}
				if (!fieldType.IsValueType)
				{
					return true;
				}
				result = value2;
			}
			Write(owner, fieldInfo, result);
			return true;
		}

		private static bool TryApplyToList(object current, Type listType, string[] parts, int index, TweakValue value, out object result, out string error)
		{
			result = current;
			error = null;
			Type type = ElementType(listType);
			ListEntry listEntry = FindListEntry(type);
			if (listEntry == null)
			{
				error = "TweakStats can't change the entries of " + JoinPath(parts, index);
				return false;
			}
			List<object> list = new List<object>();
			if (current != null)
			{
				foreach (object item in (IEnumerable)current)
				{
					list.Add(item);
				}
			}
			string text = parts[index];
			bool flag = index == parts.Length - 1;
			bool flag2 = flag && listEntry.RemoveWhenZero && value.Operation == TweakOperation.Set && value.IsNumber && value.Number == 0.0;
			List<int> list2 = new List<int>();
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i] != null && string.Equals(listEntry.Key(list[i]), text, StringComparison.OrdinalIgnoreCase))
				{
					list2.Add(i);
				}
			}
			if (list2.Count == 0)
			{
				if (flag2)
				{
					return true;
				}
				var (obj, text2) = listEntry.Create(text);
				if (obj == null)
				{
					error = text2;
					return false;
				}
				list.Add(obj);
				list2.Add(list.Count - 1);
			}
			if (flag2)
			{
				for (int num = list2.Count - 1; num >= 0; num--)
				{
					list.RemoveAt(list2[num]);
				}
			}
			else
			{
				foreach (int item2 in list2)
				{
					object obj2 = list[item2];
					if (flag)
					{
						FieldInfo field = type.GetField(listEntry.DefaultField);
						if (!value.TryApply(field.GetValue(obj2), field.FieldType, out var result2, out error))
						{
							return false;
						}
						Write(obj2, field, Limits.Clamp(field, result2));
					}
					else if (!TryApplyToField(obj2, parts, index + 1, value, out error))
					{
						return false;
					}
					list[item2] = obj2;
				}
			}
			result = MakeList(listType, type, list);
			return true;
		}

		private static void Write(object owner, FieldInfo field, object value)
		{
			if (owner.GetType().IsValueType)
			{
				field.SetValue(owner, value);
			}
			else
			{
				Originals.Set(owner, field, value);
			}
		}

		private static void ListFields(object owner, string prefix, List<string> lines, int depth, ICollection<string> skip)
		{
			foreach (FieldInfo field in GetFields(owner.GetType()))
			{
				if (skip != null && skip.Contains(field.Name))
				{
					continue;
				}
				string text = prefix + PathName(field);
				Type fieldType = field.FieldType;
				object value = field.GetValue(owner);
				if (TweakValue.CanSet(fieldType))
				{
					lines.Add(text + " = " + TweakValue.Format(value));
				}
				else if (IsList(fieldType))
				{
					ListEntry listEntry = FindListEntry(ElementType(fieldType));
					if (listEntry == null || value == null)
					{
						continue;
					}
					string[] skip2 = new string[2] { listEntry.KeyField, listEntry.DefaultField };
					foreach (object item in (IEnumerable)value)
					{
						string text2 = ((item != null) ? listEntry.Key(item) : null);
						if (!string.IsNullOrEmpty(text2))
						{
							lines.Add(text + "." + text2 + " = " + TweakValue.Format(listEntry.ElementType.GetField(listEntry.DefaultField).GetValue(item)));
							ListFields(item, text + "." + text2 + ".", lines, depth + 1, skip2);
						}
					}
				}
				else if (IsGroup(fieldType) && value != null && depth < 5)
				{
					ListFields(value, text + ".", lines, depth + 1, null);
				}
			}
		}

		private static bool IsList(Type type)
		{
			if (!type.IsArray)
			{
				if (type.IsGenericType)
				{
					return type.GetGenericTypeDefinition() == typeof(List<>);
				}
				return false;
			}
			return true;
		}

		private static Type ElementType(Type listType)
		{
			if (!listType.IsArray)
			{
				return listType.GetGenericArguments()[0];
			}
			return listType.GetElementType();
		}

		private static object MakeList(Type listType, Type elementType, List<object> entries)
		{
			if (listType.IsArray)
			{
				Array array = Array.CreateInstance(elementType, entries.Count);
				for (int i = 0; i < entries.Count; i++)
				{
					array.SetValue(entries[i], i);
				}
				return array;
			}
			IList list = (IList)Activator.CreateInstance(listType);
			foreach (object entry in entries)
			{
				list.Add(entry);
			}
			return list;
		}

		private static ListEntry FindListEntry(Type elementType)
		{
			foreach (ListEntry listEntry in listEntries)
			{
				if (listEntry.ElementType == elementType)
				{
					return listEntry;
				}
			}
			return null;
		}

		private static bool IsGroup(Type type)
		{
			if (type.IsPrimitive || type.IsEnum || type == typeof(string) || IsList(type) || typeof(Object).IsAssignableFrom(type) || typeof(Delegate).IsAssignableFrom(type))
			{
				return false;
			}
			return type.IsSerializable;
		}

		private static bool IsUniformGroup(Type type)
		{
			if (!type.IsValueType || !IsGroup(type))
			{
				return false;
			}
			List<FieldInfo> fields = GetFields(type);
			if (fields.Count < 2 || !TweakValue.CanSet(fields[0].FieldType))
			{
				return false;
			}
			foreach (FieldInfo item in fields)
			{
				if (item.FieldType != fields[0].FieldType)
				{
					return false;
				}
			}
			return true;
		}

		private static string JoinPath(string[] parts, int count)
		{
			return string.Join(".", parts, 0, count);
		}

		private static string Suggest(Type type, string name)
		{
			return Suggest(new Type[1] { type }, name);
		}

		public static string Suggest(IEnumerable<Type> types, string name)
		{
			List<string> list = new List<string>();
			foreach (Type type in types)
			{
				foreach (FieldInfo field in GetFields(type))
				{
					string text = PathName(field);
					if (((name.Length >= 3 && text.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0) || Distance(text.ToLowerInvariant(), name.ToLowerInvariant()) <= 2) && !list.Contains(text))
					{
						list.Add(text);
					}
				}
			}
			if (list.Count == 0 || list.Count > 5)
			{
				return "";
			}
			return " - did you mean " + string.Join(" or ", list) + "?";
		}

		private static int Distance(string a, string b)
		{
			int[] array = new int[b.Length + 1];
			int[] array2 = new int[b.Length + 1];
			for (int i = 0; i <= b.Length; i++)
			{
				array[i] = i;
			}
			for (int j = 1; j <= a.Length; j++)
			{
				array2[0] = j;
				for (int k = 1; k <= b.Length; k++)
				{
					int num = ((a[j - 1] != b[k - 1]) ? 1 : 0);
					array2[k] = Math.Min(Math.Min(array2[k - 1] + 1, array[k] + 1), array[k - 1] + num);
				}
				int[] array3 = array;
				array = array2;
				array2 = array3;
			}
			return array[b.Length];
		}

		private static string ExampleKey(Type listType)
		{
			if (ElementType(listType) == typeof(DamageModPair))
			{
				return "Fire";
			}
			return "Wood";
		}

		private static string ExampleField(Type type, string path)
		{
			foreach (FieldInfo field in GetFields(type))
			{
				if (TweakValue.CanSet(field.FieldType))
				{
					return ", e.g. " + path + "." + PathName(field);
				}
			}
			return "";
		}

		private static (object entry, string error) CreateRequirement(string name)
		{
			//IL_0037: 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_0043: 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_005e: Expected O, but got Unknown
			ItemDrop val = (((Object)(object)ObjectDB.instance != (Object)null) ? Prefabs.FindItem(name) : null);
			if ((Object)(object)val == (Object)null)
			{
				return (entry: null, error: "there's no item named \"" + name + "\"");
			}
			return (entry: (object)new Requirement
			{
				m_resItem = val,
				m_amount = 0,
				m_amountPerLevel = 0,
				m_recover = true
			}, error: null);
		}

		private static (object entry, string error) CreateDrop(string name)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//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_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: 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_0061: Expected O, but got Unknown
			GameObject val = (GameObject)Prefabs.Find(name, typeof(GameObject));
			if ((Object)(object)val == (Object)null)
			{
				return (entry: null, error: "there's no item or prefab named \"" + name + "\"");
			}
			return (entry: (object)new Drop
			{
				m_prefab = val,
				m_amountMin = 1,
				m_amountMax = 1,
				m_chance = 1f
			}, error: null);
		}

		private static (object entry, string error) CreateDamageModPair(string name)
		{
			//IL_0024: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: 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)
			string[] names = Enum.GetNames(typeof(DamageType));
			foreach (string text in names)
			{
				if (text.Equals(name, StringComparison.OrdinalIgnoreCase))
				{
					return (entry: (object)new DamageModPair
					{
						m_type = (DamageType)Enum.Parse(typeof(DamageType), text),
						m_modifier = (DamageModifier)0
					}, error: null);
				}
			}
			return (entry: null, error: "\"" + name + "\" isn't a damage type - use one of " + string.Join(", ", Enum.GetNames(typeof(DamageType))));
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	public static class ZNetNewConnectionPatch
	{
		private static void Postfix(ZNetPeer peer)
		{
			ServerSync.OnNewConnection(peer);
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	public static class ZNetPeerInfoSyncPatch
	{
		private static bool Prefix(ZNet __instance, ZRpc rpc)
		{
			if (__instance.IsServer())
			{
				return ServerSync.Allow(rpc);
			}
			return true;
		}

		private static void Postfix(ZNet __instance, ZRpc rpc)
		{
			if (__instance.IsServer())
			{
				ZNetPeer peer = __instance.GetPeer(rpc);
				if (peer != null && peer.m_uid != 0L)
				{
					ServerSync.SendConfig(rpc);
				}
			}
		}
	}
	public class Target
	{
		public object Root;

		public string Header;

		public bool NeedsServer;

		public virtual bool TryApply(string path, TweakValue value, out string error)
		{
			return StatPath.TryApply(Root, path, value, out error);
		}

		public virtual void List(List<string> lines)
		{
			StatPath.List(Root, lines);
		}
	}
	public static class Targets
	{
		public static List<Target> Resolve(List<string> selectors, List<string> problems, List<string> skipped)
		{
			List<Target> list = new List<Target>();
			HashSet<object> hashSet = new HashSet<object>(ReferenceComparer.Instance);
			foreach (string selector in selectors)
			{
				if (skipped != null && !Tweaks.WorldTweaksApply && IsWorldKind(selector))
				{
					skipped.Add(selector);
					continue;
				}
				string error;
				foreach (Target item in Resolve(selector, out error))
				{
					if (hashSet.Add(item.Root))
					{
						list.Add(item);
					}
				}
				if (error != null)
				{
					problems.Add(error);
				}
			}
			return list;
		}

		private static List<Target> Resolve(string selector, out string error)
		{
			//IL_0157: 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_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			List<Target> list = new List<Target>();
			error = null;
			int num = selector.IndexOf(':');
			string text = ((num >= 0) ? selector.Substring(0, num).Trim() : "");
			string text2 = ((num >= 0) ? selector.Substring(num + 1).Trim() : selector.Trim());
			if (text == "")
			{
				Regex regex = Pattern(text2);
				foreach (ItemDrop item in Prefabs.Items())
				{
					if (regex.IsMatch(((Object)((Component)item).gameObject).name))
					{
						list.Add(ItemTarget(item));
					}
				}
				if (list.Count == 0)
				{
					error = (IsWildcard(text2) ? ("no items match \"" + text2 + "\"") : ("there's no item named \"" + text2 + "\" - the tweakstats find console command looks up an item's name"));
				}
			}
			else if (text.Equals("Skill", StringComparison.OrdinalIgnoreCase))
			{
				if (!TryParseEnum<SkillType>(text2, out SkillType value))
				{
					error = "\"" + text2 + "\" isn't a skill - use one of " + string.Join(", ", Enum.GetNames(typeof(SkillType)));
					return list;
				}
				foreach (ItemDrop item2 in Prefabs.Items())
				{
					if (item2.m_itemData.m_shared.m_skillType == value)
					{
						list.Add(ItemTarget(item2));
					}
				}
			}
			else if (text.Equals("Type", StringComparison.OrdinalIgnoreCase))
			{
				if (!TryParseEnum<ItemType>(text2, out ItemType value2))
				{
					error = "\"" + text2 + "\" isn't an item type - use one of " + string.Join(", ", Enum.GetNames(typeof(ItemType)));
					return list;
				}
				foreach (ItemDrop item3 in Prefabs.Items())
				{
					if (item3.m_itemData.m_shared.m_itemType == value2)
					{
						list.Add(ItemTarget(item3));
					}
				}
			}
			else if (text.Equals("Recipe", StringComparison.OrdinalIgnoreCase))
			{
				Regex regex2 = Pattern(text2);
				foreach (Recipe item4 in Prefabs.Recipes())
				{
					if (regex2.IsMatch(((Object)((Component)item4.m_item).gameObject).name) || regex2.IsMatch(((Object)item4).name))
					{
						list.Add(new Target
						{
							Root = item4,
							Header = "Recipe:" + ((Object)item4).name
						});
					}
				}
				if (list.Count == 0)
				{
					error = (IsWildcard(text2) ? ("no recipes match \"" + text2 + "\"") : ("there's no recipe for \"" + text2 + "\""));
				}
			}
			else if (text.Equals("Creature", StringComparison.OrdinalIgnoreCase))
			{
				Regex regex3 = Pattern(text2);
				foreach (GameObject item5 in Prefabs.Creatures())
				{
					if (regex3.IsMatch(((Object)item5).name))
					{
						list.Add(new PrefabTarget(item5, "Creature:" + ((Object)item5).name));
					}
				}
				if (list.Count == 0)
				{
					error = (IsWildcard(text2) ? ("no creatures match \"" + text2 + "\"") : ("there's no creature named \"" + text2 + "\""));
				}
			}
			else if (text.Equals("Piece", StringComparison.OrdinalIgnoreCase))
			{
				Regex regex4 = Pattern(text2);
				foreach (GameObject item6 in Prefabs.Pieces())
				{
					if (regex4.IsMatch(((Object)item6).name))
					{
						list.Add(new PrefabTarget(item6, "Piece:" + ((Object)item6).name));
					}
				}
				if (list.Count == 0)
				{
					error = (IsWildcard(text2) ? ("no build pieces match \"" + text2 + "\"") : ("there's no build piece named \"" + text2 + "\""));
				}
			}
			else if (text.Equals("Effect", StringComparison.OrdinalIgnoreCase))
			{
				Regex regex5 = Pattern(text2);
				HashSet<StatusEffect> hashSet = Prefabs.HitEffects();
				foreach (StatusEffect item7 in Prefabs.Effects())
				{
					if (regex5.IsMatch(((Object)item7).name))
					{
						list.Add(new Target
						{
							Root = item7,
							Header = "Effect:" + ((Object)item7).name,
							NeedsServer = hashSet.Contains(item7)
						});
					}
				}
				if (list.Count == 0)
				{
					error = (IsWildcard(text2) ? ("no status effects match \"" + text2 + "\"") : ("there's no status effect named \"" + text2 + "\""));
				}
			}
			else
			{
				error = "\"" + text + ":\" isn't something TweakStats can change - use an item name, Skill:, Type:, Recipe:, Creature:, Piece: or Effect:";
			}
			return list;
		}

		private static bool IsWorldKind(string selector)
		{
			string text = selector.TrimStart();
			if (!text.StartsWith("Creature:", StringComparison.OrdinalIgnoreCase))
			{
				return text.StartsWith("Piece:", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static Target ItemTarget(ItemDrop item)
		{
			return new Target
			{
				Root = item.m_itemData.m_shared,
				Header = ((Object)((Component)item).gameObject).name
			};
		}

		private static bool IsWildcard(string name)
		{
			if (name.IndexOf('*') < 0)
			{
				return name.IndexOf('?') >= 0;
			}
			return true;
		}

		private static Regex Pattern(string name)
		{
			return new Regex("^" + Regex.Escape(name).Replace("\\*", ".*").Replace("\\?", ".") + "$", RegexOptions.IgnoreCase);
		}

		private static bool TryParseEnum<T>(string name, out T value) where T : struct
		{
			value = default(T);
			string[] names = Enum.GetNames(typeof(T));
			foreach (string text in names)
			{
				if (text.Equals(name, StringComparison.OrdinalIgnoreCase))
				{
					value = (T)Enum.Parse(typeof(T), text);
					return true;
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	public static class TerminalInitPatch
	{
		private static void Postfix()
		{
			Commands.Register();
		}
	}
	public class TweakLine
	{
		public int LineNumber;

		public string Path;

		public TweakValue Value;
	}
	public class TweakSection
	{
		public int LineNumber;

		public string Header;

		public List<string> Selectors = new List<string>();

		public List<string> Worlds;

		public List<TweakLine> Lines = new List<TweakLine>();
	}
	public class TweakFile
	{
		public List<TweakSection> Sections = new List<TweakSection>();

		public Dictionary<string, string> Settings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, string[]> settingValues = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase) { 
		{
			"requireMod",
			new string[3] { "auto", "true", "false" }
		} };

		public List<string> Problems = new List<string>();

		public static TweakFile Parse(string text)
		{
			TweakFile tweakFile = new TweakFile();
			TweakSection tweakSection = null;
			List<string> list = null;
			int lineNumber = 0;
			bool flag = false;
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				int num = i + 1;
				string text2 = StripComment(array[i]).Trim();
				if (text2 == "")
				{
					continue;
				}
				if (text2.StartsWith("["))
				{
					flag = false;
					if (!text2.EndsWith("]"))
					{
						tweakFile.AddProblem(num, "\"" + text2 + "\" starts with [ but doesn't end with ]");
						tweakSection = null;
						continue;
					}
					string text3 = text2.Substring(1, text2.Length - 2).Trim();
					List<string> names;
					if (IsWorldsEnd(text3))
					{
						if (list == null)
						{
							tweakFile.AddProblem(num, "[" + text3 + "] doesn't have a [Worlds: ...] before it");
						}
						list = null;
						tweakSection = null;
					}
					else if (TryReadWorlds(text3, out names))
					{
						if (list != null)
						{
							tweakFile.AddProblem(num, "[Worlds: ...] groups can't be inside each other - add [/Worlds] to end the group on line " + lineNumber + " first");
						}
						if (names.Count == 0)
						{
							tweakFile.AddProblem(num, "[" + text3 + "] doesn't name any worlds");
						}
						list = names;
						lineNumber = num;
						tweakSection = null;
					}
					else if (text3.Equals("Settings", StringComparison.OrdinalIgnoreCase))
					{
						if (list != null)
						{
							tweakFile.AddProblem(num, "[Settings] can't be inside a [Worlds: ...] group, so its settings apply everywhere");
						}
						flag = true;
						tweakSection = null;
					}
					else
					{
						tweakSection = new TweakSection
						{
							LineNumber = num,
							Header = text3,
							Selectors = SplitList(text3),
							Worlds = list
						};
						if (tweakSection.Selectors.Count == 0)
						{
							tweakFile.AddProblem(num, "[] needs the name of what to change, e.g. [SwordIron]");
							tweakSection = null;
						}
						else
						{
							tweakFile.Sections.Add(tweakSection);
						}
					}
					continue;
				}
				int num2 = text2.IndexOf('=');
				if (num2 < 0)
				{
					tweakFile.AddProblem(num, "\"" + text2 + "\" needs to be stat = value");
					continue;
				}
				if (flag)
				{
					tweakFile.ReadSetting(num, text2.Substring(0, num2).Trim(), text2.Substring(num2 + 1).Trim());
					continue;
				}
				if (tweakSection == null)
				{
					tweakFile.AddProblem(num, "\"" + text2 + "\" needs a [section] above it saying what it changes, e.g. [SwordIron]");
					continue;
				}
				string text4 = text2.Substring(0, num2).Trim();
				if (text4 == "")
				{
					tweakFile.AddProblem(num, "\"" + text2 + "\" needs the name of a stat before the =");
					continue;
				}
				if (!TweakValue.TryParse(text2.Substring(num2 + 1), out var value, out var error))
				{
					tweakFile.AddProblem(num, text4 + ": " + error);
					continue;
				}
				tweakSection.Lines.Add(new TweakLine
				{
					LineNumber = num,
					Path = text4,
					Value = value
				});
			}
			if (list != null)
			{
				tweakFile.AddProblem(lineNumber, "[Worlds: ...] has no [/Worlds] to end it, so it runs to the end of the file");
			}
			return tweakFile;
		}

		private void ReadSetting(int lineNumber, string name, string value)
		{
			if (!settingValues.TryGetValue(name, out var value2))
			{
				AddProblem(lineNumber, "there's no setting \"" + name + "\" - use one of " + string.Join(", ", settingValues.Keys));
				return;
			}
			string[] array = value2;
			foreach (string text in array)
			{
				if (text.Equals(value, StringComparison.OrdinalIgnoreCase))
				{
					Settings[name] = text;
					return;
				}
			}
			AddProblem(lineNumber, name + " needs one of " + string.Join(", ", value2) + ", not \"" + value + "\"");
		}

		private void AddProblem(int lineNumber, string message)
		{
			Problems.Add("line " + lineNumber + ": " + message);
		}

		private static string StripComment(string line)
		{
			string text = line.TrimStart();
			if (text.StartsWith("#") || text.StartsWith(";"))
			{
				return "";
			}
			for (int i = 1; i < line.Length; i++)
			{
				if (line[i] == '#' && char.IsWhiteSpace(line[i - 1]))
				{
					return line.Substring(0, i);
				}
			}
			return line;
		}

		private static bool IsWorldsEnd(string header)
		{
			if (!header.Equals("/Worlds", StringComparison.OrdinalIgnoreCase))
			{
				return header.Equals("/World", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static bool TryReadWorlds(string header, out List<string> names)
		{
			names = null;
			int num = header.IndexOf(':');
			if (num < 0)
			{
				return false;
			}
			string text = header.Substring(0, num).Trim();
			if (!text.Equals("Worlds", StringComparison.OrdinalIgnoreCase) && !text.Equals("World", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			names = SplitList(header.Substring(num + 1));
			return true;
		}

		private static List<string> SplitList(string text)
		{
			List<string> list = new List<string>();
			string[] array = text.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2 != "")
				{
					list.Add(text2);
				}
			}
			return list;
		}
	}
	public static class Tweaks
	{
		private const int NamesShown = 3;

		public static World CurrentWorld;

		public static string LocalText = "";

		private static TweakFile localFile = new TweakFile();

		private static TweakFile serverFile;

		private static string pendingMessage;

		private static readonly List<string> skippedNotes = new List<string>();

		private static bool skippedNoticed;

		private static readonly HashSet<string> reported = new HashSet<string>();

		public static bool RunsWorld
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					return ZNet.instance.IsServer();
				}
				return false;
			}
		}

		public static bool UsingServerConfig => serverFile != null;

		public static bool WorldTweaksApply
		{
			get
			{
				if (!RunsWorld)
				{
					return UsingServerConfig;
				}
				return true;
			}
		}

		public static bool RequireMod
		{
			get
			{
				if (!localFile.Settings.TryGetValue("requireMod", out var value) || value == "auto")
				{
					return HasWorldSections(localFile);
				}
				return value == "true";
			}
		}

		public static void Load()
		{
			reported.Clear();
			string text;
			try
			{
				text = (File.Exists(Plugin.ConfigPath) ? File.ReadAllText(Plugin.ConfigPath) : "");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Couldn't read " + Plugin.ConfigPath + ": " + ex.Message));
				return;
			}
			LocalText = text;
			localFile = TweakFile.Parse(text);
			foreach (string problem in localFile.Problems)
			{
				Report(Path.GetFileName(Plugin.ConfigPath) + " " + problem);
			}
		}

		public static void Reload()
		{
			Load();
			if (RunsWorld)
			{
				ServerSync.SendConfigToAll();
			}
			Apply();
			Plugin.Log.LogInfo((object)("Reloaded " + Path.GetFileName(Plugin.ConfigPath)));
			string text = "Tweak Stats: config reloaded";
			if (UsingServerConfig)
			{
				text += ", but this server's config is used while you're on it";
			}
			else if (reported.Count > 0)
			{
				text = text + " with " + reported.Count + ((reported.Count == 1) ? " problem" : " problems") + " - see the BepInEx log";
			}
			MessageHud instance = MessageHud.instance;
			if (instance != null)
			{
				instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
			if (skippedNoticed)
			{
				NoticeSkipped();
			}
		}

		public static void UseServerConfig(string text)
		{
			serverFile = TweakFile.Parse(text);
			foreach (string problem in serverFile.Problems)
			{
				Report("the server's config " + problem);
			}
			Plugin.Log.LogInfo((object)"Using the server's config");
			if (localFile.Sections.Count > 0)
			{
				pendingMessage = "Tweak Stats: this server has its own config, so your " + Path.GetFileName(Plugin.ConfigPath) + " is ignored while you're on it";
				Plugin.Log.LogInfo((object)pendingMessage);
			}
			Apply();
		}

		public static void ClearServerConfig()
		{
			serverFile = null;
			pendingMessage = null;
			skippedNoticed = false;
		}

		public static void ShowPendingMessage()
		{
			if (!((Object)(object)MessageHud.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null))
			{
				if (pendingMessage != null)
				{
					MessageHud.instance.ShowMessage((MessageType)2, pendingMessage, 0, (Sprite)null, false, true);
					pendingMessage = null;
				}
				if (!skippedNoticed)
				{
					skippedNoticed = true;
					NoticeSkipped();
				}
			}
		}

		private static void NoticeSkipped()
		{
			if (skippedNotes.Count == 0)
			{
				return;
			}
			foreach (string skippedNote in skippedNotes)
			{
				Report(skippedNote);
			}
			string text = "Tweak Stats: this server doesn't have Tweak Stats, so " + skippedNotes.Count + ((skippedNotes.Count == 1) ? " section is" : " sections are") + " skipped - see the BepInEx log";
			MessageHud instance = MessageHud.instance;
			if (instance != null)
			{
				instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
		}

		public static void Apply()
		{
			Instances.StartApply();
			Originals.Restore();
			skippedNotes.Clear();
			if ((Object)(object)ObjectDB.instance != (Object)null)
			{
				foreach (TweakSection section in (serverFile ?? localFile).Sections)
				{
					if (section.Worlds == null || InWorld(section.Worlds))
					{
						ApplySection(section);
					}
				}
			}
			Instances.Sync();
			Instances.RefreshEquipmentEffects();
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				((Humanoid)localPlayer).GetInventory().Changed(false, false);
			}
		}

		public static void WithoutTweaks(Action action)
		{
			Originals.Restore();
			try
			{
				action();
			}
			finally
			{
				Apply();
			}
		}

		private static void ApplySection(TweakSection section)
		{
			string text = "line " + section.LineNumber + " [" + section.Header + "]: ";
			string text2 = Source() + " ";
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			List<Target> list3 = Targets.Resolve(section.Selectors, list, list2);
			if (!WorldTweaksApply)
			{
				for (int num = list3.Count - 1; num >= 0; num--)
				{
					if (list3[num].NeedsServer)
					{
						list2.Add(list3[num].Header);
						list3.RemoveAt(num);
					}
				}
			}
			foreach (string item in list)
			{
				Report(text2 + text + item);
			}
			if (list2.Count > 0)
			{
				skippedNotes.Add(text2 + text + "skipped " + DescribeNames(list2) + " - creature, build piece and hit effect tweaks only apply in single player, when hosting, or on a server that has Tweak Stats");
			}
			foreach (TweakLine line in section.Lines)
			{
				Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
				foreach (Target item2 in list3)
				{
					string error;
					bool num2 = item2.TryApply(line.Path, line.Value, out error);
					List<string> list4 = Limits.TakeNotes();
					if (!num2 && error != null)
					{
						list4.Add(error);
					}
					foreach (string item3 in list4)
					{
						if (!dictionary.TryGetValue(item3, out var value))
						{
							value = new List<string>();
							dictionary.Add(item3, value);
						}
						value.Add(item2.Header);
					}
				}
				foreach (KeyValuePair<string, List<string>> item4 in dictionary)
				{
					string text3 = text2 + "line " + line.LineNumber + " [" + section.Header + "]";
					if (item4.Value.Count < list3.Count)
					{
						text3 = text3 + " " + DescribeNames(item4.Value);
					}
					Report(text3 + ": " + line.Path + ": " + item4.Key);
				}
			}
		}

		private static bool InWorld(List<string> worlds)
		{
			if (CurrentWorld == null)
			{
				return false;
			}
			foreach (string world in worlds)
			{
				if (world.Equals("SinglePlayer", StringComparison.OrdinalIgnoreCase) && ZNet.IsSinglePlayer)
				{
					return true;
				}
				if (world.Equals(CurrentWorld.m_name, StringComparison.OrdinalIgnoreCase) || world == CurrentWorld.m_uid.ToString())
				{
					return true;
				}
				if (IsCurrentServer(world))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsCurrentServer(string entry)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return false;
			}
			string serverHost = ZNet.m_serverHost;
			if (!string.IsNullOrEmpty(serverHost) && (entry.Equals(serverHost, StringComparison.OrdinalIgnoreCase) || entry.Equals(serverHost + ":" + ZNet.m_serverHostPort, StringComparison.OrdinalIgnoreCase)))
			{
				return true;
			}
			if (ZNet.m_serverSteamID != 0L && entry == ZNet.m_serverSteamID.ToString())
			{
				return true;
			}
			if (!string.IsNullOrEmpty(ZNet.m_serverPlayFabPlayerId))
			{
				return entry.Equals(ZNet.m_serverPlayFabPlayerId, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static string DescribeNames(List<string> names)
		{
			if (names.Count <= 3)
			{
				return "(" + string.Join(", ", names) + ")";
			}
			return "(" + string.Join(", ", names.GetRange(0, 3)) + " and " + (names.Count - 3) + " more)";
		}

		private static void Report(string problem)
		{
			if (reported.Add(problem))
			{
				Plugin.Log.LogWarning((object)problem);
			}
		}

		private static string Source()
		{
			if (!UsingServerConfig)
			{
				return Path.GetFileName(Plugin.ConfigPath);
			}
			return "the server's config";
		}

		private static bool HasWorldSections(TweakFile file)
		{
			foreach (TweakSection section in file.Sections)
			{
				foreach (string selector in section.Selectors)
				{
					string text = selector.TrimStart();
					if (text.StartsWith("Creature:", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Piece:", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Effect:", StringComparison.OrdinalIgnoreCase))
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	public enum TweakOperation
	{
		Set,
		Add,
		Multiply
	}
	public class TweakValue
	{
		public TweakOperation Operation;

		public double Number;

		public bool IsNumber;

		public string Text;

		public static bool TryParse(string text, out TweakValue value, out string error)
		{
			value = new TweakValue();
			error = null;
			text = text.Trim();
			if (text.StartsWith("="))
			{
				text = text.Substring(1).Trim();
				value.Operation = TweakOperation.Set;
				value.IsNumber = TryParseNumber(text, out value.Number);
			}
			else if (text.Length > 1 && (text.EndsWith("x") || text.EndsWith("X")) && TryParseNumber(text.Substring(0, text.Length - 1), out value.Number))
			{
				value.Operation = TweakOperation.Multiply;
				value.IsNumber = true;
			}
			else if ((text.StartsWith("+") || text.StartsWith("-")) && TryParseNumber(text, out value.Number))
			{
				value.Operation = TweakOperation.Add;
				value.IsNumber = true;
			}
			else
			{
				value.Operation = TweakOperation.Set;
				value.IsNumber = TryParseNumber(text, out value.Number);
			}
			value.Text = text;
			if (text == "")
			{
				error = "there's no value after the =";
				return false;
			}
			return true;
		}

		public bool TryApply(object current, Type type, out object result, out string error)
		{
			result = current;
			error = null;
			if (IsNumeric(type))
			{
				if (!IsNumber)
				{
					error = "it needs a number, not \"" + Text + "\"";
					return false;
				}
				double num = Convert.ToDouble(current, CultureInfo.InvariantCulture);
				double num2 = num;
				num2 = ((Operation == TweakOperation.Add) ? (num2 + Number) : ((Operation != TweakOperation.Multiply) ? Number : (num2 * Number)));
				if (double.IsNaN(num2))
				{
					error = "the result isn't a number the game can use";
					return false;
				}
				return TryConvertNumber(Limits.ClampMagnitude(num2, num), type, out result, out error);
			}
			if (Operation != TweakOperation.Set)
			{
				error = "only numbers can be added to or multiplied, so it needs a value like " + ExampleValue(type);
				return false;
			}
			if (type == typeof(bool))
			{
				if (Text.Equals("true", StringComparison.OrdinalIgnoreCase))
				{
					result = true;
					return true;
				}
				if (Text.Equals("false", StringComparison.OrdinalIgnoreCase))
				{
					result = false;
					return true;
				}
				error = "it needs true or false, not \"" + Text + "\"";
				return false;
			}
			if (type.IsEnum)
			{
				return TryParseEnum(Text, type, out result, out error);
			}
			if (type == typeof(string))
			{
				result = Text;
				return true;
			}
			if (typeof(Object).IsAssignableFrom(type))
			{
				if (Text.Equals("none", StringComparison.OrdinalIgnoreCase))
				{
					result = null;
					return true;
				}
				Object val = Prefabs.Find(Text, type);
				if (val == (Object)null)
				{
					error = "there's no " + Prefabs.Describe(type) + " named \"" + Text + "\"";
					return false;
				}
				result = val;
				return true;
			}
			error = "TweakStats can't change a " + type.Name;
			return false;
		}

		public static bool CanSet(Type type)
		{
			if (!IsNumeric(type) && !(type == typeof(bool)) && !type.IsEnum && !(type == typeof(string)))
			{
				return Prefabs.CanFind(type);
			}
			return true;
		}

		public static string Format(object value)
		{
			if (value == null)
			{
				return "none";
			}
			Object val = (Object)((value is Object) ? value : null);
			if (val != null)
			{
				if (!(val == (Object)null))
				{
					return Prefabs.NameOf(val);
				}
				return "none";
			}
			if (value is bool)
			{
				if (!(bool)value)
				{
					return "false";
				}
				return "true";
			}
			if (value is float || value is double)
			{
				return Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString("0.####", CultureInfo.InvariantCulture);
			}
			if (value is IFormattable formattable)
			{
				return formattable.ToString(null, CultureInfo.InvariantCulture);
			}
			return value.ToString();
		}

		public static bool IsNumeric(Type type)
		{
			if (!(type == typeof(float)) && !(type == typeof(double)) && !(type == typeof(int)) && !(type == typeof(long)) && !(type == typeof(short)) && !(type == typeof(byte)) && !(type == typeof(uint)))
			{
				return type == typeof(ushort);
			}
			return true;
		}

		private static bool TryParseNumber(string text, out double number)
		{
			if (double.TryParse(text.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out number) && !double.IsNaN(number))
			{
				return !double.IsInfinity(number);
			}
			return false;
		}

		private static bool TryConvertNumber(double number, Type type, out object result, out string error)
		{
			result = null;
			error = null;
			if (double.IsInfinity(number) || (type == typeof(float) && float.IsInfinity((float)number)))
			{
				error = "the result is too large for the game to use";
				return false;
			}
			if (type == typeof(float))
			{
				result = (float)number;
				return true;
			}
			if (type == typeof(double))
			{
				result = number;
				return true;
			}
			double num = Math.Round(number, MidpointRounding.AwayFromZero);
			try
			{
				result = Convert.ChangeType(num, type, CultureInfo.InvariantCulture);
				return true;
			}
			catch (OverflowException)
			{
				error = num.ToString(CultureInfo.InvariantCulture) + " is too large or small for it";
				return false;
			}
		}

		private static bool TryParseEnum(string text, Type type, out object result, out string error)
		{
			result = null;
			error = null;
			string[] names = Enum.GetNames(type);
			foreach (string text2 in names)
			{
				if (text2.Equals(text, StringComparison.OrdinalIgnoreCase))
				{
					result = Enum.Parse(type, text2);
					return true;
				}
			}
			error = "\"" + text + "\" isn't one of " + string.Join(", ", Enum.GetNames(type));
			return false;
		}

		private static string ExampleValue(Type type)
		{
			if (type == typeof(bool))
			{
				return "true";
			}
			if (type.IsEnum)
			{
				return Enum.GetNames(type)[0];
			}
			return "a name";
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace Kriona.Shared
{
	internal class ConfigMirror
	{
		private readonly ConfigFile config;

		private readonly Action changed;

		private bool syncing;

		public ConfigMirror(ConfigFile config, Action changed)
		{
			this.config = config;
			this.changed = changed;
			config.SaveOnConfigSet = false;
			config.SettingChanged += OnSettingChanged;
		}

		public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description, AcceptableValueBase acceptableValues, int? order, string displayName)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes();
			configurationManagerAttributes.Order = order;
			configurationManagerAttributes.DispName = displayName;
			return config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, acceptableValues, new object[1] { configurationManagerAttributes }));
		}

		public void Remove(ConfigEntryBase entry)
		{
			config.Remove(entry.Definition);
		}

		public void