Decompiled source of DemigodModTweaks v2.26.0

DemigodModTweaks.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jewelcrafting;
using UnityEngine;
using UnityEngine.Audio;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace DemigodModTweaks;

internal static class AltarFinder
{
	private class Boss
	{
		public string Label;

		public string[] Keys;

		public string[] Locations;

		public string KillKey;

		public string KillerName;

		public Boss(string label, string[] keys, params string[] locs)
		{
			Label = label;
			Keys = keys;
			Locations = locs;
		}

		public Boss After(string killKey, string killer)
		{
			KillKey = killKey;
			KillerName = killer;
			return this;
		}
	}

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

		public static ConsoleEvent <>9__10_0;

		internal void <AfterInitTerminal>b__10_0(ConsoleEventArgs args)
		{
			Plugin.RelearnPieces();
			Plugin.DiagnosePieces(MaterialPiles.AllPieces(), "Material Piles");
			Plugin.DiagnosePieces(HomeForge.AllPieces(), "Home Forge");
			args.Context.AddString("Done. Checked our hammer pieces; details are in LogOutput.log. Tell Claude.");
		}
	}

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<bool> NeedDevcommands;

	internal static ConfigEntry<bool> RevealOnBossKill;

	private static readonly Boss[] Bosses = new Boss[9]
	{
		new Boss("Eikthyr's Spirit altar", new string[3] { "meadows", "eikthyr", "spirit" }, "MeadowsAltar_RtD").After("defeated_eikthyr", "Eikthyr"),
		new Boss("Troll King altar", new string[5] { "blackforest", "bf", "forest", "troll", "trollking" }, "BlackForestAltar_RtD").After("defeated_gdking", "The Elder"),
		new Boss("Swamp Lord altar", new string[2] { "swamp", "swamplord" }, "SwampAltar_RtD").After("defeated_bonemass", "Bonemass"),
		new Boss("King Fenrir altar", new string[2] { "mountain", "fenrir" }, "MountainEntrance_Boss_RtD", "MountainAltar_RtD").After("defeated_dragon", "Moder"),
		new Boss("Spider Queen altar", new string[4] { "mistlands", "mist", "spider", "spiderqueen" }, "MistlandsEntrance_Boss_RtD", "MistlandsAltar_RtD").After("defeated_queen", "The Queen"),
		new Boss("Goblin King altar", new string[3] { "plains", "goblin", "goblinking" }, "PlainsAltar_RtD").After("defeated_goblinking", "Yagluth"),
		new Boss("Necromancer altar", new string[3] { "ashlands", "ash", "necromancer" }, "NecromancerAltar_RtD").After("defeated_fader", "Fader"),
		new Boss("Lich King shrine", new string[4] { "deepnorth", "north", "lich", "lichking" }, "SpiritShrine_RtD"),
		new Boss("Thor shrine", new string[1] { "thor" }, "ThorShrine_RtD")
	};

	private static readonly Dictionary<string, string> FaeRunestones = new Dictionary<string, string>
	{
		{ "meadows", "RuneStone_Fae_Meadows_RtD" },
		{ "blackforest", "RuneStone_Fae_BlacKForest_RtD" },
		{ "swamp", "RuneStone_Fae_Swamp_RtD" },
		{ "mountain", "RuneStone_Fae_Mountain_RtD" },
		{ "plains", "RuneStone_Fae_Plains_RtD" },
		{ "mistlands", "RuneStone_Fae_Mistlands_RtD" },
		{ "ashlands", "RuneStone_Fae_AshLands_RtD" },
		{ "deepnorth", "RuneStone_Fae_DeepNorth_RtD" }
	};

	private static bool registered;

	private static float nextCheck;

	private static string farmerLoc;

	private static object farmerZone;

	internal static void Bind(ConfigFile cfg)
	{
		Enabled = cfg.Bind<bool>("12 - Altar Finder", "Enabled", true, "Adds the console command 'altar <biome> [all]' (F5): pins the nearest RtD Legends boss altar of that biome on your map, or all of them with 'all'. Example: altar blackforest");
		NeedDevcommands = cfg.Bind<bool>("12 - Altar Finder", "Needs devcommands", false, "On = the altar command only works with devcommands turned on (counts as a cheat).");
		RevealOnBossKill = cfg.Bind<bool>("12 - Altar Finder", "Boss kill reveals altars", true, "On = killing a biome's vanilla boss pins every RtD Legends altar of that biome on your map (Eikthyr -> Eikthyr's Spirit, Elder -> Troll King, Bonemass -> Swamp Lord, Moder -> King Fenrir, Yagluth -> Goblin King, Queen -> Spider Queen, Fader -> Necromancer). Bosses killed earlier count too. Each player gets the pins once.");
	}

	internal static void Patch(Harmony h)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Expected O, but got Unknown
		h.Patch((MethodBase)AccessTools.Method(typeof(Terminal), "InitTerminal", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(AltarFinder), "AfterInitTerminal", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	internal static string Status()
	{
		if (!Enabled.Value)
		{
			return "off";
		}
		return "command 'altar <biome> [all]'" + (NeedDevcommands.Value ? " (devcommands)" : "") + (RevealOnBossKill.Value ? ", boss kills reveal altars" : "");
	}

	private static void AfterInitTerminal()
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Expected O, but got Unknown
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0072: Expected O, but got Unknown
		if (registered || !Enabled.Value)
		{
			return;
		}
		registered = true;
		new ConsoleCommand("altar", "<biome> [all] - pin the nearest RtD boss altar (or all of them). Biomes: meadows, blackforest, swamp, mountain, mistlands, plains, ashlands, deepnorth, thor. Also: altar farmer (FarmHeim Farmer), altar fae [biome] [all] (Fae Runestones / Pixie Queens)", new ConsoleEvent(Run), NeedDevcommands.Value, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		object obj = <>c.<>9__10_0;
		if (obj == null)
		{
			ConsoleEvent val = delegate(ConsoleEventArgs args)
			{
				Plugin.RelearnPieces();
				Plugin.DiagnosePieces(MaterialPiles.AllPieces(), "Material Piles");
				Plugin.DiagnosePieces(HomeForge.AllPieces(), "Home Forge");
				args.Context.AddString("Done. Checked our hammer pieces; details are in LogOutput.log. Tell Claude.");
			};
			<>c.<>9__10_0 = val;
			obj = (object)val;
		}
		new ConsoleCommand("dmt_pieces", "Demigod Mod Tweaks: re-learn our hammer pieces and write a check of them to the log (hold the hammer first).", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
	}

	private static void Run(ConsoleEventArgs args)
	{
		//IL_031f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0324: Unknown result type (might be due to invalid IL or missing references)
		//IL_0590: Unknown result type (might be due to invalid IL or missing references)
		//IL_04b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_04e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_0423: Unknown result type (might be due to invalid IL or missing references)
		//IL_0553: Unknown result type (might be due to invalid IL or missing references)
		//IL_0558: Unknown result type (might be due to invalid IL or missing references)
		//IL_0585: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			Terminal context = args.Context;
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)Minimap.instance == (Object)null)
			{
				context.AddString("altar: get in game first.");
				return;
			}
			if (args.Length < 2)
			{
				context.AddString("Usage: altar <biome> [all]   e.g. altar blackforest   Biomes: meadows, blackforest, swamp, mountain, mistlands, plains, ashlands, deepnorth, thor. Also: altar farmer (FarmHeim Farmer), altar fae [biome] [all] (Fae Runestones / Pixie Queens)");
				return;
			}
			string key = args[1].ToLowerInvariant().Replace("_", "").Replace("-", "");
			Boss boss = Bosses.FirstOrDefault((Boss b) => b.Keys.Contains(key));
			if (boss == null && (key == "farmer" || key == "farm" || key == "farmheim"))
			{
				string text = FarmerLocationName();
				if (text == null)
				{
					context.AddString("altar farmer: couldn't find FarmHeim's farm location in this game (is FarmHeim installed?).");
					return;
				}
				if (text.Length == 0 && (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()))
				{
					context.AddString("altar farmer: only the host can look this one up (FarmHeim hides its name).");
					return;
				}
				boss = new Boss("FarmHeim Farmer", new string[1] { "farmer" }, text);
			}
			int num = 2;
			if (boss == null && (key == "fae" || key == "pixie" || key == "faerune" || key == "runestone"))
			{
				string biomeKey = ((args.Length > 2) ? args[2].ToLowerInvariant().Replace("_", "").Replace("-", "") : "");
				string[] locs;
				if (biomeKey == "" || biomeKey == "all")
				{
					locs = FaeRunestones.Values.ToArray();
				}
				else
				{
					Boss boss2 = Bosses.FirstOrDefault((Boss x) => x.Keys.Contains(biomeKey));
					string key2 = ((boss2 != null) ? boss2.Keys[0] : biomeKey);
					if (!FaeRunestones.TryGetValue(key2, out var value))
					{
						context.AddString("altar fae: don't know biome '" + args[2] + "'. Try: meadows, blackforest, swamp, mountain, plains, mistlands, ashlands, deepnorth");
						return;
					}
					locs = new string[1] { value };
					num = 3;
				}
				boss = new Boss("Fae Runestone", new string[1] { "fae" }, locs);
			}
			if (boss == null)
			{
				context.AddString("altar: don't know '" + args[1] + "'. Try: meadows, blackforest, swamp, mountain, mistlands, plains, ashlands, deepnorth, thor, farmer, fae");
				return;
			}
			bool flag = (args.Length > num && args[num].ToLowerInvariant() == "all") || (num == 2 && args.Length > 2 && args[2].ToLowerInvariant() == "all");
			Vector3 me = ((Component)Player.m_localPlayer).transform.position;
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && (Object)(object)ZoneSystem.instance != (Object)null)
			{
				List<Vector3> list = new List<Vector3>();
				if (Traverse.Create((object)ZoneSystem.instance).Field("m_locationInstances").GetValue() is IDictionary dictionary)
				{
					foreach (object value4 in dictionary.Values)
					{
						Traverse val = Traverse.Create(value4);
						object value2 = val.Field("m_location").GetValue();
						if (value2 != null)
						{
							string value3 = Traverse.Create(value2).Field("m_prefabName").GetValue<string>();
							if ((boss.Label == "FarmHeim Farmer" && farmerZone != null && value2 == farmerZone) || (!string.IsNullOrEmpty(value3) && boss.Locations.Contains(value3)))
							{
								list.Add(val.Field("m_position").GetValue<Vector3>());
							}
						}
					}
				}
				if (list.Count == 0)
				{
					context.AddString("No " + boss.Label + " in this world (RtD Legends couldn't place any, or the world was made before the mod).");
					return;
				}
				list = list.OrderBy((Vector3 p) => Vector3.Distance(me, p)).ToList();
				int num2 = 0;
				IEnumerable<Vector3> enumerable;
				if (!flag)
				{
					enumerable = list.Take(1);
				}
				else
				{
					IEnumerable<Vector3> enumerable2 = list;
					enumerable = enumerable2;
				}
				foreach (Vector3 item in enumerable)
				{
					if (AddPin(item, boss.Label))
					{
						num2++;
					}
				}
				Vector3 val2 = list[0];
				context.AddString(list.Count + " " + boss.Label + "(s) in this world. " + (flag ? "Pinned all" : "Pinned the nearest") + ((num2 == 0) ? " (already pinned)" : "") + ". Nearest is " + Mathf.RoundToInt(Vector3.Distance(me, val2)) + " m away.");
				Minimap.instance.ShowPointOnMap(val2);
			}
			else
			{
				Ask(boss, me, showMap: true, flag);
				context.AddString("Asked the host for " + (flag ? "every " : "the nearest ") + boss.Label + ". Check your map.");
			}
		}
		catch (Exception ex)
		{
			args.Context.AddString("altar failed: " + ex.Message);
			Plugin.Log.LogWarning((object)("Altar Finder: " + ex));
		}
	}

	private static void Ask(Boss boss, Vector3 me, bool showMap, bool all)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		string[] locations = boss.Locations;
		foreach (string text in locations)
		{
			Game.instance.DiscoverClosestLocation(text, me, boss.Label, 9, showMap, all);
		}
	}

	internal static void Tick()
	{
		//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
		if (!Enabled.Value || !RevealOnBossKill.Value || Time.time < nextCheck)
		{
			return;
		}
		nextCheck = Time.time + 5f;
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null || (Object)(object)Game.instance == (Object)null || (Object)(object)Minimap.instance == (Object)null || ZRoutedRpc.instance == null || ((Character)localPlayer).IsTeleporting() || ((Character)localPlayer).IsDead())
		{
			return;
		}
		Boss[] bosses = Bosses;
		foreach (Boss boss in bosses)
		{
			if (boss.KillKey != null && ZoneSystem.instance.GetGlobalKey(boss.KillKey))
			{
				string text = "dmt_altars_" + boss.KillKey;
				if (!((Humanoid)localPlayer).HaveUniqueKey(text))
				{
					((Humanoid)localPlayer).AddUniqueKey(text);
					Ask(boss, ((Component)localPlayer).transform.position, showMap: false, all: true);
					((Character)localPlayer).Message((MessageType)2, boss.KillerName + " has fallen. Every " + boss.Label + " is marked on your map.", 0, (Sprite)null, false);
					Plugin.Log.LogInfo((object)("Altar Finder: " + boss.KillKey + " is set, pinned every " + boss.Label + "."));
					nextCheck = Time.time + 6f;
					break;
				}
			}
		}
	}

	private static string FarmerLocationName()
	{
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Invalid comparison between Unknown and I4
		if (farmerLoc != null)
		{
			return farmerLoc;
		}
		if ((Object)(object)ZoneSystem.instance == (Object)null)
		{
			return null;
		}
		Type type = AccessTools.TypeByName("FarmHeimFarmer");
		ZoneLocation val = null;
		int num = 0;
		foreach (ZoneLocation location in ZoneSystem.instance.m_locations)
		{
			if (location == null)
			{
				continue;
			}
			string text = location.m_prefabName ?? "";
			if (text.StartsWith("MWL_") || text.StartsWith("WoodFarm") || text.EndsWith("_RtD"))
			{
				continue;
			}
			int num2 = 0;
			if ((int)location.m_biome == 1)
			{
				num2++;
			}
			if (Mathf.Approximately(location.m_minDistanceFromCenter, 400f) && Mathf.Approximately(location.m_maxDistanceFromCenter, 1500f))
			{
				num2 += 3;
			}
			if (location.m_quantity == 4)
			{
				num2++;
			}
			if (text.ToLowerInvariant().Contains("farm"))
			{
				num2 += 2;
			}
			if (type != null)
			{
				try
				{
					Traverse val2 = Traverse.Create(Traverse.Create((object)location).Field("m_prefab").GetValue());
					bool flag = false;
					if (!val2.Property("IsLoaded", (object[])null).GetValue<bool>())
					{
						val2.Method("Load", Array.Empty<object>()).GetValue();
						flag = true;
					}
					GameObject value = val2.Property("Asset", (object[])null).GetValue<GameObject>();
					if ((Object)(object)value != (Object)null && (Object)(object)value.GetComponentInChildren(type, true) != (Object)null)
					{
						num2 += 10;
					}
					if (flag)
					{
						val2.Method("Release", Array.Empty<object>()).GetValue();
					}
				}
				catch
				{
				}
			}
			if (num2 > num)
			{
				num = num2;
				val = location;
			}
		}
		if (val == null || num < 5)
		{
			Plugin.Log.LogWarning((object)("Altar Finder: no FarmHeim farm location found (best score " + num + ")."));
			return null;
		}
		farmerZone = val;
		farmerLoc = val.m_prefabName ?? "";
		Plugin.Log.LogInfo((object)("Altar Finder: FarmHeim farm location is '" + farmerLoc + "' (score " + num + ")."));
		return farmerLoc;
	}

	private static bool AddPin(Vector3 p, string label)
	{
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		if (Traverse.Create((object)Minimap.instance).Method("HaveSimilarPin", new Type[4]
		{
			typeof(Vector3),
			typeof(PinType),
			typeof(string),
			typeof(bool)
		}, (object[])null).GetValue<bool>(new object[4]
		{
			p,
			(object)(PinType)9,
			label,
			true
		}))
		{
			return false;
		}
		MethodInfo methodInfo = AccessTools.Method(typeof(Minimap), "AddPin", (Type[])null, (Type[])null);
		ParameterInfo[] parameters = methodInfo.GetParameters();
		object[] array = new object[parameters.Length];
		for (int i = 0; i < parameters.Length; i++)
		{
			array[i] = ((parameters[i].HasDefaultValue && parameters[i].DefaultValue != null) ? parameters[i].DefaultValue : (parameters[i].ParameterType.IsValueType ? Activator.CreateInstance(parameters[i].ParameterType) : null));
		}
		array[0] = p;
		array[1] = (object)(PinType)9;
		array[2] = label;
		array[3] = true;
		array[4] = false;
		methodInfo.Invoke(Minimap.instance, array);
		return true;
	}
}
internal static class AttackSpeedCap
{
	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<float> MaxBonus;

