Decompiled source of MagicHammer v1.0.1

BepInEx/plugins/MagicHammer/MagicHammer.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace MagicHammer;

internal sealed class Glow : IDisposable
{
	private static readonly FieldInfo Equipment = AccessTools.Field(typeof(Humanoid), "m_visEquipment");

	private static readonly FieldInfo RightVisual = AccessTools.Field(typeof(VisEquipment), "m_rightItemInstance");

	private readonly Dictionary<Renderer, Material[]> originals = new Dictionary<Renderer, Material[]>();

	private readonly List<Material> copies = new List<Material>();

	private GameObject lightObject;

	internal GameObject Target { get; private set; }

	internal static GameObject HeldVisual(Player player)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Expected O, but got Unknown
		object value = Equipment.GetValue(player);
		if (value != null)
		{
			return (GameObject)RightVisual.GetValue(value);
		}
		return null;
	}

	internal Glow(GameObject target, Plugin plugin)
	{
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Expected O, but got Unknown
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Expected O, but got Unknown
		//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
		Target = target;
		try
		{
			Color val = default(Color);
			if (!ColorUtility.TryParseHtmlString(plugin.GlowColour.Value, ref val))
			{
				val = new Color(0.4f, 0.8f, 1f);
			}
			if (plugin.GlowIntensity.Value > 0f)
			{
				lightObject = new GameObject("MagicHammer_Glow");
				((Object)lightObject).hideFlags = (HideFlags)52;
				lightObject.transform.SetParent(target.transform, false);
				lightObject.transform.localPosition = new Vector3(0f, 0.15f, 0f);
				Light val2 = lightObject.AddComponent<Light>();
				val2.type = (LightType)2;
				val2.color = val;
				val2.range = plugin.GlowRange.Value;
				val2.intensity = plugin.GlowIntensity.Value;
				val2.shadows = (LightShadows)0;
			}
			if (plugin.Emission.Value <= 0f)
			{
				return;
			}
			Renderer[] componentsInChildren = target.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val3 in componentsInChildren)
			{
				Material[] sharedMaterials = val3.sharedMaterials;
				Material[] array = (Material[])sharedMaterials.Clone();
				bool flag = false;
				for (int j = 0; j < sharedMaterials.Length; j++)
				{
					Material val4 = sharedMaterials[j];
					if (!((Object)(object)val4 == (Object)null) && (val4.HasProperty("_EmissionColor") || val4.HasProperty("_EmissiveColor")))
					{
						Material val5 = new Material(val4);
						copies.Add(val5);
						((Object)val5).hideFlags = (HideFlags)52;
						val5.EnableKeyword("_EMISSION");
						if (val5.HasProperty("_EmissionColor"))
						{
							val5.SetColor("_EmissionColor", val * plugin.Emission.Value);
						}
						if (val5.HasProperty("_EmissiveColor"))
						{
							val5.SetColor("_EmissiveColor", val * plugin.Emission.Value);
						}
						array[j] = val5;
						flag = true;
					}
				}
				if (flag)
				{
					originals[val3] = sharedMaterials;
					val3.sharedMaterials = array;
				}
			}
		}
		catch
		{
			Dispose();
			throw;
		}
	}

	public void Dispose()
	{
		if ((Object)(object)lightObject != (Object)null)
		{
			Object.Destroy((Object)(object)lightObject);
		}
		foreach (KeyValuePair<Renderer, Material[]> original in originals)
		{
			if ((Object)(object)original.Key != (Object)null)
			{
				original.Key.sharedMaterials = original.Value;
			}
		}
		foreach (Material copy in copies)
		{
			if ((Object)(object)copy != (Object)null)
			{
				Object.Destroy((Object)(object)copy);
			}
		}
		originals.Clear();
		copies.Clear();
	}
}
internal static class Hooks
{
	internal static readonly MethodInfo CountOriginal = AccessTools.Method(typeof(Inventory), "CountItems", new Type[3]
	{
		typeof(string),
		typeof(int),
		typeof(bool)
	}, (Type[])null);

	internal static readonly MethodInfo HaveOriginal = AccessTools.Method(typeof(Inventory), "HaveItem", new Type[2]
	{
		typeof(string),
		typeof(bool)
	}, (Type[])null);

	internal static Func<Inventory, string, int, bool, bool, int> CraftUiCount;

	internal static int Count(Inventory inventory, string name, int quality, bool worldLevel)
	{
		if (!((Object)(object)Plugin.Instance == (Object)null))
		{
			return Plugin.Instance.Count(inventory, name, quality, worldLevel);
		}
		return inventory.CountItems(name, quality, worldLevel);
	}

	internal static bool Have(Inventory inventory, string name, bool worldLevel)
	{
		return Count(inventory, name, -1, worldLevel) > 0;
	}

