Decompiled source of Build From Chest v0.1.0

FrizzQOL.BuildFromChest.dll

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

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("FrizzQOL.BuildFromChest")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+41e0e2d57405e14d22a27a1d3d5c99c152fb52f0")]
[assembly: AssemblyProduct("FrizzQOL.BuildFromChest")]
[assembly: AssemblyTitle("FrizzQOL.BuildFromChest")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BuildFromChest
{
	public sealed class ChestCandidate
	{
		public int Id;

		public float Distance;

		public bool CanOpen;

		public int WoodCount;

		public int StoneCount;
	}
	public static class ChestPay
	{
		public const string Wood = "$item_wood";

		public const string Stone = "$item_stone";

		public const string WoodPrefab = "Wood";

		public const string StonePrefab = "Stone";

		public static int Missing(int cost, int inInventory)
		{
			int num = cost - inInventory;
			if (num <= 0)
			{
				return 0;
			}
			return num;
		}

		public static bool NeedsPull(int missingWood, int missingStone)
		{
			if (missingWood <= 0)
			{
				return missingStone > 0;
			}
			return true;
		}

		public static bool CanCover(int chestWood, int chestStone, int missingWood, int missingStone)
		{
			if (chestWood >= missingWood)
			{
				return chestStone >= missingStone;
			}
			return false;
		}

		public static bool IsInRange(float distance, float radius)
		{
			if (radius > 0f)
			{
				return distance <= radius;
			}
			return false;
		}

		public static bool IsChestPrefab(string name)
		{
			switch (name)
			{
			default:
				return name == "piece_chest_blackmetal";
			case "piece_chest_wood":
			case "piece_chest":
			case "piece_chest_private":
				return true;
			}
		}

		public static bool IsHammer(string prefabName)
		{
			return prefabName == "Hammer";
		}

		public static string PrefabKey(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "";
			}
			if (name.EndsWith("(Clone)"))
			{
				return name.Substring(0, name.Length - "(Clone)".Length);
			}
			return name;
		}

		public static void AddCost(string itemName, int amount, ref int wood, ref int stone)
		{
			if (!string.IsNullOrEmpty(itemName) && amount > 0)
			{
				if (itemName == "$item_wood")
				{
					wood += amount;
				}
				else if (itemName == "$item_stone")
				{
					stone += amount;
				}
			}
		}

		public static int? PickNearest(IList<ChestCandidate> candidates, float radius)
		{
			int? result = null;
			float num = 0f;
			if (candidates == null)
			{
				return null;
			}
			for (int i = 0; i < candidates.Count; i++)
			{
				ChestCandidate chestCandidate = candidates[i];
				if (chestCandidate != null && chestCandidate.CanOpen && IsInRange(chestCandidate.Distance, radius) && (!result.HasValue || chestCandidate.Distance < num || (chestCandidate.Distance == num && chestCandidate.Id < result.Value)))
				{
					result = chestCandidate.Id;
					num = chestCandidate.Distance;
				}
			}
			return result;
		}
	}
	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	internal static class PlacePatch
	{
		private static readonly MethodInfo CheckAccessMethod = AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null);

		private static readonly MethodInfo SaveMethod = AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null);

		private static readonly FieldInfo NViewField = AccessTools.Field(typeof(Container), "m_nview");

		private static bool _busy;

		private static bool _loggedAccess;

		private static bool _loggedSave;

		private static Container _chest;

		private static int _wood;

		private static int _stone;

		private static Collider[] _hits = (Collider[])(object)new Collider[256];

		private static readonly List<ChestCandidate> Candidates = new List<ChestCandidate>();

		private static readonly Dictionary<int, Container> ById = new Dictionary<int, Container>();

		private static void Prefix(Player __instance, Piece piece)
		{
			if (!_busy && !((Object)(object)_chest != (Object)null))
			{
				Prepare(__instance, piece);
			}
		}

		internal static void Prepare(Player player, Piece piece)
		{
			if (_busy || (Object)(object)_chest != (Object)null || (Object)(object)player == (Object)null || (Object)(object)piece == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !CanSaveChests())
			{
				return;
			}
			ItemData rightItem = ((Humanoid)player).RightItem;
			if (!ChestPay.IsHammer((rightItem != null && (Object)(object)rightItem.m_dropPrefab != (Object)null) ? ChestPay.PrefabKey(((Object)rightItem.m_dropPrefab).name) : "") || IsFreeBuild(piece))
			{
				return;
			}
			int wood = 0;
			int stone = 0;
			SumCosts(piece.m_resources, ref wood, ref stone);
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return;
			}
			int num = ChestPay.Missing(wood, inventory.CountItems("$item_wood", -1, true));
			int num2 = ChestPay.Missing(stone, inventory.CountItems("$item_stone", -1, true));
			if (!ChestPay.NeedsPull(num, num2))
			{
				return;
			}
			float radius = Plugin.CurrentRadius();
			int wood2;
			int stone2;
			Container val = FindChest(player, radius, out wood2, out stone2);
			if ((Object)(object)val == (Object)null || !ChestPay.CanCover(wood2, stone2, num, num2))
			{
				return;
			}
			_busy = true;
			try
			{
				if (MoveToPlayer(val, inventory, num, num2))
				{
					_chest = val;
					_wood = num;
					_stone = num2;
				}
			}
			catch (Exception ex)
			{
				_chest = null;
				_wood = 0;
				_stone = 0;
				Plugin.LogWarning("Chest pull failed: " + ex.Message);
			}
			finally
			{
				_busy = false;
			}
		}

		internal static void Finish(Player player)
		{
			if ((Object)(object)_chest == (Object)null)
			{
				_busy = false;
				return;
			}
			Container chest = _chest;
			int wood = _wood;
			int stone = _stone;
			_chest = null;
			_wood = 0;
			_stone = 0;
			_busy = false;
			ReturnToChest(player, chest, wood, stone);
		}

		private static bool IsFreeBuild(Piece piece)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)piece == (Object)null || (Object)(object)instance == (Object)null)
			{
				return false;
			}
			return instance.GetGlobalKey(piece.FreeBuildKey());
		}

		private static void Postfix(Player __instance, bool __result)
		{
			try
			{
				if (!((Object)(object)_chest == (Object)null))
				{
					Container chest = _chest;
					int wood = _wood;
					int stone = _stone;
					_chest = null;
					_wood = 0;
					_stone = 0;
					if (!__result)
					{
						ReturnToChest(__instance, chest, wood, stone);
					}
				}
			}
			finally
			{
				_busy = false;
			}
		}

		private static void SumCosts(Requirement[] requirements, ref int wood, ref int stone)
		{
			if (requirements == null)
			{
				return;
			}
			foreach (Requirement val in requirements)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_resItem.m_itemData != null && val.m_resItem.m_itemData.m_shared != null)
				{
					ChestPay.AddCost(val.m_resItem.m_itemData.m_shared.m_name, val.m_amount, ref wood, ref stone);
				}
			}
		}

		private static Container FindChest(Player player, float radius, out int wood, out int stone)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			wood = 0;
			stone = 0;
			if (radius <= 0f)
			{
				return null;
			}
			Vector3 position = ((Component)player).transform.position;
			int num = Overlap(position, radius + 2f);
			Candidates.Clear();
			ById.Clear();
			for (int i = 0; i < num; i++)
			{
				Collider val = _hits[i];
				_hits[i] = null;
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Container componentInParent = ((Component)val).GetComponentInParent<Container>();
				if (!((Object)(object)componentInParent == (Object)null))
				{
					int instanceID = ((Object)componentInParent).GetInstanceID();
					if (!ById.ContainsKey(instanceID) && ChestPay.IsChestPrefab(ChestPay.PrefabKey(((Object)((Component)componentInParent).gameObject).name)))
					{
						Inventory inventory = componentInParent.GetInventory();
						bool flag = inventory != null && CanOpen(componentInParent, player);
						ChestCandidate item = new ChestCandidate
						{
							Id = instanceID,
							Distance = Vector3.Distance(position, ((Component)componentInParent).transform.position),
							CanOpen = flag,
							WoodCount = (flag ? inventory.CountItems("$item_wood", -1, true) : 0),
							StoneCount = (flag ? inventory.CountItems("$item_stone", -1, true) : 0)
						};
						Candidates.Add(item);
						ById[instanceID] = componentInParent;
					}
				}
			}
			int? num2 = ChestPay.PickNearest(Candidates, radius);
			if (!num2.HasValue || !ById.TryGetValue(num2.Value, out var value))
			{
				return null;
			}
			for (int j = 0; j < Candidates.Count; j++)
			{
				if (Candidates[j].Id == num2.Value)
				{
					wood = Candidates[j].WoodCount;
					stone = Candidates[j].StoneCount;
					break;
				}
			}
			return value;
		}

		private static int Overlap(Vector3 origin, float radius)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			int num = Physics.OverlapSphereNonAlloc(origin, radius, _hits, -1, (QueryTriggerInteraction)2);
			while (num == _hits.Length && _hits.Length < 4096)
			{
				_hits = (Collider[])(object)new Collider[_hits.Length * 2];
				num = Physics.OverlapSphereNonAlloc(origin, radius, _hits, -1, (QueryTriggerInteraction)2);
			}
			return num;
		}

		private static bool MoveToPlayer(Container chest, Inventory playerInventory, int missingWood, int missingStone)
		{
			Inventory inventory = chest.GetInventory();
			if (inventory == null)
			{
				return false;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			try
			{
				num = Take(inventory, "$item_wood", missingWood);
				if (num != missingWood)
				{
					UndoPull(chest, inventory, playerInventory, num, num2, num3, num4);
					return false;
				}
				num2 = Take(inventory, "$item_stone", missingStone);
				if (num2 != missingStone)
				{
					UndoPull(chest, inventory, playerInventory, num, num2, num3, num4);
					return false;
				}
				num3 = Give(playerInventory, "Wood", "$item_wood", missingWood);
				num4 = Give(playerInventory, "Stone", "$item_stone", missingStone);
				if (num3 != missingWood || num4 != missingStone)
				{
					UndoPull(chest, inventory, playerInventory, num, num2, num3, num4);
					return false;
				}
				SaveChest(chest);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.LogWarning("Chest pull failed, restoring items: " + ex.Message);
				UndoPull(chest, inventory, playerInventory, num, num2, num3, num4);
				return false;
			}
		}

		private static void UndoPull(Container chest, Inventory chestInventory, Inventory playerInventory, int gotWood, int gotStone, int putWood, int putStone)
		{
			int num = Take(playerInventory, "$item_wood", putWood);
			int num2 = Take(playerInventory, "$item_stone", putStone);
			Give(chestInventory, "Wood", "$item_wood", gotWood - putWood + num);
			Give(chestInventory, "Stone", "$item_stone", gotStone - putStone + num2);
			SaveChest(chest);
		}

		private static void ReturnToChest(Player player, Container chest, int wood, int stone)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)chest == (Object)null || chest.GetInventory() == null || ((Humanoid)player).GetInventory() == null)
			{
				Plugin.LogWarning("Could not return pulled wood and stone.");
				return;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			Inventory inventory2 = chest.GetInventory();
			int num = Take(inventory, "$item_wood", wood);
			int num2 = Take(inventory, "$item_stone", stone);
			int num3 = Give(inventory2, "Wood", "$item_wood", num);
			int num4 = Give(inventory2, "Stone", "$item_stone", num2);
			int num5 = num - num3;
			int num6 = num2 - num4;
			if (num5 > 0)
			{
				Give(inventory, "Wood", "$item_wood", num5);
			}
			if (num6 > 0)
			{
				Give(inventory, "Stone", "$item_stone", num6);
			}
			SaveChest(chest);
			if (num != wood || num2 != stone || num5 > 0 || num6 > 0)
			{
				Plugin.LogWarning("Pulled wood or stone could not all return to the chest.");
			}
		}

		private static bool CanOpen(Container container, Player player)
		{
			if (CheckAccessMethod == null)
			{
				if (!_loggedAccess)
				{
					_loggedAccess = true;
					Plugin.LogWarning("Container.CheckAccess is missing. Chest pull is off.");
				}
				return false;
			}
			object obj = CheckAccessMethod.Invoke(container, new object[1] { player.GetPlayerID() });
			if (!(obj is bool) || !(bool)obj || container.IsInUse())
			{
				return false;
			}
			if (NViewField == null)
			{
				return false;
			}
			object? value = NViewField.GetValue(container);
			ZNetView val = (ZNetView)((value is ZNetView) ? value : null);
			if (val == null || (Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner())
			{
				return false;
			}
			return true;
		}

		private static bool CanSaveChests()
		{
			if (SaveMethod != null)
			{
				return true;
			}
			if (!_loggedSave)
			{
				_loggedSave = true;
				Plugin.LogWarning("Container.Save is missing. Chest pull is off.");
			}
			return false;
		}

		private static void SaveChest(Container container)
		{
			if ((Object)(object)container != (Object)null && SaveMethod != null)
			{
				SaveMethod.Invoke(container, null);
			}
		}

		private static int Take(Inventory inventory, string sharedName, int amount)
		{
			if (inventory == null || amount <= 0)
			{
				return 0;
			}
			int num = inventory.CountItems(sharedName, -1, true);
			inventory.RemoveItem(sharedName, amount, -1, true);
			return num - inventory.CountItems(sharedName, -1, true);
		}

		private static int Give(Inventory inventory, string prefabName, string sharedName, int amount)
		{
			if (inventory == null || amount <= 0)
			{
				return 0;
			}
			int num = inventory.CountItems(sharedName, -1, true);
			inventory.AddItem(prefabName, amount, 1, 0, 0L, "", false, false);
			return inventory.CountItems(sharedName, -1, true) - num;
		}
	}
	public static class PlacementClick
	{
		[HarmonyPatch(typeof(Player), "UpdatePlacement")]
		private static class UpdatePlacementPatch
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				//IL_008f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0099: Expected O, but got Unknown
				//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b4: Expected O, but got Unknown
				//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c9: Expected O, but got Unknown
				List<CodeInstruction> list = new List<CodeInstruction>(instructions);
				MethodInfo methodInfo = AccessTools.Method(typeof(PlacementClick), "PullBeforeRequirements", (Type[])null, (Type[])null);
				for (int i = 3; i < list.Count; i++)
				{
					if (IsPieceRequirementCheck(list[i]))
					{
						CodeInstruction val = list[i - 2];
						if (!(list[i - 3].opcode != OpCodes.Ldarg_0) && IsLoadLocal(val.opcode) && !(list[i - 1].opcode != OpCodes.Ldc_I4_0))
						{
							int num = i - 3;
							list.Insert(num, new CodeInstruction(OpCodes.Ldarg_0, (object)null));
							list.Insert(num + 1, new CodeInstruction(val.opcode, val.operand));
							list.Insert(num + 2, new CodeInstruction(OpCodes.Call, (object)methodInfo));
							return list;
						}
					}
				}
				Plugin.LogWarning("UpdatePlacement does not check requirements before place. A short hammer click will not pull.");
				return list;
			}

			private static void Postfix(Player __instance)
			{
				PlacePatch.Finish(__instance);
			}

			private static Exception Finalizer(Player __instance, Exception __exception)
			{
				PlacePatch.Finish(__instance);
				return __exception;
			}

			private static bool IsPieceRequirementCheck(CodeInstruction instruction)
			{
				if (instruction.opcode != OpCodes.Call && instruction.opcode != OpCodes.Callvirt)
				{
					return false;
				}
				MethodInfo methodInfo = instruction.operand as MethodInfo;
				if (methodInfo == null || methodInfo.Name != "HaveRequirements")
				{
					return false;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (parameters.Length == 2)
				{
					return parameters[0].ParameterType == typeof(Piece);
				}
				return false;
			}

			private static bool IsLoadLocal(OpCode opcode)
			{
				if (!(opcode == OpCodes.Ldloc) && !(opcode == OpCodes.Ldloc_S) && !(opcode == OpCodes.Ldloc_0) && !(opcode == OpCodes.Ldloc_1) && !(opcode == OpCodes.Ldloc_2))
				{
					return opcode == OpCodes.Ldloc_3;
				}
				return true;
			}
		}

		public static void PullBeforeRequirements(Player player, Piece piece)
		{
			PlacePatch.Prepare(player, piece);
		}
	}
	[BepInPlugin("com.frizzqol.buildfromchest", "FrizzQOL Build From Chest", "0.1.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.frizzqol.buildfromchest";

		public const string PluginName = "FrizzQOL Build From Chest";

		public const string PluginVersion = "0.1.0";

		internal static ConfigEntry<float> ChestRadius;

		private Harmony _harmony;

		internal static Plugin Instance { get; private set; }

		private void Awake()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			Instance = this;
			ChestRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ChestRadius", 10f, "Meters from the player to the nearest chest. 0 or less disables the pull.");
			_harmony = new Harmony("com.frizzqol.buildfromchest");
			try
			{
				_harmony.PatchAll();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"FrizzQOL Build From Chest 0.1.0 loaded");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Failed to patch placement: " + ex.Message));
			}
		}

		internal static float CurrentRadius()
		{
			if (ChestRadius == null)
			{
				return 10f;
			}
			return ChestRadius.Value;
		}

		internal static void LogWarning(string message)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				((BaseUnityPlugin)Instance).Logger.LogWarning((object)message);
			}
		}
	}
}