	private static float lastSeen;

	private static float lastCapped;

	private static FieldRef<CharacterAnimEvent, Animator> fAnim;

	private static FieldRef<CharacterAnimEvent, Character> fChar;

	private static FieldRef<Humanoid, Attack> fAttack;

	internal static void Bind(ConfigFile cfg)
	{
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Expected O, but got Unknown
		Enabled = cfg.Bind<bool>("25 - Attack Speed Cap", "Enabled", true, "One cap on attack speed from ALL sources together (infusions, gems, attributes...). Needs a restart.");
		MaxBonus = cfg.Bind<float>("25 - Attack Speed Cap", "Max attack speed bonus (%)", 50f, new ConfigDescription("Your weapon attacks can be at most this much faster than normal, however many bonuses you stack. 50 = up to 1.5x speed. Works right away.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
	}

	internal static string Status()
	{
		return "on (max +" + MaxBonus.Value.ToString("0") + "%" + ((lastSeen > 0f) ? (", last attack wanted x" + lastSeen.ToString("0.00") + ((lastCapped > 0f) ? (", capped to x" + lastCapped.ToString("0.00")) : "")) : "") + ")";
	}

	internal static void Patch(Harmony h)
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Expected O, but got Unknown
		fAnim = AccessTools.FieldRefAccess<CharacterAnimEvent, Animator>("m_animator");
		fChar = AccessTools.FieldRefAccess<CharacterAnimEvent, Character>("m_character");
		fAttack = AccessTools.FieldRefAccess<Humanoid, Attack>("m_currentAttack");
		HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(AttackSpeedCap), "After", (Type[])null, (Type[])null))
		{
			priority = 0
		};
		h.Patch((MethodBase)AccessTools.Method(typeof(CharacterAnimEvent), "CustomFixedUpdate", (Type[])null, (Type[])null), (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private static void After(CharacterAnimEvent __instance)
	{
		if (!Enabled.Value || (Object)(object)__instance == (Object)null)
		{
			return;
		}
		try
		{
			Character val = fChar.Invoke(__instance);
			if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer || !val.InAttack())
			{
				return;
			}
			Humanoid val2 = (Humanoid)(object)((val is Humanoid) ? val : null);
			if ((Object)(object)val2 == (Object)null || fAttack.Invoke(val2) == null)
			{
				return;
			}
			Animator val3 = fAnim.Invoke(__instance);
			if (!((Object)(object)val3 == (Object)null))
			{
				float num = 1f + Mathf.Max(0f, MaxBonus.Value) / 100f;
				float speed = val3.speed;
				if (speed > 1.001f)
				{
					lastSeen = speed;
				}
				if (speed > num)
				{
					val3.speed = num;
					lastCapped = num;
				}
				else if (speed > 1.001f)
				{
					lastCapped = 0f;
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Attack Speed Cap: " + ex.Message));
			Enabled.Value = false;
		}
	}
}
internal static class AutoParty
{
	private class Pending
	{
		public float firstSeen;

		public float lastTry = -999f;

		public int tries;

		public string name;
	}

	internal static ConfigEntry<bool> Enabled;

	private const float CheckEvery = 5f;

	private const float JoinDelay = 10f;

	private const float RetryEvery = 15f;

	private const int MaxTries = 3;

	private static bool resolved;

	private static bool available;

	private static MethodInfo mForce;

	private static MethodInfo mGroupPlayers;

	private static MethodInfo mGetLeader;

	private static MethodInfo mFromPlayer;

	private static MethodInfo mFromPlayerInfo;

	private static FieldInfo fPeerId;

	private static readonly Dictionary<long, Pending> pending = new Dictionary<long, Pending>();

	private static readonly HashSet<long> done = new HashSet<long>();

	private static float nextCheck;

	private static int added;

	private static string state = "waiting for the world";

	internal static void Bind(ConfigFile cfg)
	{
		Enabled = cfg.Bind<bool>("8 - Auto Party", "Enabled", true, "Needs the Groups mod. When you host, everyone who joins is put in your party automatically, no /invite needed. Each player is pulled in once per visit: if they leave the party on purpose, they stay out until they rejoin the world. Only the host's setting matters.");
	}

	private static bool Resolve()
	{
		if (resolved)
		{
			return available;
		}
		resolved = true;
		try
		{
			Type type = AccessTools.TypeByName("Groups.API");
			Type type2 = AccessTools.TypeByName("Groups.PlayerReference");
			if (type == null || type2 == null)
			{
				state = "off (Groups mod not installed)";
				return available = false;
			}
			mForce = AccessTools.Method(type, "ForcePlayerIntoOwnGroup", (Type[])null, (Type[])null);
			mGroupPlayers = AccessTools.Method(type, "GroupPlayers", (Type[])null, (Type[])null);
			mGetLeader = AccessTools.Method(type, "GetLeader", (Type[])null, (Type[])null);
			mFromPlayer = AccessTools.Method(type2, "fromPlayer", (Type[])null, (Type[])null);
			mFromPlayerInfo = AccessTools.Method(type2, "fromPlayerInfo", (Type[])null, (Type[])null);
			fPeerId = AccessTools.Field(type2, "peerId");
			available = mForce != null && mGroupPlayers != null && mFromPlayer != null && mFromPlayerInfo != null && fPeerId != null;
			if (!available)
			{
				state = "off (this Groups version is different, nothing changed)";
				Plugin.Log.LogWarning((object)"Auto Party: Groups mod found, but its API is not what this plugin expects, so Auto Party is off.");
			}
			else
			{
				Plugin.Log.LogInfo((object)"Auto Party ready (Groups mod found).");
			}
		}
		catch (Exception ex)
		{
			available = false;
			state = "off (error, see log)";
			Plugin.Log.LogWarning((object)("Auto Party: could not hook the Groups mod: " + ex.Message));
		}
		return available;
	}

	internal static void Tick()
	{
		if (Enabled == null || !Enabled.Value || Time.unscaledTime < nextCheck)
		{
			return;
		}
		nextCheck = Time.unscaledTime + 5f;
		if (!Resolve())
		{
			return;
		}
		try
		{
			Check();
		}
		catch (Exception ex)
		{
			Enabled.Value = false;
			state = "off (error, see log)";
			Plugin.Log.LogWarning((object)("Auto Party turned itself off after an error: " + ex.Message));
		}
	}

	private static long PeerOf(object playerRef)
	{
		if (playerRef != null)
		{
			return (long)fPeerId.GetValue(playerRef);
		}
		return 0L;
	}

	private static void Check()
	{
		//IL_0120: Unknown result type (might be due to invalid IL or missing references)
		ZNet instance = ZNet.instance;
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)instance == (Object)null || (Object)(object)localPlayer == (Object)null)
		{
			state = "waiting for the world";
			return;
		}
		if (!instance.IsServer())
		{
			state = "idle (only the host adds people)";
			ForgetAll();
			return;
		}
		long num = PeerOf(mFromPlayer.Invoke(null, new object[1] { localPlayer }));
		HashSet<long> hashSet = new HashSet<long>();
		if (mGroupPlayers.Invoke(null, null) is IEnumerable enumerable)
		{
			foreach (object item in enumerable)
			{
				hashSet.Add(PeerOf(item));
			}
		}
		if (hashSet.Count > 0 && mGetLeader != null)
		{
			object obj = mGetLeader.Invoke(null, null);
			if (obj != null && PeerOf(obj) != num)
			{
				state = "idle (someone else leads your party)";
				return;
			}
		}
		HashSet<long> online = new HashSet<long>();
		foreach (PlayerInfo player in instance.GetPlayerList())
		{
			object obj2 = player;
			object obj3 = mFromPlayerInfo.Invoke(null, new object[1] { obj2 });
			long num2 = PeerOf(obj3);
			if (num2 == 0L || num2 == num)
			{
				continue;
			}
			online.Add(num2);
			if (done.Contains(num2))
			{
				continue;
			}
			if (hashSet.Contains(num2))
			{
				done.Add(num2);
				pending.Remove(num2);
				added++;
				Plugin.Log.LogInfo((object)("Auto Party: " + obj3?.ToString() + " is now in your party."));
				continue;
			}
			if (!pending.TryGetValue(num2, out var value))
			{
				value = new Pending
				{
					firstSeen = Time.unscaledTime,
					name = obj3.ToString()
				};
				pending[num2] = value;
			}
			if (!(Time.unscaledTime - value.firstSeen < 10f) && !(Time.unscaledTime - value.lastTry < 15f))
			{
				if (value.tries >= 3)
				{
					done.Add(num2);
					pending.Remove(num2);
					Plugin.Log.LogWarning((object)("Auto Party: " + value.name + " did not join after " + 3 + " tries (party full, or they block invitations?). Leaving them alone this visit."));
				}
				else
				{
					value.tries++;
					value.lastTry = Time.unscaledTime;
					mForce.Invoke(null, new object[1] { obj3 });
				}
			}
		}
		done.RemoveWhere((long id) => !online.Contains(id));
		List<long> list = new List<long>();
		foreach (long key in pending.Keys)
		{
			if (!online.Contains(key))
			{
				list.Add(key);
			}
		}
		foreach (long item2 in list)
		{
			pending.Remove(item2);
		}
		state = ((online.Count == 0) ? "on, nobody else online" : ("on, " + ((hashSet.Count > 0) ? (hashSet.Count - 1) : 0) + " other(s) in your party"));
	}

	private static void ForgetAll()
	{
		done.Clear();
		pending.Clear();
	}

	internal static string Status()
	{
		return state + ((added > 0) ? (" (" + added + " added this session)") : "");
	}
}
internal static class CoinDrops
{
	internal static ConfigEntry<float> Multiplier;