	internal static int UiCount(Inventory inventory, string name, int quality, bool worldLevel, bool craft)
	{
		if (!craft)
		{
			return Count(inventory, name, quality, worldLevel);
		}
		return inventory.CountItems(name, quality, worldLevel);
	}

	internal static int CombinedUiCount(Inventory inventory, string name, int quality, bool worldLevel, bool craft)
	{
		if (!craft)
		{
			return Count(inventory, name, quality, worldLevel);
		}
		return CraftUiCount(inventory, name, quality, worldLevel, arg5: true);
	}

	internal static bool Build(Player player, Piece piece)
	{
		if (!((Object)(object)Plugin.Instance == (Object)null))
		{
			return Plugin.Instance.Build(player, piece);
		}
		return player.TryPlacePiece(piece);
	}

	internal static void Consume(Player player, Requirement[] requirements, int quality, int itemQuality, int multiplier)
	{
		if ((Object)(object)Plugin.Instance == (Object)null)
		{
			player.ConsumeResources(requirements, quality, itemQuality, multiplier);
		}
		else
		{
			Plugin.Instance.ConsumePaid(player, requirements, quality, itemQuality, multiplier);
		}
	}

	internal static bool Calls(CodeInstruction instruction, MethodInfo method)
	{
		if (instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt)
		{
			return object.Equals(instruction.operand, method);
		}
		return false;
	}
}
[HarmonyPatch]
internal static class RequirementsPatch
{
	private static MethodBase TargetMethod()
	{
		return AccessTools.Method(typeof(Player), "HaveRequirements", new Type[2]
		{
			typeof(Piece),
			typeof(RequirementMode)
		}, (Type[])null);
	}

	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		List<CodeInstruction> list = instructions.ToList();
		int num = 0;
		int num2 = 0;
		foreach (CodeInstruction item in list)
		{
			if (Hooks.Calls(item, Hooks.CountOriginal))
			{
				item.opcode = OpCodes.Call;
				item.operand = AccessTools.Method(typeof(Hooks), "Count", (Type[])null, (Type[])null);
				num++;
			}
			else if (Hooks.Calls(item, Hooks.HaveOriginal))
			{
				item.opcode = OpCodes.Call;
				item.operand = AccessTools.Method(typeof(Hooks), "Have", (Type[])null, (Type[])null);
				num2++;
			}
		}
		if (num != 1 || num2 != 1)
		{
			throw new InvalidOperationException("Unsupported HaveRequirements IL layout.");
		}
		return list;
	}
}
[HarmonyPatch]
internal static class PlacementPatch
{
	private static MethodBase TargetMethod()
	{
		return AccessTools.Method(typeof(Player), "UpdatePlacement", new Type[2]
		{
			typeof(bool),
			typeof(float)
		}, (Type[])null);
	}

	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		List<CodeInstruction> list = instructions.ToList();
		MethodInfo method = AccessTools.Method(typeof(Player), "TryPlacePiece", new Type[1] { typeof(Piece) }, (Type[])null);
		MethodInfo method2 = AccessTools.Method(typeof(Player), "ConsumeResources", new Type[4]
		{
			typeof(Requirement[]),
			typeof(int),
			typeof(int),
			typeof(int)
		}, (Type[])null);
		int num = 0;
		int num2 = 0;
		foreach (CodeInstruction item in list)
		{
			if (Hooks.Calls(item, method))
			{
				item.opcode = OpCodes.Call;
				item.operand = AccessTools.Method(typeof(Hooks), "Build", (Type[])null, (Type[])null);
				num++;
			}
			else if (Hooks.Calls(item, method2))
			{
				item.opcode = OpCodes.Call;
				item.operand = AccessTools.Method(typeof(Hooks), "Consume", (Type[])null, (Type[])null);
				num2++;
			}
		}
		if (num != 1 || num2 != 1)
		{
			throw new InvalidOperationException("Unsupported UpdatePlacement IL layout.");
		}
		return list;
	}
}
[HarmonyAfter(new string[] { "au.tyvalheim.reliablecrafting" })]
[HarmonyPatch]
internal static class RequirementUiPatch
{
	private static MethodBase TargetMethod()
	{
		return AccessTools.Method(typeof(InventoryGui), "SetupRequirement", (Type[])null, (Type[])null);
	}

	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>();
		int num = 0;
		foreach (CodeInstruction instruction in instructions)
		{
			MethodInfo methodInfo = instruction.operand as MethodInfo;
			if (methodInfo != null && methodInfo.DeclaringType.FullName == "ReliableCrafting.Hooks" && methodInfo.Name == "UiCount")
			{
				Hooks.CraftUiCount = (Func<Inventory, string, int, bool, bool, int>)Delegate.CreateDelegate(typeof(Func<Inventory, string, int, bool, bool, int>), methodInfo);
				instruction.opcode = OpCodes.Call;
				instruction.operand = AccessTools.Method(typeof(Hooks), "CombinedUiCount", (Type[])null, (Type[])null);
				num++;
			}
			else if (Hooks.Calls(instruction, Hooks.CountOriginal))
			{
				CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_3, (object)null);
				val.labels.AddRange(instruction.labels);
				val.blocks.AddRange(instruction.blocks);
				instruction.labels.Clear();
				instruction.blocks.Clear();
				list.Add(val);
				instruction.opcode = OpCodes.Call;
				instruction.operand = AccessTools.Method(typeof(Hooks), "UiCount", (Type[])null, (Type[])null);
				num++;
			}
			list.Add(instruction);
		}
		if (num != 1)
		{
			throw new InvalidOperationException("Unsupported build requirement UI IL layout.");
		}
		return list;
	}
}
[HarmonyPatch(typeof(Inventory), "ItemCheated", new Type[]
{
	typeof(Requirement[]),
	typeof(int),
	typeof(bool)
})]
internal static class CheatedPatch
{
	private static void Postfix(Inventory __instance, ref bool __result)
	{
		if ((Object)(object)Plugin.Instance != (Object)null && Plugin.Instance.ReservedCheated(__instance))
		{
			__result = true;
		}
	}
}
[HarmonyPatch(typeof(Player), "PlacePiece", new Type[]
{
	typeof(Piece),
	typeof(Vector3),
	typeof(Quaternion),
	typeof(bool),
	typeof(bool)
})]
internal static class SpawnBoundaryPatch
{
	private static void Prefix(Player __instance)
	{
		if ((Object)(object)Plugin.Instance != (Object)null)
		{
			Plugin.Instance.PlacementEntered(__instance);
		}
	}
}
internal sealed class ReferenceSnapshot<T> where T : class
{
	private readonly T[] items;

