Decompiled source of MySix NearbyChests v1.0.2

MySix.NearbyChests.dll

Decompiled 4 hours 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 BepInEx;
using HarmonyLib;
using MySix.Core;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace MySix
{
	internal static class SuiteVersion
	{
		public const string Value = "1.0.2";
	}
}
namespace MySix.NearbyChests
{
	internal static class ConnectionEffect
	{
		public static Setting<bool> Show;

		private const float Seconds = 3f;

		private static GameObject _prefab;

		private static ZNetScene _scanned;

		public static void Play(CraftingStation station, Container container)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			if (Show != null && !Show.Value)
			{
				return;
			}
			GameObject val = Prefab();
			if (!((Object)(object)val == (Object)null))
			{
				Vector3 val2 = ((Component)container).transform.position + Vector3.up * container.GetHoverOffset();
				Vector3 val3 = station.GetConnectionEffectPoint() - val2;
				if (!(((Vector3)(ref val3)).sqrMagnitude <= 0f))
				{
					GameObject obj = Object.Instantiate<GameObject>(val, val2, Quaternion.LookRotation(((Vector3)(ref val3)).normalized));
					obj.transform.localScale = new Vector3(1f, 1f, ((Vector3)(ref val3)).magnitude);
					Object.Destroy((Object)(object)obj, 3f);
				}
			}
		}