	private static long seen;

	private static long kept;

	private static bool Active => !Mathf.Approximately(Multiplier.Value, 1f);

	internal static void Bind(ConfigFile cfg)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Expected O, but got Unknown
		Multiplier = cfg.Bind<float>("24 - Coin Drops", "Coin drop multiplier", 1f, new ConfigDescription("Scales coins dropped by creatures, chests and other drop tables. 1 = unchanged, 0.5 = half, 0 = none. Trader prices are not affected. Works right away.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
	}

	internal static string Status()
	{
		float value = Multiplier.Value;
		if (Mathf.Approximately(value, 1f))
		{
			return "off (multiplier 1 = normal coins)";
		}
		return "on (coins x" + value.ToString("0.##") + ((seen > 0) ? (", this session " + kept + " of " + seen + " coins dropped") : "") + ")";
	}

	internal static void Patch(Harmony h)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Expected O, but got Unknown
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Expected O, but got Unknown
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ff: Expected O, but got Unknown
		h.Patch((MethodBase)AccessTools.Method(typeof(CharacterDrop), "GenerateDropList", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CoinDrops), "AfterCharacter", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(DropTable)))
		{
			if (declaredMethod.Name == "GetDropList" && declaredMethod.ReturnType == typeof(List<GameObject>))
			{
				h.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CoinDrops), "AfterObjects", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			else if (declaredMethod.Name == "GetDropListItems" && declaredMethod.ReturnType == typeof(List<ItemData>))
			{
				h.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CoinDrops), "AfterItems", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
	}

	private static bool IsCoin(GameObject go)
	{
		if ((Object)(object)go != (Object)null)
		{
			return ((Object)go).name == "Coins";
		}
		return false;
	}

	private static int Scale(int amount)
	{
		if (amount <= 0)
		{
			return amount;
		}
		float num = (float)amount * Mathf.Max(0f, Multiplier.Value);
		int num2 = Mathf.FloorToInt(num);
		if (Random.value < num - (float)num2)
		{
			num2++;
		}
		seen += amount;
		kept += num2;
		return num2;
	}

	private static void AfterCharacter(List<KeyValuePair<GameObject, int>> __result)
	{
		if (!Active || __result == null)
		{
			return;
		}
		try
		{
			for (int num = __result.Count - 1; num >= 0; num--)
			{
				KeyValuePair<GameObject, int> keyValuePair = __result[num];
				if (IsCoin(keyValuePair.Key))
				{
					int num2 = Scale(keyValuePair.Value);
					if (num2 <= 0)
					{
						__result.RemoveAt(num);
					}
					else
					{
						__result[num] = new KeyValuePair<GameObject, int>(keyValuePair.Key, num2);
					}
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Coin Drops: " + ex.Message));
		}
	}

	private static void AfterObjects(List<GameObject> __result)
	{
		if (!Active || __result == null)
		{
			return;
		}
		try
		{
			List<GameObject> list = new List<GameObject>();
			for (int num = __result.Count - 1; num >= 0; num--)
			{
				if (IsCoin(__result[num]))
				{
					int num2 = Scale(1);
					if (num2 == 0)
					{
						__result.RemoveAt(num);
					}
					for (int i = 1; i < num2; i++)
					{
						list.Add(__result[num]);
					}
				}
			}
			__result.AddRange(list);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Coin Drops: " + ex.Message));
		}
	}

	private static void AfterItems(List<ItemData> __result)
	{
		if (!Active || __result == null)
		{
			return;
		}
		try
		{
			for (int num = __result.Count - 1; num >= 0; num--)
			{
				ItemData val = __result[num];
				if (val != null && val.m_shared != null && !(val.m_shared.m_name != "$item_coins"))
				{
					int num2 = Scale(val.m_stack);
					if (num2 <= 0)
					{
						__result.RemoveAt(num);
					}
					else
					{
						val.m_stack = num2;
					}
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Coin Drops: " + ex.Message));
		}
	}
}
internal static class CompatFix
{
	internal static ConfigEntry<bool> Enabled;

	private static Harmony ours;

	private static readonly HashSet<MethodInfo> checkedPatches = new HashSet<MethodInfo>();

	private static float nextScan;

	private static int removedTotal;

	private static readonly Dictionary<short, OpCode> ops = BuildOpTable();

	private static readonly string[][] KnownBroken = new string[6][]
	{
		new string[4] { "com.odinplus.potionsplus", "4.3.9", "PotionsPlus.PotionsPlus+ApplyAlchemySkillOnAlchemyTableCrafting", "Prefix" },
		new string[4] { "com.odinplus.potionsplus", "4.3.9", "PotionsPlus.PotionsPlus+ApplyAlchemySkillOnAlchemyTableCrafting", "Postfix" },
		new string[4] { "blacks7ar.LazyVikings", "1.2.4", "LazyVikings.Patches.CookingStationPatch", "UpdateCooking_Prefix" },
		new string[4] { "flueno.SmartContainers", "1.7.0", "SmartContainers.Inventory_Patch", "Postfix" },
		new string[4] { "flueno.SmartContainers", "1.7.0", "SmartContainers.Gui_Patch", "Postfix" },
		new string[4] { "flueno.SmartContainers", "1.7.0", "SmartContainers.Container_OnStackAll_Patch", "Postfix" }
	};

	private static readonly Assembly gameAsm = typeof(Player).Assembly;

	internal static void Bind(ConfigFile cfg)
	{
		Enabled = cfg.Bind<bool>("7 - Compat Fix", "Enabled", true, "Removes other mods' patches that would crash because the game updated (they call game methods that no longer exist). Example on Valheim 1.0.21: PotionsPlus broke all crafting, LazyVikings broke cooking stations, Smart Containers broke item moves. Each removal is listed in the BepInEx log as 'Compat Fix: removed ...'. Turn off only to test. Needs a restart to take effect.");
	}

	internal static void Init(Harmony h)
	{
		ours = h;
	}

	internal static void Tick()
	{
		if (Enabled.Value && !(Time.unscaledTime < nextScan))
		{
			nextScan = Time.unscaledTime + 10f;
			Scan();
		}
	}

	private static string KnownBrokenReason(MethodInfo pm)
	{
		if (pm.DeclaringType == null)
		{
			return null;
		}
		string[][] knownBroken = KnownBroken;
		foreach (string[] array in knownBroken)
		{
			if (!(pm.DeclaringType.FullName != array[2]) && !(pm.Name != array[3]) && Chainloader.PluginInfos.TryGetValue(array[0], out var value) && !(value.Metadata.Version.ToString() != array[1]))
			{
				return "a game method changed by the Valheim 1.0.21 update (known issue in " + value.Metadata.Name + " " + array[1] + ")";
			}
		}
		return null;
	}

	internal static void Scan()
	{
		try
		{
			foreach (MethodBase item in Harmony.GetAllPatchedMethods().ToList())
			{
				Patches patchInfo = Harmony.GetPatchInfo(item);
				if (patchInfo == null)
				{
					continue;
				}
				foreach (Patch item2 in patchInfo.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Finalizers).ToList())
				{
					MethodInfo patchMethod = item2.PatchMethod;
					if (patchMethod == null || item2.owner == "kenny.demigodmod.tweaks" || !checkedPatches.Add(patchMethod))
					{
						continue;
					}
					string text = FindMissing(patchMethod, 0, new HashSet<MethodBase>()) ?? KnownBrokenReason(patchMethod);
					if (text != null)
					{
						try
						{
							ours.Unpatch(item, patchMethod);
							removedTotal++;
							Plugin.Log.LogWarning((object)("Compat Fix: removed broken patch " + Describe(patchMethod) + " (mod '" + item2.owner + "') from " + Describe(item) + ". It calls " + text + ", which this Valheim version no longer has."));
						}
						catch (Exception ex)
						{
							Plugin.Log.LogError((object)("Compat Fix: could not remove " + Describe(patchMethod) + " from " + Describe(item) + ": " + ex.Message));
						}
					}
				}
			}
		}
		catch (Exception ex2)
		{
			Plugin.Log.LogWarning((object)("Compat Fix scan failed: " + ex2.Message));
		}
	}

	private static string FindMissing(MethodBase m, int depth, HashSet<MethodBase> seen)
	{
		if (m == null || !seen.Add(m) || m.IsAbstract)
		{
			return null;
		}
		byte[] iLAsByteArray;
		try
		{
			MethodBody methodBody = m.GetMethodBody();
			if (methodBody == null)
			{
				return null;
			}
			iLAsByteArray = methodBody.GetILAsByteArray();
		}
		catch
		{
			return null;
		}
		if (iLAsByteArray == null)
		{
			return null;
		}
		Module module = m.Module;
		Type[] genericTypeArguments = ((m.DeclaringType != null && m.DeclaringType.IsGenericType) ? m.DeclaringType.GetGenericArguments() : null);
		Type[] genericMethodArguments = (m.IsGenericMethod ? m.GetGenericArguments() : null);
		int num = 0;
		while (num < iLAsByteArray.Length)
		{
			short num2 = iLAsByteArray[num++];
			if (num2 == 254 && num < iLAsByteArray.Length)
			{
				num2 = (short)(0xFE00 | iLAsByteArray[num++]);
			}
			if (!ops.TryGetValue(num2, out var value))
			{
				return null;
			}
			switch (value.OperandType)
			{
			case OperandType.ShortInlineBrTarget:
			case OperandType.ShortInlineI:
			case OperandType.ShortInlineVar:
				num++;
				break;
			case OperandType.InlineVar:
				num += 2;
				break;
			case OperandType.InlineI8:
			case OperandType.InlineR:
				num += 8;
				break;
			case OperandType.InlineSwitch:
			{
				int num4 = BitConverter.ToInt32(iLAsByteArray, num);
				num += 4 + 4 * num4;
				break;
			}
			case OperandType.InlineField:
			case OperandType.InlineMethod:
			case OperandType.InlineTok:
			case OperandType.InlineType:
			{
				int num3 = BitConverter.ToInt32(iLAsByteArray, num);
				num += 4;
				MemberInfo memberInfo;
				try
				{
					memberInfo = module.ResolveMember(num3, genericTypeArguments, genericMethodArguments);
				}
				catch (MissingMemberException ex)
				{
					if (depth == 0 || IsGameMember(ex.Message))
					{
						return ex.Message.Replace("Method not found: ", "").Replace("Field not found: ", "");
					}
					break;
				}
				catch (TypeLoadException ex2)
				{
					if (depth == 0 || ex2.Message.IndexOf("assembly_valheim", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return "type " + ex2.TypeName;
					}
					break;
				}
				catch (ArgumentException)
				{
					if (depth == 0 && num3 >> 24 == 10 && WontCompile(m))
					{
						return "a member that no longer exists (token 0x" + num3.ToString("X8") + ")";
					}
					break;
				}
				catch
				{
					break;
				}
				MethodBase methodBase = memberInfo as MethodBase;
				if (methodBase != null && depth < 4 && methodBase.Module == m.Module && value.OperandType == OperandType.InlineMethod)
				{
					string text = FindMissing(methodBase, depth + 1, seen);
					if (text != null)
					{
						return text;
					}
				}
				break;
			}
			default:
				num += 4;
				break;
			case OperandType.InlineNone:
				break;
			}
		}
		return null;
	}

	private static bool IsGameMember(string msg)
	{
		if (string.IsNullOrEmpty(msg))
		{
			return false;
		}
		string[] array = Regex.Split(msg, "[^A-Za-z0-9_+./`]");
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].Trim('.', '\'', '"').Replace('/', '+');
			while (text.Length > 0)
			{
				try
				{
					if (gameAsm.GetType(text, throwOnError: false) != null)
					{
						return true;
					}
				}
				catch
				{
				}
				int num = text.LastIndexOf('.');
				if (num <= 0)
				{
					break;
				}
				text = text.Substring(0, num);
			}
		}
		return false;
	}

	private static string Describe(MethodBase m)
	{
		if (!(m == null))
		{
			return ((m.DeclaringType != null) ? (m.DeclaringType.FullName + ".") : "") + m.Name;
		}
		return "?";
	}

	private static bool WontCompile(MethodBase m)
	{
		try
		{
			RuntimeHelpers.PrepareMethod(m.MethodHandle);
			return false;
		}
		catch (MissingMemberException)
		{
			return true;
		}
		catch (TypeLoadException)
		{
			return true;
		}
		catch
		{
			return false;
		}
	}

	internal static string Status()
	{
		if (removedTotal != 0)
		{
			return removedTotal + " broken patch(es) removed (see log)";
		}
		return "no broken patches found";
	}

	private static Dictionary<short, OpCode> BuildOpTable()
	{
		Dictionary<short, OpCode> dictionary = new Dictionary<short, OpCode>();
		FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (!(fieldInfo.FieldType != typeof(OpCode)))
			{
				OpCode value = (OpCode)fieldInfo.GetValue(null);
				dictionary[value.Value] = value;
			}
		}
		return dictionary;
	}
}
internal static class CraftMax
{
	internal static ConfigEntry<bool> Enabled;