	private readonly int[] counts;

	internal ReferenceSnapshot(IEnumerable<T> source, Func<T, int> getCount)
	{
		items = source.ToArray();
		counts = items.Select(getCount).ToArray();
	}

	internal void Restore(IList<T> destination, Action<T, int> setCount)
	{
		for (int i = 0; i < items.Length; i++)
		{
			setCount(items[i], counts[i]);
		}
		destination.Clear();
		T[] array = items;
		foreach (T item in array)
		{
			destination.Add(item);
		}
	}
}
internal sealed class Need
{
	internal string Key;

	internal int Amount;
}
internal sealed class Stock
{
	internal string Key;

	internal int Available;

	internal object Tag;
}
internal sealed class Withdrawal
{
	internal Stock Stock;

	internal int Amount;
}
internal static class Planning
{
	internal static List<Withdrawal> Create(IEnumerable<Need> needs, IList<Stock> stocks)
	{
		List<Withdrawal> list = new List<Withdrawal>();
		foreach (IGrouping<string, Need> group in from n in needs
			where n.Amount > 0
			group n by n.Key)
		{
			int num = group.Sum((Need n) => n.Amount);
			foreach (Stock item in stocks.Where((Stock s) => s.Key == group.Key))
			{
				int num2 = Math.Min(num, Math.Max(0, item.Available));
				if (num2 > 0)
				{
					list.Add(new Withdrawal
					{
						Stock = item,
						Amount = num2
					});
				}
				num -= num2;
				if (num == 0)
				{
					break;
				}
			}
			if (num > 0)
			{
				return null;
			}
		}
		return list;
	}

	internal static HashSet<string> ParseList(string text)
	{
		return new HashSet<string>(from s in text.Split(new char[1] { ',' })
			select s.Trim() into s
			where s.Length > 0
			select s, StringComparer.OrdinalIgnoreCase);
	}

	internal static Dictionary<string, string> ReadSection(IEnumerable<string> lines, string section)
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		bool flag = false;
		foreach (string line in lines)
		{
			string text = line.Trim();
			if (text.Length == 0 || text.StartsWith("#") || text.StartsWith(";"))
			{
				continue;
			}
			if (text.StartsWith("[") && text.EndsWith("]"))
			{
				flag = string.Equals(text.Substring(1, text.Length - 2).Trim(), section, StringComparison.OrdinalIgnoreCase);
				continue;
			}
			int num = text.IndexOf('=');
			if (flag && num > 0)
			{
				dictionary[text.Substring(0, num).Trim()] = text.Substring(num + 1).Split('#', ';')[0].Trim();
			}
		}
		return dictionary;
	}
}
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("au.tyvalheim.magichammer", "Magic Hammer", "1.0.1")]
public sealed class Plugin : BaseUnityPlugin
{
	private sealed class Source
	{
		internal Inventory Inventory;

		internal Container Chest;

		internal ItemData Item;
	}

	private sealed class Receipt
	{
		internal List<Withdrawal> Plan;

		internal Dictionary<Inventory, ReferenceSnapshot<ItemData>> Backups = new Dictionary<Inventory, ReferenceSnapshot<ItemData>>();