		private static GameObject Prefab()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			if ((Object)(object)instance == (Object)(object)_scanned)
			{
				return _prefab;
			}
			_scanned = instance;
			_prefab = null;
			foreach (GameObject prefab in instance.m_prefabs)
			{
				StationExtension component = prefab.GetComponent<StationExtension>();
				if ((Object)(object)component != (Object)null && (Object)(object)component.m_connectionPrefab != (Object)null)
				{
					_prefab = component.m_connectionPrefab;
					return _prefab;
				}
			}
			CorePlugin.Log.LogWarning((object)"NearbyChests found no station connection effect to borrow; materials are still taken from nearby containers, just without the beam.");
			return null;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	public static class DoCraftingPatch
	{
		private enum Outcome
		{
			Nothing,
			Fetch,
			Short
		}

		private struct Withdrawal
		{
			public string Name;

			public int Quality;

			public int Amount;

			public int Carried;

			public int Fetched;
		}

		private struct Taking
		{
			public Container Container;

			public int Withdrawal;

			public int Amount;
		}

		private static readonly FieldRef<InventoryGui, Recipe> CraftRecipe = AccessTools.FieldRefAccess<InventoryGui, Recipe>("m_craftRecipe");

		private static readonly FieldRef<InventoryGui, ItemData> CraftUpgradeItem = AccessTools.FieldRefAccess<InventoryGui, ItemData>("m_craftUpgradeItem");

		private static readonly FieldRef<InventoryGui, bool> MultiCrafting = AccessTools.FieldRefAccess<InventoryGui, bool>("m_multiCrafting");

		private static readonly List<Withdrawal> Plan = new List<Withdrawal>();

		private static readonly List<Taking> Taken = new List<Taking>();

		private static readonly List<Container> From = new List<Container>();

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

		private static CraftingStation _station;

		private static bool Prefix(InventoryGui __instance, Player player)
		{
			Reset();
			Recipe val = CraftRecipe.Invoke(__instance);
			CraftingStation val2 = NearbyContainers.Station();
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			if (player.NoCostCheat() || ZoneSystem.instance.GetGlobalKey((GlobalKeys)25))
			{
				return true;
			}
			ItemData val3 = CraftUpgradeItem.Invoke(__instance);
			int qualityLevel = ((val3 == null) ? 1 : (val3.m_quality + 1));
			int multiplier = ((!MultiCrafting.Invoke(__instance)) ? 1 : __instance.m_multiCraftAmount);
			switch (Decide(player, val, val2, qualityLevel, multiplier))
			{
			case Outcome.Nothing:
				Reset();
				return true;
			case Outcome.Short:
				Reset();
				((Character)player).Message((MessageType)2, "$msg_missingrequirement", 0, (Sprite)null, false);
				return false;
			default:
				_station = val2;
				return Fetch(player);
			}
		}

		private static void Finalizer(Player player)
		{
			if (Taken.Count != 0 && !((Object)(object)player == (Object)null))
			{
				int num = RollBack(player);
				if (num > 0)
				{
					CorePlugin.Log.LogInfo((object)($"NearbyChests: the craft did not go through, so {num} fetched " + "material(s) went back into the containers they came from"));
				}
				else
				{
					ShowConnections();
				}
				Reset();
			}
		}

		private static void Reset()
		{
			Plan.Clear();
			Taken.Clear();
			From.Clear();
			_station = null;
		}

		private static Outcome Decide(Player player, Recipe recipe, CraftingStation station, int qualityLevel, int multiplier)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			bool requireOnlyOneIngredient = recipe.m_requireOnlyOneIngredient;
			bool flag = false;
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement val in resources)
			{
				if ((Object)(object)val.m_resItem == (Object)null || station.m_upgrader != val.m_upgraderResource)
				{
					continue;
				}
				int num = val.GetAmount(qualityLevel) * multiplier;
				if (num <= 0)
				{
					continue;
				}
				flag = true;
				ItemData itemData = val.m_resItem.m_itemData;
				string name = itemData.m_shared.m_name;
				bool num2 = !requireOnlyOneIngredient;
				bool flag2 = false;
				int quality = -1;
				int num3 = -1;
				int num4 = 0;
				for (int j = (num2 ? 1 : 0); j <= itemData.m_shared.m_maxQuality; j++)
				{
					int num5 = inventory.CountItems(name, j, true);
					if (num5 >= num)
					{
						flag2 = true;
						break;
					}
					int num6 = num5 + NearbyContainers.CountIn(name, j, matchWorldLevel: true);
					if (num6 > num3)
					{
						num3 = num6;
						quality = j;
						num4 = num5;
					}
					if (requireOnlyOneIngredient && num6 >= num)
					{
						break;
					}
				}
				if (flag2)
				{
					if (requireOnlyOneIngredient)
					{
						return Outcome.Nothing;
					}
					continue;
				}
				if (num3 < num)
				{
					if (!requireOnlyOneIngredient)
					{
						return Outcome.Short;
					}
					continue;
				}
				Plan.Add(new Withdrawal
				{
					Name = name,
					Quality = quality,
					Amount = num - num4
				});
				if (requireOnlyOneIngredient)
				{
					return Outcome.Fetch;
				}
			}
			if (requireOnlyOneIngredient && flag)
			{
				return Outcome.Short;
			}
			if (Plan.Count <= 0)
			{
				return Outcome.Nothing;
			}
			return Outcome.Fetch;
		}

		private static bool Fetch(Player player)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			From.AddRange(NearbyContainers.Current());
			for (int i = 0; i < Plan.Count; i++)
			{
				Withdrawal value = Plan[i];
				value.Carried = inventory.CountItems(value.Name, -1, true);
				Plan[i] = value;
			}
			bool flag = true;
			for (int j = 0; j < Plan.Count; j++)
			{
				Withdrawal value2 = Plan[j];
				int num = value2.Amount;
				for (int k = 0; k < From.Count; k++)
				{
					if (num <= 0)
					{
						break;
					}
					num -= TakeFrom(From[k], inventory, j, value2.Name, value2.Quality, num);
				}
				value2.Fetched = value2.Amount - num;
				Plan[j] = value2;
				if (num > 0)
				{
					flag = false;
				}
			}
			if (flag)
			{
				return true;
			}
			RollBack(player);
			Reset();
			((Character)player).Message((MessageType)2, "Not enough room in your inventory for the materials", 0, (Sprite)null, false);
			CorePlugin.Log.LogInfo((object)"NearbyChests: the materials would not fit in the player's inventory, so they went back into the containers and the craft was cancelled");
			return false;
		}

		private static int TakeFrom(Container container, Inventory inventory, int withdrawal, string name, int quality, int wanted)
		{
			Inventory inventory2 = container.GetInventory();
			Stacks.Clear();
			inventory2.GetAllItems(name, Stacks);
			int num = 0;
			foreach (ItemData stack in Stacks)
			{
				if (num >= wanted)
				{
					break;
				}
				if (quality < 0 || stack.m_quality == quality)
				{
					if (!NearbyContainers.Claim(container))
					{
						break;
					}
					num += Move(inventory2, inventory, stack, wanted - num);
				}
			}
			if (num > 0)
			{
				Taken.Add(new Taking
				{
					Container = container,
					Withdrawal = withdrawal,
					Amount = num
				});
			}
			return num;
		}

		private static int RollBack(Player player)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			int num = 0;
			for (int i = 0; i < Plan.Count; i++)
			{
				Withdrawal withdrawal = Plan[i];
				int num2 = Mathf.Min(withdrawal.Fetched, inventory.CountItems(withdrawal.Name, -1, true) - withdrawal.Carried);
				for (int j = 0; j < Taken.Count; j++)
				{
					if (num2 <= 0)
					{
						break;
					}
					if (Taken[j].Withdrawal == i)
					{
						int num3 = PutBack(inventory, Taken[j].Container, withdrawal.Name, withdrawal.Quality, Mathf.Min(num2, Taken[j].Amount));
						num2 -= num3;
						num += num3;
					}
				}
			}
			return num;
		}

		private static int PutBack(Inventory inventory, Container container, string name, int quality, int amount)
		{
			if ((Object)(object)container == (Object)null || !NearbyContainers.Claim(container))
			{
				return 0;
			}
			Inventory inventory2 = container.GetInventory();
			Stacks.Clear();
			inventory.GetAllItems(name, Stacks);
			int num = 0;
			foreach (ItemData stack in Stacks)
			{
				if (num >= amount)
				{
					break;
				}
				if (quality < 0 || stack.m_quality == quality)
				{
					num += Move(inventory, inventory2, stack, amount - num);
				}
			}
			return num;
		}

		private static void ShowConnections()
		{
			if ((Object)(object)_station == (Object)null)
			{
				return;
			}
			for (int i = 0; i < Taken.Count; i++)
			{
				if ((Object)(object)Taken[i].Container == (Object)null)
				{
					continue;
				}
				bool flag = false;
				for (int j = 0; j < i; j++)
				{
					if (flag)
					{
						break;
					}
					flag = (Object)(object)Taken[j].Container == (Object)(object)Taken[i].Container;
				}
				if (!flag)
				{
					ConnectionEffect.Play(_station, Taken[i].Container);
				}
			}
		}

		private static int Move(Inventory from, Inventory to, ItemData stack, int wanted)
		{
			int num = Mathf.Min(wanted, stack.m_stack);
			if (num <= 0)
			{
				return 0;
			}
			ItemData val = stack.Clone();
			val.m_stack = num;
			int num2 = (to.AddItem(val) ? num : (num - val.m_stack));
			if (num2 > 0)
			{
				from.RemoveItem(stack, num2);
			}
			return num2;
		}
	}
	[BepInPlugin("com.mysix.nearbychests", "MySix Nearby Chests", "1.0.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class NearbyChestsPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.mysix.nearbychests";

		public const string PluginName = "MySix Nearby Chests";

		public const string PluginVersion = "1.0.2";

		private const int RequiredCoreApi = 1;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			if (CorePlugin.RequireApi("MySix Nearby Chests", 1))
			{
				NearbyContainers.Enabled = Settings.Register<bool>("MySix Nearby Chests", ((BaseUnityPlugin)this).Config.Bind<bool>("NearbyChests", "Enabled", true, "Crafting and upgrading at a crafting station also uses materials from containers standing near it. Building with the hammer is unaffected and always takes materials from your own inventory."), (Scope)0, (string)null);
				NearbyContainers.Range = Settings.Register<float>("MySix Nearby Chests", ((BaseUnityPlugin)this).Config.Bind<float>("NearbyChests", "Range", 10f, "How far a container may stand from the crafting station, in metres. Large values cost a little performance, because every container in range is counted while the crafting menu is open."), (Scope)0, (string)null);
				ConnectionEffect.Show = Settings.Register<bool>("MySix Nearby Chests", ((BaseUnityPlugin)this).Config.Bind<bool>("NearbyChests", "ShowConnection", true, "Draw the station's own connection beam to each container it took materials from."), (Scope)1, (string)null);
				_harmony = new Harmony("com.mysix.nearbychests");
				_harmony.PatchAll(typeof(NearbyChestsPlugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"MySix Nearby Chests 1.0.2 loaded");
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class NearbyContainers
	{
		public static Setting<bool> Enabled;

		public static Setting<float> Range;

		private const float CacheSeconds = 0.25f;

		private static readonly List<Container> Usable = new List<Container>();

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

		private static readonly Comparison<Container> ByDistance = CompareByDistance;

		private static CraftingStation _station;

		private static float _range;

		private static float _at = float.NegativeInfinity;

		private static Vector3 _centre;

		private static readonly Func<Container, long, bool> CheckAccess = AccessTools.MethodDelegate<Func<Container, long, bool>>(AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null), (object)null, true);

		private static readonly FieldRef<Container, ZNetView> NView = AccessTools.FieldRefAccess<Container, ZNetView>("m_nview");

		public static CraftingStation Station()
		{
			if (Enabled == null || !Enabled.Value)
			{
				return null;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				return localPlayer.GetCurrentCraftingStation();
			}
			return null;
		}

		public static List<Container> Current()
		{
			CraftingStation val = Station();
			if ((Object)(object)val == (Object)null)
			{
				Usable.Clear();
				_station = null;
				return Usable;
			}
			float num = Mathf.Max(0f, (Range != null) ? Range.Value : 10f);
			if ((Object)(object)val == (Object)(object)_station && num == _range && Time.unscaledTime - _at < 0.25f)
			{
				return Usable;
			}
			_station = val;
			_range = num;
			_at = Time.unscaledTime;
			Refresh(val, num);
			return Usable;
		}

		public static int CountIn(string sharedName, int quality, bool matchWorldLevel)
		{
			List<Container> list = Current();
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				num += list[i].GetInventory().CountItems(sharedName, quality, matchWorldLevel);
			}
			return num;
		}

		public static bool Claim(Container container)
		{
			ZNetView val = NView.Invoke(container);
			if (!val.IsOwner())
			{
				val.ClaimOwnership();
			}
			return val.IsOwner();
		}

		private static void Refresh(CraftingStation station, float range)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			Usable.Clear();
			Nearby.Clear();
			_centre = ((Component)station).transform.position;
			Piece.GetAllPiecesInRadius(_centre, range, Nearby);
			long playerID = Game.instance.GetPlayerProfile().GetPlayerID();
			for (int i = 0; i < Nearby.Count; i++)
			{
				Container component = ((Component)Nearby[i]).GetComponent<Container>();
				if ((Object)(object)component != (Object)null && MayTakeFrom(component, playerID))
				{
					Usable.Add(component);
				}
			}
			Usable.Sort(ByDistance);
		}

		private static bool MayTakeFrom(Container container, long playerId)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = NView.Invoke(container);
			if ((Object)(object)val == (Object)null || !val.IsValid())
			{
				return false;
			}
			if (container.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false))
			{
				return false;
			}
			if (!CheckAccess(container, playerId))
			{
				return false;
			}
			return val.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 0;
		}

		private static int CompareByDistance(Container a, Container b)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = ((Component)a).transform.position - _centre;
			float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
			val = ((Component)b).transform.position - _centre;
			return sqrMagnitude.CompareTo(((Vector3)(ref val)).sqrMagnitude);
		}
	}
	public static class RequirementCounting
	{
		private static readonly MethodInfo CountItems = AccessTools.Method(typeof(Inventory), "CountItems", new Type[3]
		{
			typeof(string),
			typeof(int),
			typeof(bool)
		}, (Type[])null);

		public static int CountForCrafting(Inventory inventory, string sharedName, int quality, bool matchWorldLevel)
		{
			int num = inventory.CountItems(sharedName, quality, matchWorldLevel);
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || inventory != ((Humanoid)localPlayer).GetInventory())
			{
				return num;
			}
			return num + NearbyContainers.CountIn(sharedName, quality, matchWorldLevel);
		}

		public static int CountForRequirementRow(Inventory inventory, string sharedName, int quality, bool matchWorldLevel)
		{
			if (!SetupRequirementListPatch.Drawing)
			{
				return inventory.CountItems(sharedName, quality, matchWorldLevel);
			}
			return CountForCrafting(inventory, sharedName, quality, matchWorldLevel);
		}

		public static IEnumerable<CodeInstruction> Redirect(IEnumerable<CodeInstruction> instructions, MethodInfo replacement, string where)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (CodeInstructionExtensions.Calls(list[i], CountItems))
				{
					list[i] = new CodeInstruction(OpCodes.Call, (object)replacement)
					{
						labels = list[i].labels,
						blocks = list[i].blocks
					};
					num++;
				}
			}
			if (num == 1)
			{
				return list;
			}
			CorePlugin.Log.LogError((object)($"NearbyChests could not patch {where} (found {num} item counts, expected 1). " + "Crafting will only see your own inventory."));
			return instructions;
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirementItems")]
	public static class HaveRequirementItemsPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return RequirementCounting.Redirect(instructions, AccessTools.Method(typeof(RequirementCounting), "CountForCrafting", (Type[])null, (Type[])null), "Player.HaveRequirementItems");
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirement")]
	public static class SetupRequirementPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return RequirementCounting.Redirect(instructions, AccessTools.Method(typeof(RequirementCounting), "CountForRequirementRow", (Type[])null, (Type[])null), "InventoryGui.SetupRequirement");
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirementList")]
	public static class SetupRequirementListPatch
	{
		public static bool Drawing { get; private set; }

		private static void Prefix()
		{
			Drawing = true;
		}

		private static void Finalizer()
		{
			Drawing = false;
		}
	}
}