	private static string state = "off";

	private static MethodInfo getNearby;

	private static MethodInfo countIn;

	private static MethodInfo canPull;

	private static FieldInfo cfcRunning;

	private static ConfigEntry<float> azuRange;

	private static readonly Dictionary<string, int> cache = new Dictionary<string, int>();

	private static int cacheFrame = -1;

	internal static void Bind(ConfigFile cfg)
	{
		Enabled = cfg.Bind<bool>("15 - Craft Max From Chests", "Enabled", true, "MultiCraft's max (hold Left Ctrl + click +) also counts materials AzuCraftyBoxes can pull from nearby chests. Needs a restart.");
	}

	internal static string Status()
	{
		return state;
	}

	internal static void Patch(Harmony h)
	{
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Expected O, but got Unknown
		Type type = AccessTools.TypeByName("MultiCraft.Helper");
		Type type2 = AccessTools.TypeByName("AzuCraftyBoxes.API");
		if (type == null || type2 == null)
		{
			state = "off (" + ((type == null) ? "MultiCraft" : "AzuCraftyBoxes") + " not found)";
			return;
		}
		MethodInfo methodInfo = AccessTools.Method(type, "GetAvailableItems", (Type[])null, (Type[])null);
		getNearby = AccessTools.Method(type2, "GetNearbyContainers", (Type[])null, (Type[])null);
		countIn = AccessTools.Method(type2, "CountItemInContainer", (Type[])null, (Type[])null);
		canPull = AccessTools.Method(type2, "CanItemBePulled", (Type[])null, (Type[])null);
		cfcRunning = AccessTools.Field(type, "CraftFromContainersRunning");
		if (methodInfo == null || getNearby == null || countIn == null)
		{
			state = "off (MultiCraft or AzuCraftyBoxes changed, see log)";
			Plugin.Log.LogWarning((object)"Craft Max: a mod changed its code; nothing patched. Tell Claude.");
		}
		else
		{
			h.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CraftMax), "AfterCount", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			state = "on (MultiCraft max counts AzuCraftyBoxes chests)";
		}
	}

	private static float Range()
	{
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Expected O, but got Unknown
		if (azuRange == null && Chainloader.PluginInfos.TryGetValue("Azumatt.AzuCraftyBoxes", out var value) && (Object)(object)value.Instance != (Object)null)
		{
			value.Instance.Config.TryGetEntry<float>(new ConfigDefinition("2 - CraftyBoxes", "Container Range"), ref azuRange);
		}
		if (azuRange == null)
		{
			return 20f;
		}
		return azuRange.Value;
	}

	private static void AfterCount(string __0, ref int __result)
	{
		try
		{
			if (!Enabled.Value || string.IsNullOrEmpty(__0) || (cfcRunning != null && (bool)cfcRunning.GetValue(null)))
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				if (Time.frameCount != cacheFrame)
				{
					cache.Clear();
					cacheFrame = Time.frameCount;
				}
				if (!cache.TryGetValue(__0, out var value))
				{
					value = CountNearby(localPlayer, __0);
					cache[__0] = value;
				}
				__result += value;
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Craft Max: " + ex.Message));
			Enabled.Value = false;
		}
	}

	private static int CountNearby(Player p, string sharedName)
	{
		CraftingStation currentCraftingStation = p.GetCurrentCraftingStation();
		Component val = (Component)(((Object)(object)currentCraftingStation != (Object)null) ? ((object)currentCraftingStation) : ((object)p));
		if (!((getNearby.IsGenericMethodDefinition ? getNearby.MakeGenericMethod(((object)val).GetType()) : getNearby).Invoke(null, new object[2]
		{
			val,
			Range()
		}) is IEnumerable enumerable))
		{
			return 0;
		}
		string text = null;
		if (canPull != null && (Object)(object)ObjectDB.instance != (Object)null)
		{
			GameObject val2 = ((IEnumerable<GameObject>)ObjectDB.instance.m_items).FirstOrDefault((Func<GameObject, bool>)delegate(GameObject g)
			{
				ItemDrop component = g.GetComponent<ItemDrop>();
				return (Object)(object)component != (Object)null && component.m_itemData.m_shared.m_name == sharedName;
			});
			text = (((Object)(object)val2 != (Object)null) ? ((Object)val2).name : null);
		}
		int num = 0;
		foreach (object item in enumerable)
		{
			if (item == null)
			{
				continue;
			}
			if (canPull != null && text != null)
			{
				object obj = ((canPull.GetParameters()[0].ParameterType == typeof(string)) ? Traverse.Create(item).Method("GetPrefabName", Array.Empty<object>()).GetValue<string>() : item);
				if (!(bool)canPull.Invoke(null, new object[2] { obj, text }))
				{
					continue;
				}
			}
			num += (int)countIn.Invoke(null, new object[2] { item, sharedName });
		}
		return num;
	}
}
internal static class DoorCloser
{
	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<float> Delay;

	private static readonly Dictionary<Door, float> opened = new Dictionary<Door, float>();

	private static float nextTick;

	private static int closedCount;