		internal HashSet<Inventory> Touched = new HashSet<Inventory>();

		internal bool Cheated;

		internal bool PlacementEntered;
	}

	public const string Id = "au.tyvalheim.magichammer";

	internal static Plugin Instance;

	internal ConfigEntry<bool> Enabled;

	internal ConfigEntry<bool> GlowEnabled;

	internal ConfigEntry<bool> LogUse;

	internal ConfigEntry<float> Radius;

	internal ConfigEntry<float> GlowIntensity;

	internal ConfigEntry<float> GlowRange;

	internal ConfigEntry<float> Emission;

	internal ConfigEntry<string> GlowColour;

	private ConfigEntry<bool> inventoryFirst;

	private ConfigEntry<bool> privateChests;

	private ConfigEntry<bool> reload;

	private ConfigEntry<bool> catalogue;

	private ConfigEntry<bool> messages;

	private ConfigEntry<float> refresh;

	private ConfigEntry<int> minimumStock;

	private ConfigEntry<string> chestPrefabs;

	private ConfigEntry<string> prohibitedList;

	private HashSet<string> allowedChests;

	private HashSet<string> prohibited;

	private Dictionary<string, int> reserves = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

	private Container[] cachedChests = (Container[])(object)new Container[0];

	private List<Stock> cachedStocks;

	private int stockFrame = -1;

	private float nextChestScan;

	private float nextFileCheck;

	private DateTime configStamp;

	private ObjectDB cataloguedDb;

	private Harmony harmony;

	private bool faulted;

	private bool glowFaulted;

	private Glow glow;

	private Receipt activeReceipt;

	private Requirement[] paidResources;

	private Player paidPlayer;

	private int paidFrame;

	private MethodInfo requestChestAccess;

	private bool accessPending;

	private static readonly MethodInfo LoadChest = AccessTools.Method(typeof(Container), "Load", Type.EmptyTypes, (Type[])null);