	internal static void Bind(ConfigFile cfg)
	{
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Expected O, but got Unknown
		Enabled = cfg.Bind<bool>("21 - Villagers Close Doors", "Enabled", true, "Doors opened by villagers/NPCs (no player close by) close again once the doorway is clear. Doors you open are left alone. Needs a restart.");
		Delay = cfg.Bind<float>("21 - Villagers Close Doors", "Close after (seconds)", 3f, new ConfigDescription("Shortest time a villager-opened door stays open.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>()));
	}

	internal static string Status()
	{
		return "on" + ((closedCount > 0) ? (" (closed " + closedCount + " doors this session)") : "");
	}

	internal static void Patch(Harmony h)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Expected O, but got Unknown
		h.Patch((MethodBase)AccessTools.Method(typeof(Door), "RPC_UseDoor", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(DoorCloser), "AfterUseDoor", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private static int State(Door d)
	{
		ZNetView val = (((Object)(object)d != (Object)null) ? ((Component)d).GetComponent<ZNetView>() : null);
		if (!((Object)(object)val != (Object)null) || !val.IsValid())
		{
			return 0;
		}
		return val.GetZDO().GetInt(ZDOVars.s_state, 0);
	}

	private static bool PlayerNear(Vector3 pos, float r)
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		foreach (Player allPlayer in Player.GetAllPlayers())
		{
			if ((Object)(object)allPlayer != (Object)null && Vector3.Distance(((Component)allPlayer).transform.position, pos) < r)
			{
				return true;
			}
		}
		return false;
	}

	private static void AfterUseDoor(Door __instance)
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (Enabled.Value && !((Object)(object)__instance == (Object)null) && !__instance.m_canNotBeClosed)
			{
				if (State(__instance) == 0)
				{
					opened.Remove(__instance);
				}
				else if (PlayerNear(((Component)__instance).transform.position, 6f))
				{
					opened.Remove(__instance);
				}
				else
				{
					opened[__instance] = Time.time;
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Villagers Close Doors: " + ex.Message));
		}
	}

	internal static void Tick()
	{
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		if (!Enabled.Value || opened.Count == 0 || Time.time < nextTick)
		{
			return;
		}
		nextTick = Time.time + 1f;
		try
		{
			List<Door> list = new List<Door>();
			foreach (KeyValuePair<Door, float> item in opened)
			{
				Door key = item.Key;
				if ((Object)(object)key == (Object)null || State(key) == 0)
				{
					list.Add(key);
					continue;
				}
				float num = Time.time - item.Value;
				if (num > 300f)
				{
					list.Add(key);
				}
				else
				{
					if (num < Delay.Value)
					{
						continue;
					}
					Vector3 position = ((Component)key).transform.position;
					if (PlayerNear(position, 3f))
					{
						continue;
					}
					bool flag = false;
					foreach (Character allCharacter in Character.GetAllCharacters())
					{
						if ((Object)(object)allCharacter != (Object)null && !allCharacter.IsPlayer() && Vector3.Distance(((Component)allCharacter).transform.position, position) < 2f)
						{
							flag = true;
							break;
						}
					}
					if (!flag)
					{
						ZNetView component = ((Component)key).GetComponent<ZNetView>();
						if ((Object)(object)component != (Object)null && component.IsValid())
						{
							component.InvokeRPC("UseDoor", new object[1] { true });
							closedCount++;
						}
						list.Add(key);
					}
				}
			}
			foreach (Door item2 in list)
			{
				opened.Remove(item2);
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Villagers Close Doors: " + ex.Message));
		}
	}
}
internal static class EnchantRework
{
	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<int> MaxSockets;

	internal static ConfigEntry<bool> CapeBossOnly;

	internal static ConfigEntry<int> Choices;

	internal static ConfigEntry<bool> BothHands;

	private static string socketNote = "";

	private static string state = "not run yet";

	private const BindingFlags BF = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

	private static FieldInfo gPieces;

	private static FieldInfo pPlace;

	private static FieldInfo pTrophies;

	private static FieldInfo pOpen;

	private static MethodInfo mSlot;

	private static MethodInfo mBoss;

	private static MethodInfo mPrefab;

	internal const string DefaultFile = "# Demigod Mod: Kenny's enchant rework for Forge of Enchantment (read by Demigod Mod Tweaks, module 9).\n# This file is OURS. Forge of Enchantment updates never touch it. Edit with the game closed.\n# One enchant per line:  <enchant id>  <changes...>      Anything after # is a note.\n#   off                 cut: can't roll any more, and copies already on items do nothing (cap 0)\n#   on                  turn back on (put the max= back too)\n#   max=<number>        new cap = the best perfect Mythic roll. Changes enchants already on items too.\n#   slots=<A,B>         which items it can go on: Weapon, Shield, Helmet, Chest, Legs, Cape, Utility, Trinket, Tool\n#   restrictions=none   drop the 'only on X weapon' rule (e.g. climbing was harpoon-only)\n#   home=<Group>        which hunt group the enchant belongs to: Wildlife, Warriors, Mystics, Brutes, Blight\n# Delete a line to give that enchant back its normal values. Delete the whole file to get this default back.\n# Plan: Google sheet 'ENCHANT LIST' tab (2026-10-30).\n\n# --- Cut: raw damage (rework goal: no damage from enchants) ---\ndmg_all off\ndmg_amp_slash off\ndmg_amp_blunt off\ndmg_amp_pierce off\ndmg_amp_fire off\ndmg_amp_frost off\ndmg_amp_lightning off\ndmg_amp_poison off\ndmg_amp_spirit off\ndmg_add_slash off\ndmg_add_blunt off\ndmg_add_pierce off\ndmg_add_fire off\ndmg_add_frost off\ndmg_add_lightning off\ndmg_add_poison off\ndmg_add_spirit off\n\n# --- Cut: Kenny's calls (gems cover these, or too many enchants) ---\nattack_speed off          # Spinel gem instead\nprojectile_speed off\nblock_pct off\nblock_flat off\nstagger off\narmor_pct off\narmor_flat off\nhealth off\nhealth_regen off\nfall_damage off\nstealth off\ncarry off\nskill_level_active off    # Diamond gem instead\nskill_level_passive off   # Diamond gem instead\nrepair_time off           # Sulfur gem instead\ndurability off            # Sulfur gem instead\nlevel_xp off              # only works with Skills Reworked, which we don't have\n\n# --- New caps ---\nblock_stamina max=25      # was 15\neitr_cost max=20          # was 15\nblood_cost max=20         # was 15\neitr_regen max=20         # was 12\nadrenaline max=25         # was 12\nstamina_regen max=20      # was 12\nscout max=50              # was 25\nswim_speed max=30         # was 16\nskill_xp max=25           # was 15\nchop max=100              # was 20 (extra wood from trees comes later, phase 3)\nmine max=100              # was 20 (extra ore comes later, phase 3)\ngourmet max=100           # was 12\nrested max=100            # was 20\nwind_speed max=50         # was 14, Kenny: 'super buff this bad boy'\n\n# --- Slot moves ---\n# climbing stays on the harpoon: it's a grappling hook (throw the harpoon, it hooks, you climb the rope),\n# so it can't work from legs. Example of the syntax if we ever move one:\n#   some_enchant slots=Legs,Cape restrictions=none\n\n# Not done here yet (need new code, later phases): max eitr / max stamina as %, Ward, Cleanse,\n# Elemental resistance, Second Wind, Eitr Siphon, Bloodthirst, Mistform, boss ultimates, re-roll.\n";

	internal static string FilePath => Path.Combine(Paths.ConfigPath, "kenny.demigodmod.enchants.txt");

	internal static void Bind(ConfigFile cfg)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Expected O, but got Unknown
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Expected O, but got Unknown
		MaxSockets = cfg.Bind<int>("9 - Enchant Rework", "Max enchant sockets per item", 1, new ConfigDescription("Kenny 2026-11-07: 1 enchant socket per item (Jewelcrafting already gives gem sockets). The forge mod's own numbers are 3 for weapons/shields/armor, 1 for the rest. 0 = leave the forge mod's numbers. Items that already have more open sockets keep them. Needs a restart.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>()));
		Choices = cfg.Bind<int>("9 - Enchant Rework", "Enchants offered per trophy", 6, new ConfigDescription("Kenny 2026-11-07: when you set a trophy, the forge offers this many enchants from that trophy's group and you pick one. The forge mod stops at 3; we lift that. If the group has fewer for that item, you see all of them. 0 = leave it to the forge mod's own 'Base reels' setting.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 8), Array.Empty<object>()));
		BothHands = cfg.Bind<bool>("9 - Enchant Rework", "Both hand items count for hunt groups", true, "Kenny 2026-11-07: everything equipped counts. The forge mod only counts ONE of the two items in your hands toward hunt group stages and boss cape synergies (the one with more sockets). With this on, the trophies in your weapon AND your shield/off-hand both count. Every creature still counts only once. Needs a restart.");
		CapeBossOnly = cfg.Bind<bool>("9 - Enchant Rework", "Capes take boss trophies only", true, "Kenny 2026-11-07: capes are for boss trophies. Normal (hunt group) trophies can't be set into a cape. Needs a restart.");
		Enabled = cfg.Bind<bool>("9 - Enchant Rework", "Enabled", true, "Applies Kenny's enchant rework (caps, cut enchants, slot moves) to Forge of Enchantment. The rules are in BepInEx\\config\\kenny.demigodmod.enchants.txt (one line per enchant, explained at the top of that file). Edit that file with the game closed. Off = the forge mod's own values. Needs a restart.");
	}

	internal static void Patch(Harmony h)
	{
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Expected O, but got Unknown
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Expected O, but got Unknown
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Expected O, but got Unknown
		//IL_0170: Unknown result type (might be due to invalid IL or missing references)
		//IL_017d: Expected O, but got Unknown
		Type type = AccessTools.TypeByName("ForgeOfEnchantment.Integration.CatalogLoader");
		MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "Load", (Type[])null, (Type[])null));
		if (methodInfo != null)
		{
			h.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(EnchantRework), "AfterLoad", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		Type type2 = AccessTools.TypeByName("ForgeOfEnchantment.Integration.GameForgeRules");
		MethodInfo methodInfo2 = ((type2 == null) ? null : AccessTools.PropertyGetter(type2, "Reels"));
		if (methodInfo2 != null)
		{
			h.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(EnchantRework), "AfterReels", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		else
		{
			Plugin.Log.LogWarning((object)"Enchant Rework: GameForgeRules.Reels not found; the forge offers its own number of enchants.");
		}
		Type type3 = AccessTools.TypeByName("ForgeOfEnchantment.Core.SynergyRules");
		MethodInfo methodInfo3 = ((type3 == null) ? null : AccessTools.Method(type3, "CountedTrophies", (Type[])null, (Type[])null));
		if (methodInfo3 != null && BothHands.Value)
		{
			h.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(EnchantRework), "AfterCountedTrophies", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		else if (methodInfo3 == null)
		{
			Plugin.Log.LogWarning((object)"Enchant Rework: SynergyRules.CountedTrophies not found; only one hand item counts for hunt groups.");
		}
		Type type4 = AccessTools.TypeByName("ForgeOfEnchantment.Core.SocketRules");
		MethodInfo methodInfo4 = ((type4 == null) ? null : AccessTools.Method(type4, "CanInsert", (Type[])null, (Type[])null));
		if (methodInfo4 != null)
		{
			h.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(EnchantRework), "AfterCanInsert", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		else
		{
			Plugin.Log.LogWarning((object)"Enchant Rework: SocketRules.CanInsert not found; 'capes take boss trophies only' is OFF.");
		}
	}

	private static void AfterLoad()
	{
		if (Enabled.Value)
		{
			Apply();
		}
	}

	private static void AfterReels(ref int __result)
	{
		if (Enabled.Value && Choices.Value > 0)
		{
			__result = Choices.Value;
		}
	}

	private static void AfterCountedTrophies(object gear, object __result)
	{
		if (!Enabled.Value || !BothHands.Value || gear == null || !(__result is IList))
		{
			return;
		}
		try
		{
			if (gPieces == null)
			{
				gPieces = AccessTools.Field(gear.GetType(), "Pieces");
			}
			IEnumerable enumerable = ((gPieces == null) ? null : (gPieces.GetValue(gear) as IEnumerable));
			if (enumerable == null)
			{
				return;
			}
			object obj = null;
			object obj2 = null;
			foreach (object item in enumerable)
			{
				if (item != null)
				{
					if (pPlace == null)
					{
						Type type = item.GetType();
						pPlace = AccessTools.Field(type, "Place");
						pTrophies = AccessTools.Field(type, "Trophies");
						pOpen = AccessTools.Field(type, "OpenSockets");
					}
					switch (Convert.ToInt32(pPlace.GetValue(item)))
					{
					case 6:
						obj = item;
						break;
					case 7:
						obj2 = item;
						break;
					}
				}
			}
			if (obj == null || obj2 == null)
			{
				return;
			}
			bool flag = Convert.ToInt32(pOpen.GetValue(obj)) >= Convert.ToInt32(pOpen.GetValue(obj2));
			if (!(pTrophies.GetValue(flag ? obj2 : obj) is IEnumerable enumerable2))
			{
				return;
			}
			IList list = (IList)__result;
			foreach (object item2 in enumerable2)
			{
				list.Add(item2);
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Enchant Rework (both hands): " + ex.Message));
		}
	}

	private static void AfterCanInsert(ItemData item, ItemData trophy, ref string reasonKey, ref bool __result)
	{
		if (!__result || !Enabled.Value || !CapeBossOnly.Value || item == null || trophy == null)
		{
			return;
		}
		try
		{
			if (mSlot == null)
			{
				mSlot = AccessTools.Method(AccessTools.TypeByName("ForgeOfEnchantment.Core.ItemTraits"), "Slot", (Type[])null, (Type[])null);
				Type type = AccessTools.TypeByName("ForgeOfEnchantment.Core.SocketRules");
				mBoss = AccessTools.Method(type, "IsBossTrophy", (Type[])null, (Type[])null);
				mPrefab = AccessTools.Method(type, "TrophyPrefabName", (Type[])null, (Type[])null);
			}
			object obj = mSlot.Invoke(null, new object[1] { item });
			if (obj != null && !(obj.ToString() != "Cape") && (!(mPrefab.Invoke(null, new object[1] { trophy }) is string text) || !(bool)mBoss.Invoke(null, new object[1] { text })))
			{
				__result = false;
				reasonKey = "Capes take boss trophies only";
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Enchant Rework (cape rule): " + ex.Message));
		}
	}

	private static void ApplySockets(Type catT, object cat)
	{
		socketNote = "";
		int value = MaxSockets.Value;
		if (value <= 0)
		{
			return;
		}
		IDictionary dictionary = AccessTools.Field(catT, "_slots").GetValue(cat) as IDictionary;
		Type type = AccessTools.TypeByName("ForgeOfEnchantment.Core.Catalogs.SlotDefinition");
		FieldInfo fieldInfo = ((type == null) ? null : AccessTools.Field(type, "<Sockets>k__BackingField"));
		if (dictionary == null || fieldInfo == null)
		{
			Plugin.Log.LogWarning((object)"Enchant Rework: forge slot list not found; sockets left as they were.");
			return;
		}
		int num = 0;
		foreach (object value2 in dictionary.Values)
		{
			if (value2 != null && (int)fieldInfo.GetValue(value2) > value)
			{
				fieldInfo.SetValue(value2, value);
				num++;
			}
		}
		socketNote = ", " + value + " socket" + ((value == 1) ? "" : "s") + " per item";
		Plugin.Log.LogInfo((object)("Enchant Rework: " + num + " item kinds now have at most " + value + " enchant socket(s)."));
	}

	internal static string Status()
	{
		return state;
	}

	internal static void Apply()
	{
		try
		{
			Type type = AccessTools.TypeByName("ForgeOfEnchantment.Core.Catalogs.ForgeCatalog");
			Type type2 = AccessTools.TypeByName("ForgeOfEnchantment.Core.Catalogs.EffectDefinition");
			Type type3 = AccessTools.TypeByName("ForgeOfEnchantment.Core.Catalogs.ItemSlot");
			if (type == null || type2 == null || type3 == null)
			{
				state = "off (Forge of Enchantment not found)";
				return;
			}
			object value = AccessTools.Property(type, "Current").GetValue(null, null);
			if (value == null)
			{
				state = "waiting (forge catalog not loaded yet)";
				return;
			}
			if (!(AccessTools.Field(type, "_effects").GetValue(value) is IList list))
			{
				state = "off (forge catalog changed, see log)";
				Plugin.Log.LogWarning((object)"Enchant Rework: forge catalog has no effect list; nothing changed.");
				return;
			}
			FieldInfo fieldInfo = AccessTools.Field(type2, "<Max>k__BackingField");
			FieldInfo fieldInfo2 = AccessTools.Field(type2, "<Enabled>k__BackingField");
			FieldInfo fieldInfo3 = AccessTools.Field(type2, "<Slots>k__BackingField");
			FieldInfo fieldInfo4 = AccessTools.Field(type2, "<Restrictions>k__BackingField");
			FieldInfo fieldInfo5 = AccessTools.Field(type2, "<Home>k__BackingField");
			PropertyInfo propertyInfo = AccessTools.Property(type2, "Id");
			if (fieldInfo == null || fieldInfo2 == null || fieldInfo3 == null || fieldInfo4 == null || propertyInfo == null)
			{
				state = "off (forge mod changed, see log)";
				Plugin.Log.LogWarning((object)"Enchant Rework: Forge of Enchantment's enchant format changed; nothing changed. Tell Claude.");
				return;
			}
			NewEnchants.Register(type, value, type2, list);
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
			foreach (object item in list)
			{
				dictionary[(string)propertyInfo.GetValue(item, null)] = item;
			}
			EnsureFile();
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			string[] array = File.ReadAllLines(FilePath);
			foreach (string obj in array)
			{
				num4++;
				string text = obj;
				int num5 = text.IndexOf('#');
				if (num5 >= 0)
				{
					text = text.Substring(0, num5);
				}
				string[] array2 = text.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
				if (array2.Length < 2)
				{
					continue;
				}
				if (!dictionary.TryGetValue(array2[0], out var value2))
				{
					num3++;
					Plugin.Log.LogWarning((object)("Enchant Rework: line " + num4 + ": no enchant called '" + array2[0] + "' (skipped)."));
					continue;
				}
				for (int j = 1; j < array2.Length; j++)
				{
					string text2 = array2[j].ToLowerInvariant();
					try
					{
						if (text2 == "off")
						{
							fieldInfo2.SetValue(value2, false);
							if (((float?)fieldInfo.GetValue(value2)).HasValue)
							{
								fieldInfo.SetValue(value2, 0f);
							}
							num2++;
						}
						else if (text2 == "on")
						{
							fieldInfo2.SetValue(value2, true);
						}
						else if (text2.StartsWith("max="))
						{
							float num6 = float.Parse(text2.Substring(4), CultureInfo.InvariantCulture);
							fieldInfo.SetValue(value2, num6);
							num++;
						}
						else if (text2.StartsWith("slots="))
						{
							IList list2 = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type3));
							string[] array3 = array2[j].Substring(6).Split(',');
							foreach (string text3 in array3)
							{
								list2.Add(Enum.Parse(type3, text3.Trim(), ignoreCase: true));
							}
							fieldInfo3.SetValue(value2, list2);
							num++;
						}
						else if (text2.StartsWith("home=") && fieldInfo5 != null)
						{
							Type enumType = Nullable.GetUnderlyingType(fieldInfo5.FieldType) ?? fieldInfo5.FieldType;
							fieldInfo5.SetValue(value2, Enum.Parse(enumType, array2[j].Substring(5).Trim(), ignoreCase: true));
							num++;
						}
						else if (text2 == "restrictions=none")
						{
							Type typeFromHandle = typeof(IReadOnlyList<string>);
							fieldInfo4.SetValue(value2, Activator.CreateInstance(typeof(Dictionary<, >).MakeGenericType(type3, typeFromHandle)));
							num++;
						}
						else
						{
							num3++;
							Plugin.Log.LogWarning((object)("Enchant Rework: line " + num4 + ": don't understand '" + array2[j] + "' (skipped)."));
						}
					}
					catch (Exception ex)
					{
						num3++;
						Plugin.Log.LogWarning((object)("Enchant Rework: line " + num4 + " '" + array2[j] + "': " + ex.Message));
					}
				}
			}
			try
			{
				ApplySockets(type, value);
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Enchant Rework (sockets): " + ex2.Message));
			}
			state = num2 + " cut, " + num + " changed" + socketNote + ((Choices.Value > 0) ? (", pick 1 of up to " + Choices.Value) : "") + (CapeBossOnly.Value ? ", capes boss-only" : "") + (BothHands.Value ? ", both hands count" : "") + ((num3 > 0) ? (", " + num3 + " problem(s) (see log)") : "");
			Plugin.Log.LogInfo((object)("Enchant Rework applied: " + state + "."));
		}
		catch (Exception ex3)
		{
			state = "off (error, see log)";
			Plugin.Log.LogError((object)("Enchant Rework failed, forge mod left as it was: " + ex3));
		}
	}

	private static void EnsureFile()
	{
		if (!File.Exists(FilePath))
		{
			File.WriteAllText(FilePath, "# Demigod Mod: Kenny's enchant rework for Forge of Enchantment (read by Demigod Mod Tweaks, module 9).\n# This file is OURS. Forge of Enchantment updates never touch it. Edit with the game closed.\n# One enchant per line:  <enchant id>  <changes...>      Anything after # is a note.\n#   off                 cut: can't roll any more, and copies already on items do nothing (cap 0)\n#   on                  turn back on (put the max= back too)\n#   max=<number>        new cap = the best perfect Mythic roll. Changes enchants already on items too.\n#   slots=<A,B>         which items it can go on: Weapon, Shield, Helmet, Chest, Legs, Cape, Utility, Trinket, Tool\n#   restrictions=none   drop the 'only on X weapon' rule (e.g. climbing was harpoon-only)\n#   home=<Group>        which hunt group the enchant belongs to: Wildlife, Warriors, Mystics, Brutes, Blight\n# Delete a line to give that enchant back its normal values. Delete the whole file to get this default back.\n# Plan: Google sheet 'ENCHANT LIST' tab (2026-10-30).\n\n# --- Cut: raw damage (rework goal: no damage from enchants) ---\ndmg_all off\ndmg_amp_slash off\ndmg_amp_blunt off\ndmg_amp_pierce off\ndmg_amp_fire off\ndmg_amp_frost off\ndmg_amp_lightning off\ndmg_amp_poison off\ndmg_amp_spirit off\ndmg_add_slash off\ndmg_add_blunt off\ndmg_add_pierce off\ndmg_add_fire off\ndmg_add_frost off\ndmg_add_lightning off\ndmg_add_poison off\ndmg_add_spirit off\n\n# --- Cut: Kenny's calls (gems cover these, or too many enchants) ---\nattack_speed off          # Spinel gem instead\nprojectile_speed off\nblock_pct off\nblock_flat off\nstagger off\narmor_pct off\narmor_flat off\nhealth off\nhealth_regen off\nfall_damage off\nstealth off\ncarry off\nskill_level_active off    # Diamond gem instead\nskill_level_passive off   # Diamond gem instead\nrepair_time off           # Sulfur gem instead\ndurability off            # Sulfur gem instead\nlevel_xp off              # only works with Skills Reworked, which we don't have\n\n# --- New caps ---\nblock_stamina max=25      # was 15\neitr_cost max=20          # was 15\nblood_cost max=20         # was 15\neitr_regen max=20         # was 12\nadrenaline max=25         # was 12\nstamina_regen max=20      # was 12\nscout max=50              # was 25\nswim_speed max=30         # was 16\nskill_xp max=25           # was 15\nchop max=100              # was 20 (extra wood from trees comes later, phase 3)\nmine max=100              # was 20 (extra ore comes later, phase 3)\ngourmet max=100           # was 12\nrested max=100            # was 20\nwind_speed max=50         # was 14, Kenny: 'super buff this bad boy'\n\n# --- Slot moves ---\n# climbing stays on the harpoon: it's a grappling hook (throw the harpoon, it hooks, you climb the rope),\n# so it can't work from legs. Example of the syntax if we ever move one:\n#   some_enchant slots=Legs,Cape restrictions=none\n\n# Not done here yet (need new code, later phases): max eitr / max stamina as %, Ward, Cleanse,\n# Elemental resistance, Second Wind, Eitr Siphon, Bloodthirst, Mistform, boss ultimates, re-roll.\n");
			Plugin.Log.LogInfo((object)("Enchant Rework: wrote the default rules to " + FilePath));
		}
	}
}
internal static class GearDump
{
	internal static ConfigEntry<bool> Enabled;

	private static string state = "waiting for a world";

	private static bool done;

	internal static void Bind(ConfigFile cfg)
	{
		Enabled = cfg.Bind<bool>("28 - Gear List Export", "Enabled", true, "Once per session, writes all gear + recipes to BepInEx\\config\\kenny.demigodmod.gear.json for Claude's gear browser. Changes nothing in game.");
	}

	internal static string Status()
	{
		return state;
	}

	internal static void Tick()
	{
		if (done || !Enabled.Value || (Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || Localization.instance == null)
		{
			return;
		}
		done = true;
		try
		{
			Write();
		}
		catch (Exception ex)
		{
			state = "failed (see log)";
			Plugin.Log.LogWarning((object)("Gear List Export: " + ex));
		}
	}

	private static string Q(string s)
	{
		if (s == null)
		{
			return "null";
		}
		StringBuilder stringBuilder = new StringBuilder("\"");
		foreach (char c in s)
		{
			if (c == '"' || c == '\\')
			{
				stringBuilder.Append('\\').Append(c);
			}
			else if (c < ' ')
			{
				StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
				int num = c;
				stringBuilder2.Append(num.ToString("x4"));
			}
			else
			{
				stringBuilder.Append(c);
			}
		}
		return stringBuilder.Append('"').ToString();
	}

	private static string F(float f)
	{
		return f.ToString("0.###", CultureInfo.InvariantCulture);
	}

	private static string L(string k)
	{
		return Localization.instance.Localize(k ?? "");
	}

	private static void Write()
	{
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_019c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_033a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0355: Unknown result type (might be due to invalid IL or missing references)
		//IL_0370: Unknown result type (might be due to invalid IL or missing references)
		//IL_038b: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_03dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_0412: Unknown result type (might be due to invalid IL or missing references)
		//IL_042d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0452: Unknown result type (might be due to invalid IL or missing references)
		//IL_0459: Unknown result type (might be due to invalid IL or missing references)
		//IL_0461: Unknown result type (might be due to invalid IL or missing references)
		//IL_0469: Unknown result type (might be due to invalid IL or missing references)
		//IL_0471: Unknown result type (might be due to invalid IL or missing references)
		//IL_0479: Unknown result type (might be due to invalid IL or missing references)
		//IL_0481: Unknown result type (might be due to invalid IL or missing references)
		//IL_0489: Unknown result type (might be due to invalid IL or missing references)
		ObjectDB instance = ObjectDB.instance;
		Dictionary<string, Recipe> dictionary = new Dictionary<string, Recipe>();
		foreach (Recipe recipe in instance.m_recipes)
		{
			if ((Object)(object)recipe != (Object)null && (Object)(object)recipe.m_item != (Object)null && !dictionary.ContainsKey(((Object)recipe.m_item).name))
			{
				dictionary[((Object)recipe.m_item).name] = recipe;
			}
		}
		HashSet<ItemType> hashSet = new HashSet<ItemType>
		{
			(ItemType)3,
			(ItemType)14,
			(ItemType)22,
			(ItemType)4,
			(ItemType)5,
			(ItemType)6,
			(ItemType)7,
			(ItemType)11,
			(ItemType)17,
			(ItemType)18,
			(ItemType)19,
			(ItemType)9,
			(ItemType)15,
			(ItemType)24
		};
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("{\"game\":").Append(Q(Version.GetVersionString(false))).Append(",\"items\":[");
		int num = 0;
		foreach (GameObject item in instance.m_items)
		{
			ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			SharedData shared = val.m_itemData.m_shared;
			if (!hashSet.Contains(shared.m_itemType))
			{
				continue;
			}
			dictionary.TryGetValue(((Object)item).name, out var value);
			DamageTypes damages = shared.m_damages;
			DamageTypes damagesPerLevel = shared.m_damagesPerLevel;
			if (num++ > 0)
			{
				stringBuilder.Append(',');
			}
			stringBuilder.Append("{\"prefab\":").Append(Q(((Object)item).name)).Append(",\"name\":")
				.Append(Q(L(shared.m_name)))
				.Append(",\"type\":")
				.Append(Q(((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString()))
				.Append(",\"skill\":")
				.Append(Q(((object)Unsafe.As<SkillType, SkillType>(ref shared.m_skillType)/*cast due to .constrained prefix*/).ToString()))
				.Append(",\"maxQuality\":")
				.Append(shared.m_maxQuality)
				.Append(",\"weight\":")
				.Append(F(shared.m_weight))
				.Append(",\"move\":")
				.Append(F(shared.m_movementModifier))
				.Append(",\"armor\":")
				.Append(F(shared.m_armor))
				.Append(",\"armorPerLevel\":")
				.Append(F(shared.m_armorPerLevel))
				.Append(",\"block\":")
				.Append(F(shared.m_blockPower))
				.Append(",\"blockPerLevel\":")
				.Append(F(shared.m_blockPowerPerLevel))
				.Append(",\"toolTier\":")
				.Append(shared.m_toolTier)
				.Append(",\"set\":")
				.Append(Q(shared.m_setName))
				.Append(",\"dmg\":{\"blunt\":")
				.Append(F(damages.m_blunt))
				.Append(",\"slash\":")
				.Append(F(damages.m_slash))
				.Append(",\"pierce\":")
				.Append(F(damages.m_pierce))
				.Append(",\"chop\":")
				.Append(F(damages.m_chop))
				.Append(",\"pickaxe\":")
				.Append(F(damages.m_pickaxe))
				.Append(",\"fire\":")
				.Append(F(damages.m_fire))
				.Append(",\"frost\":")
				.Append(F(damages.m_frost))
				.Append(",\"lightning\":")
				.Append(F(damages.m_lightning))
				.Append(",\"poison\":")
				.Append(F(damages.m_poison))
				.Append(",\"spirit\":")
				.Append(F(damages.m_spirit))
				.Append("}")
				.Append(",\"dmgPerLevel\":")
				.Append(F(damagesPerLevel.m_blunt + damagesPerLevel.m_slash + damagesPerLevel.m_pierce + damagesPerLevel.m_fire + damagesPerLevel.m_frost + damagesPerLevel.m_lightning + damagesPerLevel.m_poison + damagesPerLevel.m_spirit))
				.Append(",\"desc\":")
				.Append(Q(L(shared.m_description)));
			if ((Object)(object)value != (Object)null)
			{
				stringBuilder.Append(",\"recipe\":{\"station\":").Append(Q(((Object)(object)value.m_craftingStation != (Object)null) ? L(value.m_craftingStation.m_name) : "")).Append(",\"stationPrefab\":")
					.Append(Q(((Object)(object)value.m_craftingStation != (Object)null) ? ((Object)value.m_craftingStation).name : ""))
					.Append(",\"level\":")
					.Append(value.m_minStationLevel)
					.Append(",\"enabled\":")
					.Append(value.m_enabled ? "true" : "false")
					.Append(",\"amount\":")
					.Append(value.m_amount)
					.Append(",\"needs\":[");
				int num2 = 0;
				Requirement[] resources = value.m_resources;
				foreach (Requirement val2 in resources)
				{
					if (val2 != null && !((Object)(object)val2.m_resItem == (Object)null))
					{
						if (num2++ > 0)
						{
							stringBuilder.Append(',');
						}
						stringBuilder.Append("{\"prefab\":").Append(Q(((Object)val2.m_resItem).name)).Append(",\"name\":")
							.Append(Q(L(val2.m_resItem.m_itemData.m_shared.m_name)))
							.Append(",\"amount\":")
							.Append(val2.m_amount)
							.Append(",\"perLevel\":")
							.Append(val2.m_amountPerLevel)
							.Append('}');
					}
				}
				stringBuilder.Append("]}");
			}
			stringBuilder.Append('}');
		}
		stringBuilder.Append("]}");
		File.WriteAllText(Path.Combine(Paths.ConfigPath, "kenny.demigodmod.gear.json"), stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		state = "wrote " + num + " items to config\\kenny.demigodmod.gear.json";
		Plugin.Log.LogInfo((object)("Gear List Export: " + state));
	}
}
internal static class GemBreak
{
	internal static ConfigEntry<bool> Enabled;

	private static string state = "off";

	private static FieldInfo socketable;

	private static MethodInfo hasEffect;

	internal static void Bind(ConfigFile cfg)
	{
		Enabled = cfg.Bind<bool>("19 - Boss Gems Break Too", "Enabled", true, "Boss (unique) gems are destroyed when you take them out of a socket, like regular gems (Jewelcrafting's break chances are set to 100). Turn off to get boss gems back when removed. Needs a restart.");
	}

	internal static string Status()
	{
		return state;
	}

	internal static void Patch(Harmony h)
	{
		//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Expected O, but got Unknown
		Type type = AccessTools.TypeByName("Jewelcrafting.GemStones");
		MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "ShallDestroyGem", (Type[])null, (Type[])null) : null);
		socketable = ((type != null) ? AccessTools.Field(type, "socketableGemStones") : null);
		hasEffect = ((type != null) ? AccessTools.Method(type, "GemHasEffectInOpenEquipment", (Type[])null, (Type[])null) : null);
		if (methodInfo == null || socketable == null || hasEffect == null)
		{
			if (AccessTools.TypeByName("Jewelcrafting.Jewelcrafting") == null && !Chainloader.PluginInfos.ContainsKey("org.bepinex.plugins.jewelcrafting"))
			{
				state = "off (Jewelcrafting not installed)";
				return;
			}
			state = "off (Jewelcrafting changed, see log)";
			Plugin.Log.LogWarning((object)"Boss Gems Break Too: Jewelcrafting's code changed; nothing patched. Tell Claude.");
		}
		else
		{
			h.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(GemBreak), "After", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			state = "on (removing a boss gem destroys it)";
		}
	}

	private static void After(ItemData item, ref bool __result)
	{
		if (__result || !Enabled.Value || item == null)
		{
			return;
		}
		try
		{
			object value = socketable.GetValue(null);
			MethodInfo methodInfo = value?.GetType().GetMethod("Contains");
			if (!(methodInfo == null) && (bool)methodInfo.Invoke(value, new object[1] { item.m_shared.m_name }) && (bool)hasEffect.Invoke(null, new object[1] { item }))
			{
				__result = true;
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Boss Gems Break Too: " + ex.Message));
		}
	}
}
internal static class GemDefaults
{
	internal const string Yaml = "# Demigod Mod: Kenny's gem rework for Jewelcrafting (read by Demigod Mod Tweaks, module 10, at game start).\n# This file is OURS. Jewelcrafting updates never touch it. Edit with the game closed, then restart.\n# Delete this file to get the default back.\n#\n# Gem colors: red = Ruby, black = Onyx, blue = Sapphire, yellow = Sulfur, purple = Spinel,\n#             green = Emerald, orange = Humite, white = Diamond, cyan = Topaz (group gems).\n# power: [simple, advanced, perfect]. Slots: sword, knife, club, polearm, spear, axe, bow, crossbow,\n#   elementalmagic, bloodmagic, tool (pickaxes, hammer, hoe...), shield, head, chest, legs, cloak, utility, trinket.\n# Jewelry (rings, necklaces) counts as utility. Jewelcrafting can't tell one- from two-handed weapons:\n#   weapon numbers here are the TWO-handed ones; one-handed weapons get 2/3 (F1 -> Demigod Mod Tweaks ->\n#   10 - Gem Rework -> One-handed weapon share). Caps (most you can stack) are in that F1 section too.\n# Plan: Google sheet 'JEWELCRAFTING' tab (2026-10-30).\n\n# ---------- Old Jewelcrafting socket effects: turned off (rework goal: no damage from gems) ----------\n# Kept as Jewelcrafting has them for now: boss gems, Wisplight, Wishbone, Topaz/group gems, and\n# magnetic (Onyx in spears = thrown spear comes back). Color synergies are untouched too (phase 3).\nfire starter: unset\nice heart: unset\nsnake bite: unset\nshadow hit: unset\nvampire: unset\nberserk: unset\nopportunity: unset\nperforation: unset\nthunderclap: unset\nmagical bargain: unset\nprecious blood: unset\ndefender: unset\nvitality: unset\nregeneration: unset\nice skin: unset\nmirror: unset\nfade: unset\nsprinter: unset\nninja: unset\nnimble: unset\nmarathon: unset\nmountain goat: unset\nlifeguard: unset\nmomentum: unset\nsnow piercer: unset\npower recovery: unset\ncomfortable: unset\nhercules: unset\nglowing spirit: unset\ndungeon guide: unset\ndaring: unset\nexplorer: unset\nstudent: unset\nresilience: unset\ngourmet: unset\nmerciful death: unset\neitr surge: unset\nsnow goggles: unset\nendless arrows: unset\nmaster archer: unset\nquick draw: unset\nendless bolts: unset\nmaster arbalist: unset\nquick load: unset\necho: unset\nnecromancer: unset\nricochet: unset\nelemental chaos: unset\nglider: unset\ninconspicuous: unset\nturtle shell: unset\nwind walk: unset\nair dried: unset\nunfazed: unset\nparry master: unset\navoidance: unset\ntank: unset\npain tolerance: unset\nfast reaction: unset\nvampiric parry: unset\nunbreakable: unset\nenergetic: unset\nfrenzy: unset\nlucky lumberjack: unset\nlucky miner: unset\nraging: unset\nprotective trinket: unset\nresentful adrenaline: unset\n\n# IMPORTANT (Jewelcrafting rule): inside one effect, each gem color may appear only ONCE. A second entry\n# with the same color REPLACES the first (whatever its slots). That's why slots with different numbers\n# use their own effect name (e.g. Scholar for armor, Charm Scholar for utility/trinket).\n\n# ---------- RUBY (red): skill XP ----------\nweapon scholar:\n  slot: [ sword, knife, club, polearm, spear, axe, bow, crossbow, elementalmagic, bloodmagic ]\n  gem: red\n  power: [ 6, 9, 12 ]\ngatherer scholar:\n  slot: tool\n  gem: red\n  power: [ 8, 12, 16 ]\nguard scholar:\n  slot: shield\n  gem: red\n  power: [ 6, 9, 12 ]\nscholar:\n  slot: [ head, chest, legs, cloak ]\n  gem: red\n  power: [ 2, 3, 4 ]\ncharm scholar:\n  slot: [ utility, trinket ]\n  gem: red\n  power: [ 3, 4, 5 ]\n\n# ---------- ONYX (black): ammo save on bows/crossbows/jewelry, pickup range on tools (spears: Jewelcrafting's magnetic, kept),\n#            bigger max stamina/eitr everywhere else (Kenny 2026-11-06; weapons: 2H number, 1H gets 2/3) ----------\nammo saver:\n  slot: [ bow, crossbow ]\n  gem: black\n  power: [ 6, 10, 15 ]\nweapon vigor:\n  slot: [ sword, knife, club, polearm, axe ]\n  gem: black\n  power: [ 12, 18, 25 ]\nstaff focus:\n  slot: [ elementalmagic, bloodmagic ]\n  gem: black\n  power: [ 12, 18, 25 ]\nshield reserves:\n  slot: shield\n  gem: black\n  power: [ 4, 6, 8 ]\narmor reserves:\n  slot: [ head, chest, legs, cloak ]\n  gem: black\n  power: [ 4, 6, 8 ]\ncharm ammo saver:\n  slot: [ utility, trinket ]\n  gem: black\n  power: [ 4, 6, 10 ]\nlong reach:\n  slot: tool\n  gem: black\n  power: [ 1, 1.5, 2 ]\n\n# ---------- SAPPHIRE (blue): parry charges (100 = one charge a minute) ----------\nparry charge:\n  slot: [ sword, knife, club, polearm, spear, axe, bow, crossbow, elementalmagic, bloodmagic, tool, head, chest, legs, cloak, utility, trinket ]\n  gem: blue\n  power: [ 67, 80, 100 ]\nshield parry charge:\n  slot: shield\n  gem: blue\n  power: [ 80, 100, 133 ]\n\n# ---------- SULFUR (yellow): repair + slower wear ----------\n# Self Repair numbers are also built into the plugin (it reads each item's own gems); change both together.\nself repair:\n  slot: [ sword, knife, club, polearm, spear, axe, bow, crossbow, elementalmagic, bloodmagic, shield, head, chest, legs, cloak ]\n  gem: yellow\n  power:\n    repair: [ 2, 3, 5 ]\n    wear: [ 10, 15, 20 ]\ntool self repair:\n  slot: tool\n  gem: yellow\n  power:\n    repair: [ 3, 5, 8 ]\n    wear: [ 15, 20, 25 ]\ngear care:\n  slot: [ utility, trinket ]\n  gem: yellow\n  power:\n    repair: [ 1, 1.5, 2 ]\n    wear: [ 5, 8, 10 ]\n\n# ---------- SPINEL (purple): speed ----------\nswift strikes:\n  slot: [ sword, knife, club, polearm, spear, axe ]\n  gem: purple\n  power: [ 5, 7, 10 ]\nswift casting:\n  slot: [ elementalmagic, bloodmagic ]\n  gem: purple\n  power: [ 4, 6, 8 ]\nswift draw:\n  slot: bow\n  gem: purple\n  power: [ 4, 6, 8 ]\nswift reload:\n  slot: crossbow\n  gem: purple\n  power: [ 4, 6, 8 ]\nbusy hands:\n  slot: tool\n  gem: purple\n  power: [ 15, 20, 25 ]\nswiftness:\n  slot: [ shield, head, chest, legs, cloak, utility, trinket ]\n  gem: purple\n  power: [ 2, 3, 4 ]\n\n# ---------- EMERALD (green): move speed ----------\nfleet foot:\n  slot: [ sword, knife, club, polearm, spear, axe, bow, crossbow, elementalmagic, bloodmagic, tool, shield, head, chest ]\n  gem: green\n  power: [ 1, 2, 3 ]\nswift legs:\n  slot: legs\n  gem: green\n  power: [ 3, 4, 6 ]\ncharm fleet foot:\n  slot: [ cloak, utility, trinket ]\n  gem: green\n  power: [ 2, 3, 4 ]\n\n# ---------- HUMITE (orange): regen ----------\nsecond breath:\n  slot: [ sword, knife, club, polearm, spear, axe, bow, crossbow, tool ]\n  gem: orange\n  power: [ 3, 5, 7 ]\ndeep breath:\n  slot: [ shield, legs ]\n  gem: orange\n  power: [ 4, 6, 8 ]\nmind spring:\n  slot: [ elementalmagic, bloodmagic, head ]\n  gem: orange\n  power: [ 4, 6, 8 ]\nlifebloom:\n  slot: chest\n  gem: orange\n  power: [ 4, 6, 8 ]\ncharm regen:\n  slot: [ cloak, utility, trinket ]\n  gem: orange\n  power: [ 2, 3, 4 ]\n\n# ---------- DIAMOND (white): skill levels ----------\nweapon mastery:\n  slot: [ sword, knife, club, polearm, spear, axe, bow, crossbow, elementalmagic, bloodmagic ]\n  gem: white\n  power: [ 3, 5, 7 ]\ngathering mastery:\n  slot: tool\n  gem: white\n  power: [ 3, 5, 8 ]\nguard mastery:\n  slot: shield\n  gem: white\n  power: [ 3, 5, 7 ]\nbulwark mastery:\n  slot: chest\n  gem: white\n  power: [ 2, 3, 4 ]\narcane mastery:\n  slot: head\n  gem: white\n  power: [ 2, 3, 4 ]\nstride mastery:\n  slot: legs\n  gem: white\n  power: [ 3, 5, 8 ]\nswim mastery:\n  slot: cloak\n  gem: white\n  power: [ 3, 5, 8 ]\nwanderer mastery:\n  slot: utility\n  gem: white\n  power: [ 2, 3, 4 ]\nbattle mastery:\n  slot: trinket\n  gem: white\n  power: [ 1, 2, 3 ]\n";
}
public struct GemPower
{
	[AdditivePower]
	public float Power;
}
public struct GemCare
{
	[AdditivePower]
	public float Repair;

	[AdditivePower]
	public float Wear;
}
internal static class GemRework
{
	internal static class ParryState
	{
		internal static int Charges;

		internal static float Progress;
	}

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<float> OneHandFactor;

	internal static ConfigEntry<float> CapXp;

	internal static ConfigEntry<float> CapLevels;

	internal static ConfigEntry<float> CapAmmo;

	internal static ConfigEntry<float> CapAttackSpeed;

	internal static ConfigEntry<float> CapDrawReload;

	internal static ConfigEntry<float> CapMove;

	internal static ConfigEntry<float> CapRegen;

	internal static ConfigEntry<float> CapReach;

	internal static ConfigEntry<float> CapParryRate;

	internal static ConfigEntry<float> CapMaxStamina;

	internal static ConfigEntry<float> CapMaxEitr;

	internal static ConfigEntry<float> CapMaxHealth;

	private static string state = "not run yet";

	private static bool live;

	internal const string Scholar = "Scholar";

	internal const string WeaponScholar = "Weapon Scholar";

	internal const string GuardScholar = "Guard Scholar";

	internal const string GatherScholar = "Gatherer Scholar";

	internal const string AmmoSaver = "Ammo Saver";

	internal const string LongReach = "Long Reach";

	internal const string ParryCharge = "Parry Charge";

	internal const string SelfRepair = "Self Repair";

	internal const string GearCare = "Gear Care";

	internal const string SwiftStrikes = "Swift Strikes";

	internal const string Swiftness = "Swiftness";

	internal const string SwiftDraw = "Swift Draw";

	internal const string SwiftReload = "Swift Reload";

	internal const string BusyHands = "Busy Hands";

	internal const string FleetFoot = "Fleet Foot";

	internal const string SecondBreath = "Second Breath";

	internal const string MindSpring = "Mind Spring";

	internal const string Lifebloom = "Lifebloom";

	internal const string WeaponMastery = "Weapon Mastery";

	internal const string GatherMastery = "Gathering Mastery";

	internal const string GuardMastery = "Guard Mastery";

	internal const string ArcaneMastery = "Arcane Mastery";

	internal const string StrideMastery = "Stride Mastery";

	internal const string SwimMastery = "Swim Mastery";

	internal const string WandererMastery = "Wanderer Mastery";

	internal const string BattleMastery = "Battle Mastery";

	internal const string CharmScholar = "Charm Scholar";

	internal const string SpareAmmo = "Spare Ammo";

	internal const string CharmAmmo = "Charm Ammo Saver";

	internal const string ShieldParry = "Shield Parry Charge";

	internal const string ToolSelfRepair = "Tool Self Repair";

	internal const string SwiftCasting = "Swift Casting";

	internal const string SwiftLegs = "Swift Legs";

	internal const string CharmFleet = "Charm Fleet Foot";

	internal const string DeepBreath = "Deep Breath";

	internal const string CharmRegen = "Charm Regen";

	internal const string BulwarkMastery = "Bulwark Mastery";

	internal const string WeaponVigor = "Weapon Vigor";

	internal const string StaffFocus = "Staff Focus";

	internal const string ShieldReserves = "Shield Reserves";

	internal const string ArmorReserves = "Armor Reserves";

	private static int cacheFrame = -1;

	private static readonly Dictionary<long, Dictionary<string, float>> cache = new Dictionary<long, Dictionary<string, float>>();

	private static float nextCare;

	private static float baseReach = -1f;

	private static readonly Dictionary<ItemData, float> lastDurability = new Dictionary<ItemData, float>();

	private static readonly float[] RepairOther = new float[3] { 2f, 3f, 5f };

	private static readonly float[] WearOther = new float[3] { 10f, 15f, 20f };

	private static readonly float[] RepairTool = new float[3] { 3f, 5f, 8f };

	private static readonly float[] WearTool = new float[3] { 15f, 20f, 25f };

	internal static string FilePath => Path.Combine(Paths.ConfigPath, "kenny.demigodmod.gems.yml");

	internal static void Bind(ConfigFile cfg)
	{
		Enabled = cfg.Bind<bool>("10 - Gem Rework", "Enabled", true, "Kenny's gem rework (Jewelcrafting). Gem numbers are in BepInEx\\config\\kenny.demigodmod.gems.yml. Off = Jewelcrafting's own gems. Needs a restart.");
		OneHandFactor = cfg.Bind<float>("10 - Gem Rework", "One-handed weapon share", 0.67f, "Weapon-socket gems (Weapon Scholar, Swift Strikes, Weapon Mastery) on a ONE-handed weapon give this share of the listed number (the gem tooltip shows the two-handed number). Kenny: 1H lower because the shield has its own gems.");
		CapXp = cfg.Bind<float>("10 - Gem Rework", "Cap: skill XP %", 100f, "Most extra skill XP from Ruby across all gear, per skill.");
		CapLevels = cfg.Bind<float>("10 - Gem Rework", "Cap: skill levels", 15f, "Most extra skill levels from Diamond across all gear, per skill (skills can go past 100).");
		CapAmmo = cfg.Bind<float>("10 - Gem Rework", "Cap: ammo save %", 50f, "Most chance from Onyx for an arrow or bolt not to be used up.");
		CapAttackSpeed = cfg.Bind<float>("10 - Gem Rework", "Cap: attack/cast speed %", 35f, "Most attack and cast speed from Spinel.");
		CapDrawReload = cfg.Bind<float>("10 - Gem Rework", "Cap: draw/reload speed %", 25f, "Most bow draw / crossbow reload speed from Spinel.");
		CapMove = cfg.Bind<float>("10 - Gem Rework", "Cap: move speed %", 25f, "Most move speed from Emerald.");
		CapRegen = cfg.Bind<float>("10 - Gem Rework", "Cap: regen % (each)", 50f, "Most stamina, eitr and health regen from Humite, each.");
		CapReach = cfg.Bind<float>("10 - Gem Rework", "Cap: pickup range (m)", 4f, "Most extra item pickup range from Onyx in tools.");
		CapMaxStamina = cfg.Bind<float>("10 - Gem Rework", "Cap: max stamina", 60f, "Most extra max stamina from Onyx (weapons, shield, armor).");
		CapMaxEitr = cfg.Bind<float>("10 - Gem Rework", "Cap: max eitr", 60f, "Most extra max eitr from Onyx (staffs, shield, armor).");
		CapMaxHealth = cfg.Bind<float>("10 - Gem Rework", "Cap: max health", 8f, "Most extra max health from Onyx (shield).");
		CapParryRate = cfg.Bind<float>("10 - Gem Rework", "Cap: parry charge speed %", 200f, "Sapphire: 100% = one parry charge a minute. This caps how fast charges come back (200% = one every 30 s). Max 3 stored charges.");
	}

	internal static void Init(Harmony h)
	{
		if (!Enabled.Value)
		{
			state = "off";
			return;
		}
		try
		{
			if (!API.IsLoaded())
			{
				state = "off (Jewelcrafting not loaded)";
				return;
			}
			RegisterEffects();
			EnsureFile();
			API.AddGemConfig(File.ReadAllText(FilePath));
			Patch(h);
			AnimSpeed.Hook();
			live = true;
			state = "on";
			Plugin.Log.LogInfo((object)("Gem Rework ready: our gem effects registered, gem config loaded from " + FilePath + (AnimSpeed.Ok ? "" : " (attack speed part OFF: AnimationSpeedManager not found)")));
		}
		catch (Exception ex)
		{
			live = false;
			state = "off (error, see log)";
			Plugin.Log.LogError((object)("Gem Rework failed, Jewelcrafting gems left as they were: " + ex));
		}
	}

	internal static string Status()
	{
		if (!live)
		{
			return state;
		}
		return state + ((ParryState.Charges > 0 || Power(Player.m_localPlayer, "Parry Charge") + Power(Player.m_localPlayer, "Shield Parry Charge") > 0f) ? (", parry charges " + ParryState.Charges + "/3") : "");
	}

	private static void RegisterEffects()
	{
		API.AddGemEffect<GemPower>("Scholar", "More skill XP.", "+$1% XP for all skills.");
		API.AddGemEffect<GemPower>("Weapon Scholar", "More XP for this weapon's skill.", "+$1% XP for the skill of the weapon you hold (bow, crossbow and staff count). (One-handed weapons get 2/3.)");
		API.AddGemEffect<GemPower>("Guard Scholar", "More Blocking XP.", "+$1% Blocking XP.");
		API.AddGemEffect<GemPower>("Gatherer Scholar", "More gathering XP.", "+$1% Pickaxes, Woodcutting and Farming XP.");
		API.AddGemEffect<GemPower>("Ammo Saver", "Ammo is sometimes not used up.", "$1% chance that a shot doesn't use an arrow or bolt.");
		API.AddGemEffect<GemPower>("Long Reach", "Bigger pickup range.", "Pick up items from +$1 m further away.");
		API.AddGemEffect<GemPower>("Parry Charge", "Stores perfect blocks.", "Gain a parry charge every 60 s at 100% (this gem: $1% speed). While blocking, a charge turns a block into a perfect block. Max 3.");
		API.AddGemEffect<GemCare>("Self Repair", "This item repairs itself.", "This item repairs $1% durability per minute and wears out $2% slower.");
		API.AddGemEffect<GemCare>("Gear Care", "Your gear repairs itself.", "All worn gear repairs $1% durability per minute and wears out $2% slower.");
		API.AddGemEffect<GemPower>("Swift Strikes", "Faster attacks with this weapon.", "+$1% attack speed with this weapon. (One-handed weapons get 2/3.)");
		API.AddGemEffect<GemPower>("Swiftness", "Faster attacks.", "+$1% attack and cast speed.");
		API.AddGemEffect<GemPower>("Swift Draw", "Faster bow draw.", "Bows draw $1% faster.");
		API.AddGemEffect<GemPower>("Swift Reload", "Faster crossbow reload.", "Crossbows reload $1% faster.");
		API.AddGemEffect<GemPower>("Busy Hands", "Faster tool use.", "Pickaxes, hammers and other tools swing $1% faster.");
		API.AddGemEffect<GemPower>("Fleet Foot", "Faster movement.", "+$1% movement speed.");
		API.AddGemEffect<GemPower>("Second Breath", "Faster stamina regen.", "+$1% stamina regeneration.");
		API.AddGemEffect<GemPower>("Mind Spring", "Faster eitr regen.", "+$1% eitr regeneration.");
		API.AddGemEffect<GemPower>("Lifebloom", "Faster health regen.", "+$1% health regeneration.");
		API.AddGemEffect<GemPower>("Weapon Mastery", "Skill levels for this weapon.", "+$1 levels to the skill of the weapon you hold (can go past 100). (One-handed weapons get 2/3.)");
		API.AddGemEffect<GemPower>("Gathering Mastery", "Gathering skill levels.", "+$1 Pickaxes, Woodcutting, Farming and Cooking levels.");
		API.AddGemEffect<GemPower>("Guard Mastery", "Blocking levels.", "+$1 Blocking levels.");
		API.AddGemEffect<GemPower>("Arcane Mastery", "Magic skill levels.", "+$1 Elemental Magic and Blood Magic levels.");
		API.AddGemEffect<GemPower>("Stride Mastery", "Run and jump levels.", "+$1 Run and Jump levels.");
		API.AddGemEffect<GemPower>("Swim Mastery", "Swim levels.", "+$1 Swim levels.");
		API.AddGemEffect<GemPower>("Wanderer Mastery", "Travel skill levels.", "+$1 Run, Jump, Swim and Sneak levels.");
		API.AddGemEffect<GemPower>("Battle Mastery", "Weapon skill levels.", "+$1 levels to every weapon and magic skill.");
		API.AddGemEffect<GemPower>("Charm Scholar", "More skill XP.", "+$1% XP for all skills.");
		API.AddGemEffect<GemPower>("Spare Ammo", "Ammo is sometimes not used up.", "$1% chance that a shot from any bow or crossbow doesn't use an arrow or bolt.");
		API.AddGemEffect<GemPower>("Charm Ammo Saver", "Ammo is sometimes not used up.", "$1% chance that a shot from any bow or crossbow doesn't use an arrow or bolt.");
		API.AddGemEffect<GemPower>("Shield Parry Charge", "Stores perfect blocks.", "Gain a parry charge every 60 s at 100% (this gem: $1% speed). While blocking, a charge turns a block into a perfect block. Max 3.");
		API.AddGemEffect<GemCare>("Tool Self Repair", "This tool repairs itself.", "This tool repairs $1% durability per minute and wears out $2% slower.");
		API.AddGemEffect<GemPower>("Swift Casting", "Faster casting with this staff.", "+$1% cast speed with this staff.");
		API.AddGemEffect<GemPower>("Swift Legs", "Faster movement.", "+$1% movement speed.");
		API.AddGemEffect<GemPower>("Charm Fleet Foot", "Faster movement.", "+$1% movement speed.");
		API.AddGemEffect<GemPower>("Deep Breath", "Faster stamina regen.", "+$1% stamina regeneration.");
		API.AddGemEffect<GemPower>("Charm Regen", "Faster regen.", "+$1% stamina, eitr and health regeneration.");
		API.AddGemEffect<GemPower>("Bulwark Mastery", "Blocking levels.", "+$1 Blocking levels.");
		API.AddGemEffect<GemPower>("Weapon Vigor", "More stamina.", "+$1 max stamina while you hold this weapon. (One-handed weapons get 2/3.)");
		API.AddGemEffect<GemPower>("Staff Focus", "More eitr.", "+$1 max eitr.");
		API.AddGemEffect<GemPower>("Shield Reserves", "Bigger pools.", "+$1 max health, stamina and eitr.");
		API.AddGemEffect<GemPower>("Armor Reserves", "More stamina and eitr.", "+$1 max stamina and eitr.");
	}

	internal static float Power(Player p, string effect)
	{
		if (!live || (Object)(object)p == (Object)null)
		{
			return 0f;
		}
		if (Time.frameCount != cacheFrame)
		{
			cache.Clear();
			ca