	private static readonly MethodInfo ChestAccess = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null);

	private static readonly FieldInfo NoPlacementCost = AccessTools.Field(typeof(Player), "m_noPlacementCost");

	private static readonly MethodInfo InventoryChanged = AccessTools.Method(typeof(Inventory), "Changed", new Type[2]
	{
		typeof(bool),
		typeof(bool)
	}, (Type[])null);

	private void Awake()
	{
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Expected O, but got Unknown
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Expected O, but got Unknown
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0129: Expected O, but got Unknown
		//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f4: Expected O, but got Unknown
		//IL_0229: Unknown result type (might be due to invalid IL or missing references)
		//IL_0233: Expected O, but got Unknown
		//IL_0268: Unknown result type (might be due to invalid IL or missing references)
		//IL_0272: Expected O, but got Unknown
		//IL_0374: Unknown result type (might be due to invalid IL or missing references)
		//IL_037e: Expected O, but got Unknown
		Instance = this;
		((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable chest-assisted hammer building.");
		Radius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ChestRadiusMeters", 20f, new ConfigDescription("3D chest radius around the player, approximately a vanilla workbench build radius.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), new object[0]));
		refresh = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ChestDiscoveryIntervalSeconds", 1f, new ConfigDescription("Discovery cache interval; access and stock are checked again before payment.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), new object[0]));
		inventoryFirst = ((BaseUnityPlugin)this).Config.Bind<bool>("Materials", "UsePlayerInventoryFirst", true, "Use carried materials before nearest chests. False prefers chests.");
		prohibitedList = ((BaseUnityPlugin)this).Config.Bind<string>("Materials", "Prohibited", "", "Additional comma-separated prefab names forbidden from CHESTS; carried materials remain usable.");
		minimumStock = ((BaseUnityPlugin)this).Config.Bind<int>("Materials", "MinimumStockPerChest", 0, new ConfigDescription("Leave this many of each material in EACH chest. ReserveAmounts entries override this per material.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), new object[0]));
		chestPrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("Chests", "AllowedPrefabs", "piece_chest_wood,piece_chest,piece_chest_blackmetal,piece_chest_private", "Only listed player-built Container prefabs are eligible. Remove a name to exclude that chest type.");
		privateChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Chests", "IncludePersonalChests", false, "Allow personal chests only if the local player has vanilla access. Ward checks always apply.");
		GlowEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Glow", "Enabled", true, "Make the locally held hammer glow. Cosmetic, client-only.");
		GlowColour = ((BaseUnityPlugin)this).Config.Bind<string>("Glow", "Colour", "#66CCFF", "Glow colour as an HTML hex colour.");
		GlowIntensity = ((BaseUnityPlugin)this).Config.Bind<float>("Glow", "LightIntensity", 1.2f, new ConfigDescription("Point-light intensity; zero disables the light.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), new object[0]));
		GlowRange = ((BaseUnityPlugin)this).Config.Bind<float>("Glow", "LightRangeMeters", 3f, new ConfigDescription("Shadowless light radius.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), new object[0]));
		Emission = ((BaseUnityPlugin)this).Config.Bind<float>("Glow", "EmissionIntensity", 1.5f, new ConfigDescription("Hammer material emission where the shader supports it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 8f), new object[0]));
		reload = ((BaseUnityPlugin)this).Config.Bind<bool>("Configuration", "ReloadOnFileChange", true, "Re-read settings within two seconds after saving this file.");
		catalogue = ((BaseUnityPlugin)this).Config.Bind<bool>("Configuration", "AppendDiscoveredMaterials", true, "Append missing commented material examples when a world is loaded, including modded materials.");
		messages = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "ShowBuildMessages", true, "Show a short message after using chest materials or if stock changed.");
		LogUse = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LogChestConsumption", false, "Log chest material quantities after successful builds.");
		try
		{
			if (LoadChest == null || ChestAccess == null || NoPlacementCost == null || InventoryChanged == null)
			{
				throw new MissingMemberException("Required Valheim inventory/build API was not found.");
			}
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				WriteInitialConfig();
			}
			ReadMaterialRules();
			configStamp = File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath);
			harmony = new Harmony("au.tyvalheim.magichammer");
			harmony.PatchAll(typeof(Plugin).Assembly);
			Type type = AccessTools.TypeByName("ReliableCrafting.Plugin");
			if (type != null)
			{
				requestChestAccess = AccessTools.Method(type, "RequestChestAccess", new Type[3]
				{
					typeof(Container),
					typeof(Player),
					typeof(float)
				}, (Type[])null);
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Magic Hammer 1.0.1 loaded. Read-only remote stock previews; optional Reliable Crafting chest-access handshake.");
		}
		catch (Exception ex)
		{
			Fault(ex);
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}

	private void WriteInitialConfig()
	{
		((BaseUnityPlugin)this).Config.Save();
		File.AppendAllText(((BaseUnityPlugin)this).Config.ConfigFilePath, "\n[ReserveAmounts]\n# Wood = 50\n# Stone = 50\n\n[ProhibitedMaterials]\n# Uncomment a prefab to prohibit chest withdrawal. true = prohibited.\n# FineWood = true\n# RoundLog = true\n");
	}

	private void ReadMaterialRules()
	{
		string[] lines = File.ReadAllLines(((BaseUnityPlugin)this).Config.ConfigFilePath);
		HashSet<string> hashSet = Planning.ParseList(prohibitedList.Value);
		foreach (KeyValuePair<string, string> item in Planning.ReadSection(lines, "ProhibitedMaterials"))
		{
			if (!bool.TryParse(item.Value, out var result))
			{
				throw new FormatException("Invalid ProhibitedMaterials entry: " + item.Key);
			}
			if (result)
			{
				hashSet.Add(item.Key);
			}
		}
		Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
		foreach (KeyValuePair<string, string> item2 in Planning.ReadSection(lines, "ReserveAmounts"))
		{
			if (!int.TryParse(item2.Value, out var result2) || result2 < 0)
			{
				throw new FormatException("Invalid ReserveAmounts entry: " + item2.Key);
			}
			dictionary[item2.Key] = result2;
		}
		prohibited = hashSet;
		reserves = dictionary;
		allowedChests = Planning.ParseList(chestPrefabs.Value);
		nextChestScan = 0f;
		stockFrame = -1;
	}

	private void Update()
	{
		if (faulted)
		{
			return;
		}
		try
		{
			if (reload.Value && Time.unscaledTime >= nextFileCheck)
			{
				nextFileCheck = Time.unscaledTime + 2f;
				DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath);
				if (lastWriteTimeUtc != configStamp)
				{
					((BaseUnityPlugin)this).Config.Reload();
					ReadMaterialRules();
					configStamp = lastWriteTimeUtc;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Magic Hammer configuration reloaded.");
					DisposeGlow();
				}
			}
			if (catalogue.Value && (Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ZNetScene.instance != (Object)null && (Object)(object)cataloguedDb != (Object)(object)ObjectDB.instance)
			{
				AppendMaterialCatalogue();
				cataloguedDb = ObjectDB.instance;
			}
		}
		catch (Exception ex)
		{
			Fault(ex);
		}
		if (glowFaulted)
		{
			return;
		}
		try
		{
			UpdateGlow();
		}
		catch (Exception ex2)
		{
			glowFaulted = true;
			DisposeGlow();
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Hammer glow disabled: " + ex2));
		}
	}

	internal static string Prefab(Object obj)
	{
		if (!(obj == (Object)null))
		{
			return obj.name.Replace("(Clone)", "").Trim();
		}
		return "";
	}

	internal bool CanUse(Player player)
	{
		if (faulted || !Enabled.Value || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
		{
			return false;
		}
		ItemData rightItem = ((Humanoid)player).RightItem;
		if (rightItem != null)
		{
			return string.Equals(Prefab((Object)(object)rightItem.m_dropPrefab), "Hammer", StringComparison.OrdinalIgnoreCase);
		}
		return false;
	}

	private bool Usable(Container chest, Player player, bool requireOwner = false)
	{
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: Invalid comparison between Unknown and I4
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)chest == (Object)null || !allowedChests.Contains(Prefab((Object)(object)((Component)chest).gameObject)))
		{
			return false;
		}
		Piece component = ((Component)chest).GetComponent<Piece>();
		ZNetView component2 = ((Component)chest).GetComponent<ZNetView>();
		if ((Object)(object)component == (Object)null || !component.IsPlacedByPlayer() || (Object)(object)component2 == (Object)null || !component2.IsValid() || (requireOwner && !component2.IsOwner()))
		{
			return false;
		}
		Vector3 val = ((Component)chest).transform.position - ((Component)player).transform.position;
		if (((Vector3)(ref val)).sqrMagnitude > Radius.Value * Radius.Value)
		{
			return false;
		}
		if (chest.IsInUse() || component2.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0)
		{
			return false;
		}
		if (!privateChests.Value && (int)chest.m_privacy != 2)
		{
			return false;
		}
		if ((bool)ChestAccess.Invoke(chest, new object[1] { player.GetPlayerID() }))
		{
			return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, true);
		}
		return false;
	}

	private Container[] Chests(Player player, bool force)
	{
		if (force || Time.unscaledTime >= nextChestScan)
		{
			cachedChests = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			nextChestScan = Time.unscaledTime + refresh.Value;
		}
		Container[] array = cachedChests.Where((Container c) => Usable(c, player)).OrderBy(delegate(Container c)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = ((Component)c).transform.position - ((Component)player).transform.position;
			return ((Vector3)(ref val)).sqrMagnitude;
		}).ThenBy((Container c) => ((Object)c).GetInstanceID())
			.ToArray();
		Container[] array2 = array;
		foreach (Container obj in array2)
		{
			LoadChest.Invoke(obj, null);
		}
		return array;
	}

	private List<Stock> Stocks(Player player, bool force)
	{
		if (!force && stockFrame == Time.frameCount && cachedStocks != null)
		{
			return cachedStocks;
		}
		List<Stock> list = new List<Stock>();
		foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
		{
			if (allItem.m_stack > 0 && allItem.m_worldLevel >= Game.m_worldLevel)
			{
				list.Add(new Stock
				{
					Key = allItem.m_shared.m_name,
					Available = allItem.m_stack,
					Tag = new Source
					{
						Inventory = ((Humanoid)player).GetInventory(),
						Item = allItem
					}
				});
			}
		}
		List<Stock> list2 = new List<Stock>();
		Container[] array = Chests(player, force);
		foreach (Container val in array)
		{
			foreach (IGrouping<string, ItemData> item in (from val2 in val.GetInventory().GetAllItems()
				where val2.m_stack > 0 && val2.m_worldLevel >= Game.m_worldLevel && (Object)(object)val2.m_dropPrefab != (Object)null
				select val2).GroupBy<ItemData, string>((ItemData val2) => Prefab((Object)(object)val2.m_dropPrefab), StringComparer.OrdinalIgnoreCase))
			{
				if (prohibited.Contains(item.Key))
				{
					continue;
				}
				if (!reserves.TryGetValue(item.Key, out var value))
				{
					value = minimumStock.Value;
				}
				int num = Math.Max(0, item.Sum((ItemData val2) => val2.m_stack) - value);
				foreach (ItemData item2 in item)
				{
					int num2 = Math.Min(num, item2.m_stack);
					if (num2 > 0)
					{
						list2.Add(new Stock
						{
							Key = item2.m_shared.m_name,
							Available = num2,
							Tag = new Source
							{
								Inventory = val.GetInventory(),
								Chest = val,
								Item = item2
							}
						});
					}
					num -= num2;
				}
			}
		}
		cachedStocks = (inventoryFirst.Value ? list.Concat(list2).ToList() : list2.Concat(list).ToList());
		stockFrame = Time.frameCount;
		return cachedStocks;
	}

	internal int Count(Inventory inventory, string name, int quality, bool worldLevel)
	{
		int num = inventory.CountItems(name, quality, worldLevel);
		Player localPlayer = Player.m_localPlayer;
		if (!CanUse(localPlayer) || inventory != ((Humanoid)localPlayer).GetInventory())
		{
			return num;
		}
		try
		{
			int num2 = (from s in Stocks(localPlayer, force: false)
				where (Object)(object)((Source)s.Tag).Chest != (Object)null && s.Key == name && (quality < 0 || ((Source)s.Tag).Item.m_quality == quality)
				select s).Sum((Stock s) => s.Available);
			return checked(num + num2);
		}
		catch (Exception ex)
		{
			Fault(ex);
			return num;
		}
	}

	private Receipt Reserve(Player player, Piece piece)
	{
		IEnumerable<Need> needs = from r in piece.m_resources
			where (Object)(object)r.m_resItem != (Object)null && r.GetAmount(0) > 0
			select new Need
			{
				Key = r.m_resItem.m_itemData.m_shared.m_name,
				Amount = r.GetAmount(0)
			};
		List<Withdrawal> list = Planning.Create(needs, Stocks(player, force: true));
		if (list == null)
		{
			return null;
		}
		foreach (Source item in from w in list
			select (Source)w.Stock.Tag into s
			where (Object)(object)s.Chest != (Object)null
			group s by s.Chest into g
			select g.First())
		{
			if (!((requestChestAccess != null) ? ((bool)requestChestAccess.Invoke(null, new object[3] { item.Chest, player, Radius.Value })) : Usable(item.Chest, player, requireOwner: true)))
			{
				accessPending = true;
				if (requestChestAccess == null)
				{
					Tell(player, "Magic Hammer: materials found, but chest access is needed. Open/close it, or enable Reliable Crafting for remote access.");
				}
				return null;
			}
		}
		Receipt receipt = new Receipt();
		receipt.Plan = list;
		receipt.Cheated = list.Any((Withdrawal w) => ((Source)w.Stock.Tag).Item.m_cheated);
		Receipt receipt2 = receipt;
		foreach (Inventory item2 in list.Select((Withdrawal w) => ((Source)w.Stock.Tag).Inventory).Distinct())
		{
			receipt2.Backups.Add(item2, new ReferenceSnapshot<ItemData>(item2.GetAllItems(), (ItemData i) => i.m_stack));
		}
		try
		{
			foreach (Withdrawal item3 in list)
			{
				Source source = (Source)item3.Stock.Tag;
				if ((Object)(object)source.Chest != (Object)null && !Usable(source.Chest, player, requireOwner: true))
				{
					throw new InvalidOperationException("Chest access changed during reservation.");
				}
				if (!source.Inventory.GetAllItems().Contains(source.Item) || source.Item.m_stack < item3.Amount)
				{
					throw new InvalidOperationException("Material stock changed during reservation.");
				}
				receipt2.Touched.Add(source.Inventory);
				if (!source.Inventory.RemoveItem(source.Item, item3.Amount))
				{
					throw new InvalidOperationException("The game rejected a material withdrawal.");
				}
			}
			return receipt2;
		}
		catch
		{
			Restore(receipt2);
			throw;
		}
	}

	private void Restore(Receipt receipt)
	{
		List<Exception> list = new List<Exception>();
		foreach (Inventory item2 in receipt.Touched)
		{
			try
			{
				receipt.Backups[item2].Restore(item2.GetAllItems(), delegate(ItemData val, int count)
				{
					val.m_stack = count;
				});
				InventoryChanged.Invoke(item2, new object[2] { false, false });
			}
			catch (Exception item)
			{
				list.Add(item);
			}
		}
		if (list.Count > 0)
		{
			throw new AggregateException("One or more inventory restore callbacks failed.", list);
		}
	}

	internal bool Build(Player player, Piece piece)
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		paidResources = null;
		paidPlayer = null;
		if (!CanUse(player) || (bool)NoPlacementCost.GetValue(player) || ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey()))
		{
			return player.TryPlacePiece(piece);
		}
		if (activeReceipt != null)
		{
			throw new InvalidOperationException("Nested Magic Hammer placement is unsupported.");
		}
		Receipt receipt = null;
		bool flag = false;
		accessPending = false;
		try
		{
			receipt = Reserve(player, piece);
			if (receipt == null)
			{
				if (!accessPending)
				{
					Tell(player, "Magic Hammer: materials unavailable (chest busy, protected, out of range or stock changed).");
				}
				return false;
			}
			activeReceipt = receipt;
			flag = player.TryPlacePiece(piece);
			if (!flag)
			{
				Restore(receipt);
			}
			else
			{
				paidResources = piece.m_resources;
				paidPlayer = player;
				paidFrame = Time.frameCount;
				int num = receipt.Plan.Where((Withdrawal w) => (Object)(object)((Source)w.Stock.Tag).Chest != (Object)null).Sum((Withdrawal w) => w.Amount);
				if (num > 0)
				{
					Tell(player, "Magic Hammer: used " + num + " materials from nearby chests.");
					if (LogUse.Value)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Built " + Prefab((Object)(object)((Component)piece).gameObject) + ": " + string.Join(", ", (from w in receipt.Plan
							where (Object)(object)((Source)w.Stock.Tag).Chest != (Object)null
							select Prefab((Object)(object)((Source)w.Stock.Tag).Item.m_dropPrefab) + " x" + w.Amount).ToArray())));
					}
				}
			}
			return flag;
		}
		catch (Exception ex)
		{
			if (receipt != null && !receipt.PlacementEntered && !flag)
			{
				try
				{
					Restore(receipt);
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Material restoration failed: " + ex2));
				}
			}
			Fault(ex);
			return flag;
		}
		finally
		{
			activeReceipt = null;
			stockFrame = -1;
		}
	}

	internal void ConsumePaid(Player player, Requirement[] resources, int quality, int itemQuality, int multiplier)
	{
		if ((Object)(object)player == (Object)(object)paidPlayer && object.ReferenceEquals(resources, paidResources) && paidFrame == Time.frameCount)
		{
			paidResources = null;
			paidPlayer = null;
		}
		else
		{
			player.ConsumeResources(resources, quality, itemQuality, multiplier);
		}
	}

	internal bool ReservedCheated(Inventory inventory)
	{
		if (activeReceipt != null && activeReceipt.Cheated && (Object)(object)Player.m_localPlayer != (Object)null)
		{
			return inventory == ((Humanoid)Player.m_localPlayer).GetInventory();
		}
		return false;
	}

	internal void PlacementEntered(Player player)
	{
		if (activeReceipt != null && (Object)(object)player == (Object)(object)Player.m_localPlayer)
		{
			activeReceipt.PlacementEntered = true;
		}
	}

	private void Tell(Player player, string text)
	{
		if (messages.Value)
		{
			((Character)player).Message((MessageType)1, text, 0, (Sprite)null, false);
		}
	}

	internal void Fault(Exception ex)
	{
		faulted = true;
		((BaseUnityPlugin)this).Logger.LogError((object)("Magic Hammer chest assistance disabled until restart. Native building remains available. " + ex));
		DisposeGlow();
	}

	private void AppendMaterialCatalogue()
	{
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Invalid comparison between Unknown and I4
		SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
		foreach (GameObject item in ObjectDB.instance.m_items)
		{
			ItemDrop component = item.GetComponent<ItemDrop>();
			if ((Object)(object)component != (Object)null && (int)component.m_itemData.m_shared.m_itemType == 1)
			{
				sortedSet.Add(((Object)item).name);
			}
		}
		GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Hammer");
		if ((Object)(object)itemPrefab != (Object)null)
		{
			PieceTable buildPieces = itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_buildPieces;
			if ((Object)(object)buildPieces != (Object)null)
			{
				foreach (GameObject piece in buildPieces.m_pieces)
				{
					Piece val = (((Object)(object)piece == (Object)null) ? null : piece.GetComponent<Piece>());
					if (!((Object)(object)val != (Object)null))
					{
						continue;
					}
					Requirement[] resources = val.m_resources;
					foreach (Requirement val2 in resources)
					{
						if ((Object)(object)val2.m_resItem != (Object)null)
						{
							sortedSet.Add(((Object)((Component)val2.m_resItem).gameObject).name);
						}
					}
				}
			}
		}
		HashSet<string> existing = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		string[] array = File.ReadAllLines(((BaseUnityPlugin)this).Config.ConfigFilePath);
		foreach (string text in array)
		{
			string text2 = text.Trim().TrimStart('#', ';').Trim();
			int num = text2.IndexOf('=');
			if (num > 0)
			{
				existing.Add(text2.Substring(0, num).Trim());
			}
		}
		string[] array2 = sortedSet.Where((string n) => !existing.Contains(n)).ToArray();
		if (array2.Length > 0)
		{
			File.AppendAllText(((BaseUnityPlugin)this).Config.ConfigFilePath, "\n[ProhibitedMaterials]\n# Additional materials discovered in this game installation:\n" + string.Join("\n", array2.Select((string n) => "# " + n + " = true").ToArray()) + "\n");
			configStamp = File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Added " + array2.Length + " commented material examples to the config."));
		}
	}

	private void UpdateGlow()
	{
		Player localPlayer = Player.m_localPlayer;
		if (!GlowEnabled.Value || !CanUse(localPlayer))
		{
			DisposeGlow();
			return;
		}
		GameObject val = Glow.HeldVisual(localPlayer);
		if ((Object)(object)val == (Object)null)
		{
			DisposeGlow();
			return;
		}
		if (glow != null && (Object)(object)glow.Target != (Object)(object)val)
		{
			DisposeGlow();
		}
		if (glow == null)
		{
			glow = new Glow(val, this);
		}
	}

	private void DisposeGlow()
	{
		if (glow != null)
		{
			glow.Dispose();
			glow = null;
		}
	}

	private void OnDestroy()
	{
		DisposeGlow();
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		if ((Object)(object)Instance == (Object)(object)this)
		{
			Instance = null;
		}
	}
}