Decompiled source of CraftFromContainerGroups v0.10.1

CraftFromContainerGroups.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("CraftFromContainerGroups")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.10.1.0")]
[assembly: AssemblyInformationalVersion("0.10.1+046c5e6621ebb4fc892eefcc8ca492c1ed0fc0e3")]
[assembly: AssemblyProduct("CraftFromContainerGroups")]
[assembly: AssemblyTitle("CraftFromContainerGroups")]
[assembly: AssemblyVersion("0.10.1.0")]
[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 CraftFromContainerGroups
{
	internal static class Audit
	{
		private sealed class Snapshot
		{
			public Dictionary<string, int> Counts;

			public long Owner;
		}

		private sealed class MachineState
		{
			public Dictionary<string, int> Queue;

			public float Fuel;

			public long Owner;
		}

		private sealed class CookState
		{
			public string[] Slots;

			public long Owner;
		}

		private static readonly Dictionary<Container, Snapshot> Last = new Dictionary<Container, Snapshot>();

		private static readonly Dictionary<ZDOID, MachineState> MachineStates = new Dictionary<ZDOID, MachineState>();

		private static float _nextMachineCheck;

		private static readonly Dictionary<ZDOID, CookState> CookStates = new Dictionary<ZDOID, CookState>();

		private static readonly FieldInfo FunctionsField = AccessTools.Field(typeof(ZNetView), "m_functions");

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

		public static void Apply(Harmony h)
		{
			Util.Patch(h, "Container.Load (audit)", AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null), typeof(Audit), null, "AfterLoad");
			Util.Patch(h, "Container.Save (audit)", AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null), typeof(Audit), null, "AfterSave");
			Util.Patch(h, "ZNetView.HandleRoutedRPC (audit)", AccessTools.Method(typeof(ZNetView), "HandleRoutedRPC", (Type[])null, (Type[])null), typeof(Audit), "BeforeRpc");
			Util.Patch(h, "Smelter.RPC_AddOre (audit)", AccessTools.Method(typeof(Smelter), "RPC_AddOre", (Type[])null, (Type[])null), typeof(Audit), "AuditAddOre");
			Util.Patch(h, "Smelter.QueueProcessed (audit)", AccessTools.Method(typeof(Smelter), "QueueProcessed", (Type[])null, (Type[])null), typeof(Audit), "AuditMade");
		}

		private static string At(Component c)
		{
			return Util.At(c);
		}

		private static string GameOf(long uid)
		{
			if (uid != ZNet.GetUID())
			{
				return Chests.PlayerName(uid) + "'s game";
			}
			return "your game";
		}

		private static bool Watched(Container c, ZNetView nview)
		{
			if (Groups.Of(nview).Count <= 0)
			{
				return Groups.BuiltByMe((Component)(object)c);
			}
			return true;
		}

		private static string HowFar(long who, Vector3 pos)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				foreach (Player allPlayer in Player.GetAllPlayers())
				{
					ZNetView val = (((Object)(object)allPlayer != (Object)null) ? ((Component)allPlayer).GetComponent<ZNetView>() : null);
					if (!((Object)(object)val == (Object)null) && val.IsValid() && val.GetZDO().GetOwner() == who)
					{
						float num = Vector3.Distance(((Component)allPlayer).transform.position, pos);
						return (num <= 5f) ? "standing at it" : $"{Mathf.RoundToInt(num)} m away, too far to reach by hand";
					}
				}
			}
			catch
			{
			}
			return "not near you, too far to reach by hand";
		}

		private static Dictionary<string, int> Count(Container c)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			Inventory inventory = c.GetInventory();
			if (inventory == null)
			{
				return dictionary;
			}
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				string name = allItem.m_shared.m_name;
				dictionary[name] = (dictionary.TryGetValue(name, out var value) ? value : 0) + allItem.m_stack;
			}
			return dictionary;
		}

		private static void AfterSave(Container __instance)
		{
			try
			{
				if (Plugin.AuditLog.Value && !((Object)(object)__instance == (Object)null))
				{
					ZNetView val = Util.NView((Component)(object)__instance);
					if (!((Object)(object)val == (Object)null) && val.IsValid() && Watched(__instance, val))
					{
						Last[__instance] = new Snapshot
						{
							Counts = Count(__instance),
							Owner = val.GetZDO().GetOwner()
						};
					}
				}
			}
			catch
			{
			}
		}

		private static void AfterLoad(Container __instance, bool __result)
		{
			//IL_0291: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!__result || (Object)(object)__instance == (Object)null)
				{
					return;
				}
				ZNetView val = Util.NView((Component)(object)__instance);
				if ((Object)(object)val == (Object)null || !val.IsValid())
				{
					return;
				}
				if (Groups.Of(val).Count > 0)
				{
					Chests.InvalidateCache();
				}
				if (!Plugin.AuditLog.Value)
				{
					return;
				}
				if (!Watched(__instance, val))
				{
					Last.Remove(__instance);
					return;
				}
				long owner = val.GetZDO().GetOwner();
				Dictionary<string, int> dictionary = Count(__instance);
				Last.TryGetValue(__instance, out var value);
				Last[__instance] = new Snapshot
				{
					Counts = dictionary,
					Owner = owner
				};
				if (Last.Count > 4096)
				{
					Last.Clear();
				}
				else
				{
					if (value == null)
					{
						return;
					}
					long uID = ZNet.GetUID();
					long num = ((owner != uID) ? owner : value.Owner);
					if (num == uID || num == 0L)
					{
						return;
					}
					List<string> list = new List<string>();
					foreach (KeyValuePair<string, int> count in value.Counts)
					{
						int value2;
						int num2 = (dictionary.TryGetValue(count.Key, out value2) ? value2 : 0);
						if (num2 < count.Value)
						{
							list.Add($"-{count.Value - num2} {Transfer.Name(count.Key)}");
						}
					}
					foreach (KeyValuePair<string, int> item in dictionary)
					{
						int value3;
						int num3 = (value.Counts.TryGetValue(item.Key, out value3) ? value3 : 0);
						if (item.Value > num3)
						{
							list.Add($"+{item.Value - num3} {Transfer.Name(item.Key)}");
						}
					}
					if (list.Count != 0)
					{
						Plugin.Log.LogInfo((object)("[Audit] " + At((Component)(object)__instance) + " [" + Groups.Display(val) + "]: " + string.Join(", ", list.ToArray()) + " (by " + GameOf(num) + "; " + Chests.PlayerName(num) + " was " + HowFar(num, ((Component)__instance).transform.position) + ")"));
					}
				}
			}
			catch
			{
			}
		}

		public static void Tick()
		{
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_033d: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.AuditLog.Value || Time.time < _nextMachineCheck || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			_nextMachineCheck = Time.time + 2f;
			long uID = ZNet.GetUID();
			foreach (MonoBehaviour smelter in Registry.Smelters)
			{
				try
				{
					ZNetView val = (((Object)(object)smelter != (Object)null) ? Util.NView((Component)(object)smelter) : null);
					if ((Object)(object)val == (Object)null || !val.IsValid())
					{
						continue;
					}
					ZDO zDO = val.GetZDO();
					Dictionary<string, int> dictionary = new Dictionary<string, int>();
					int num = zDO.GetInt(ZDOVars.s_queued, 0);
					for (int i = 0; i < num && i < 200; i++)
					{
						string text = zDO.GetString("item" + i, "");
						if (text.Length > 0)
						{
							dictionary[text] = (dictionary.TryGetValue(text, out var value) ? value : 0) + 1;
						}
					}
					MachineState machineState = new MachineState
					{
						Queue = dictionary,
						Fuel = zDO.GetFloat(ZDOVars.s_fuel, 0f),
						Owner = zDO.GetOwner()
					};
					MachineStates.TryGetValue(zDO.m_uid, out var value2);
					MachineStates[zDO.m_uid] = machineState;
					if ((value2 == null || value2.Owner != machineState.Owner) && machineState.Owner != uID && machineState.Owner != 0L)
					{
						Plugin.Log.LogInfo((object)("[Audit] " + At((Component)(object)smelter) + " is now controlled by " + Chests.PlayerName(machineState.Owner)));
					}
					if (value2 == null)
					{
						continue;
					}
					long owner = machineState.Owner;
					if (owner == uID || owner == 0L)
					{
						continue;
					}
					List<string> list = new List<string>();
					List<string> list2 = new List<string>();
					Smelter val2 = (Smelter)(object)((smelter is Smelter) ? smelter : null);
					foreach (KeyValuePair<string, int> item in machineState.Queue)
					{
						int value3;
						int num2 = (value2.Queue.TryGetValue(item.Key, out value3) ? value3 : 0);
						if (item.Value > num2)
						{
							list.Add($"+{item.Value - num2} {item.Key} queued");
							if ((Object)(object)val2 != (Object)null && !Accepts(val2, item.Key))
							{
								list2.Add(item.Key);
							}
						}
					}
					float num3 = machineState.Fuel - value2.Fuel;
					if (num3 >= 0.99f)
					{
						list.Add($"+{Mathf.RoundToInt(num3)} fuel");
					}
					if (list.Count != 0)
					{
						string text2 = "[Audit] " + At((Component)(object)smelter) + ": " + string.Join(", ", list.ToArray()) + " (by " + GameOf(owner) + "; " + Chests.PlayerName(owner) + " was " + HowFar(owner, ((Component)smelter).transform.position) + ")";
						if (list2.Count > 0)
						{
							text2 = text2 + " WARNING: a normal " + ((Object)smelter).name.Replace("(Clone)", "") + " doesn't accept " + string.Join(", ", list2.ToArray()) + ": that game runs a mod that changes what this machine takes";
						}
						Plugin.Log.LogInfo((object)text2);
					}
				}
				catch
				{
				}
			}
			if (MachineStates.Count > 2048)
			{
				MachineStates.Clear();
			}
			foreach (MonoBehaviour item2 in Registry.Cooking)
			{
				try
				{
					CheckCooking((CookingStation)(object)((item2 is CookingStation) ? item2 : null), uID);
				}
				catch
				{
				}
			}
			if (CookStates.Count > 2048)
			{
				CookStates.Clear();
			}
		}

		private static void CheckCooking(CookingStation c, long me)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_031d: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = (((Object)(object)c != (Object)null) ? Util.NView((Component)(object)c) : null);
			if ((Object)(object)val == (Object)null || !val.IsValid() || c.m_slots == null)
			{
				return;
			}
			ZDO zDO = val.GetZDO();
			CookState cookState = new CookState
			{
				Slots = new string[c.m_slots.Length],
				Owner = zDO.GetOwner()
			};
			for (int i = 0; i < cookState.Slots.Length; i++)
			{
				cookState.Slots[i] = zDO.GetString("slot" + i, "") ?? "";
			}
			CookStates.TryGetValue(zDO.m_uid, out var value);
			CookStates[zDO.m_uid] = cookState;
			if ((value == null || value.Owner != cookState.Owner) && cookState.Owner != me && cookState.Owner != 0L)
			{
				Plugin.Log.LogInfo((object)("[Audit] " + At((Component)(object)c) + " is now controlled by " + Chests.PlayerName(cookState.Owner)));
			}
			if (value == null || value.Slots.Length != cookState.Slots.Length)
			{
				return;
			}
			long num = ((cookState.Owner != me) ? cookState.Owner : value.Owner);
			if (num == me || num == 0L)
			{
				return;
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			Dictionary<string, int> dictionary2 = new Dictionary<string, int>();
			for (int j = 0; j < cookState.Slots.Length; j++)
			{
				string text = value.Slots[j];
				string text2 = cookState.Slots[j];
				if (text.Length > 0 && text2.Length == 0)
				{
					dictionary[text] = (dictionary.TryGetValue(text, out var value2) ? value2 : 0) + 1;
				}
				else if (text.Length == 0 && text2.Length > 0)
				{
					dictionary2[text2] = (dictionary2.TryGetValue(text2, out var value3) ? value3 : 0) + 1;
				}
			}
			if (dictionary.Count == 0 && dictionary2.Count == 0)
			{
				return;
			}
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, int> item in dictionary)
			{
				list.Add($"{item.Value} {ItemName(item.Key)} taken off");
			}
			foreach (KeyValuePair<string, int> item2 in dictionary2)
			{
				list.Add($"{item2.Value} {ItemName(item2.Key)} put on");
			}
			Plugin.Log.LogInfo((object)("[Audit] " + At((Component)(object)c) + ": " + string.Join(", ", list.ToArray()) + " (by " + GameOf(num) + "; " + Chests.PlayerName(num) + " was " + HowFar(num, ((Component)c).transform.position) + ")"));
		}

		private static string ItemName(string prefab)
		{
			try
			{
				object obj;
				if (!((Object)(object)ObjectDB.instance != (Object)null))
				{
					obj = null;
				}
				else
				{
					GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab);
					obj = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null);
				}
				ItemDrop val = (ItemDrop)obj;
				if ((Object)(object)val != (Object)null)
				{
					return Transfer.Name(val.m_itemData.m_shared.m_name);
				}
			}
			catch
			{
			}
			return prefab;
		}

		private static bool Accepts(Smelter s, string prefabName)
		{
			foreach (ItemConversion item in s.m_conversion)
			{
				if (item != null && (Object)(object)item.m_from != (Object)null && ((Object)((Component)item.m_from).gameObject).name == prefabName)
				{
					return true;
				}
			}
			return false;
		}

		private static void AuditAddOre(Smelter __instance, long sender, string name)
		{
			try
			{
				if (Plugin.AuditLog.Value)
				{
					ZNetView val = Util.NView((Component)(object)__instance);
					if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner())
					{
						Plugin.Log.LogInfo((object)("[Audit] " + At((Component)(object)__instance) + ": " + name + " added by " + Chests.PlayerName(sender)));
					}
				}
			}
			catch
			{
			}
		}

		private static void AuditMade(Smelter __instance, string ore)
		{
			try
			{
				if (Plugin.AuditLog.Value)
				{
					Plugin.Log.LogInfo((object)("[Audit] " + At((Component)(object)__instance) + ": finished one " + ore));
				}
			}
			catch
			{
			}
		}

		private static void BeforeRpc(ZNetView __instance, RoutedRPCData rpcData)
		{
			try
			{
				if (!Plugin.AuditLog.Value || FunctionsField == null || (Object)(object)__instance == (Object)null || rpcData == null || !(FunctionsField.GetValue(__instance) is IDictionary dictionary) || dictionary.Contains(rpcData.m_methodHash))
				{
					return;
				}
				string key = rpcData.m_senderPeerID + "|" + rpcData.m_methodHash + "|" + ((Object)__instance).name;
				if (!LastUnknown.TryGetValue(key, out var value) || !(Time.time - value < 30f))
				{
					LastUnknown[key] = Time.time;
					if (LastUnknown.Count > 512)
					{
						LastUnknown.Clear();
					}
					Plugin.Log.LogInfo((object)($"[Audit] Unknown message {rpcData.m_methodHash} to {At((Component)(object)__instance)} " + "from " + GameOf(rpcData.m_senderPeerID) + " (a mod on that game sent it)"));
				}
			}
			catch
			{
			}
		}
	}
	internal static class Chests
	{
		private sealed class CacheEntry
		{
			public List<Container> Chests;

			public float Time;

			public readonly Dictionary<string, int> Counts = new Dictionary<string, int>();
		}

		private static readonly HashSet<Container> All = new HashSet<Container>();

		private static readonly Func<Container, long, bool> CheckAccessFn = Util.Link<Func<Container, long, bool>>(AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null));

		private const float DisplayCacheSeconds = 0.5f;

		private static readonly Dictionary<string, CacheEntry> DisplayCache = new Dictionary<string, CacheEntry>();

		private static HashSet<string> _lastGroupsRef;

		private static string _lastGroupsSig;

		private static CacheEntry _lastEntry;

		private static HashSet<string> _lastEntryGroups;

		private static int _lx;

		private static int _ly;

		private static int _lz;

		public static int Version { get; private set; }

		public static void Register(Container c)
		{
			if ((Object)(object)c != (Object)null)
			{
				All.Add(c);
			}
		}

		public static IEnumerable<Container> AllLoaded()
		{
			All.RemoveWhere((Container c) => (Object)(object)c == (Object)null);
			return All;
		}

		public static void InvalidateCache()
		{
			DisplayCache.Clear();
			_lastEntry = null;
			Version++;
		}

		public static List<Container> Eligible(Vector3 origin, HashSet<string> groups)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Entry(origin, groups).Chests;
		}

		private static CacheEntry Entry(Vector3 origin, HashSet<string> groups)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: 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_0093: Unknown result type (might be due to invalid IL or missing references)
			if (groups == null || groups.Count == 0)
			{
				return new CacheEntry
				{
					Chests = new List<Container>(),
					Time = Time.time
				};
			}
			int num = Mathf.RoundToInt(origin.x);
			int num2 = Mathf.RoundToInt(origin.y);
			int num3 = Mathf.RoundToInt(origin.z);
			if (_lastEntry != null && groups == _lastEntryGroups && num == _lx && num2 == _ly && num3 == _lz && Time.time - _lastEntry.Time < 0.5f)
			{
				return _lastEntry;
			}
			CacheEntry result = (_lastEntry = EntrySlow(groups, num, num2, num3, origin));
			_lastEntryGroups = groups;
			_lx = num;
			_ly = num2;
			_lz = num3;
			return result;
		}

		private static CacheEntry EntrySlow(HashSet<string> groups, int x, int y, int z, Vector3 origin)
		{
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			if (groups != _lastGroupsRef)
			{
				List<string> list = new List<string>(groups);
				list.Sort(StringComparer.OrdinalIgnoreCase);
				_lastGroupsSig = string.Join(",", list.ToArray());
				_lastGroupsRef = groups;
			}
			string key = _lastGroupsSig + "|" + x + "," + y + "," + z;
			if (DisplayCache.TryGetValue(key, out var value) && Time.time - value.Time < 0.5f)
			{
				return value;
			}
			if (DisplayCache.Count > 32)
			{
				DisplayCache.Clear();
			}
			CacheEntry cacheEntry = new CacheEntry
			{
				Chests = Fresh(origin, groups),
				Time = Time.time
			};
			DisplayCache[key] = cacheEntry;
			return cacheEntry;
		}

		public static List<Container> Fresh(Vector3 origin, HashSet<string> groups)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: 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)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			List<Container> list = new List<Container>();
			if (groups == null || groups.Count == 0)
			{
				return list;
			}
			All.RemoveWhere((Container c) => (Object)(object)c == (Object)null);
			float value = Plugin.Range.Value;
			long playerId = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerID() : 0);
			foreach (Container item in All)
			{
				if (InGroupsAndRange(item, origin, groups, value) && !item.IsInUse() && HasAccess(item, playerId) && (!item.m_checkGuardStone || PrivateArea.CheckAccess(((Component)item).transform.position, 0f, false, false)))
				{
					list.Add(item);
				}
			}
			list.Sort(delegate(Container a, Container b)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: 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_0022: 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_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = origin - ((Component)a).transform.position;
				float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
				val = origin - ((Component)b).transform.position;
				return sqrMagnitude.CompareTo(((Vector3)(ref val)).sqrMagnitude);
			});
			return list;
		}

		public static List<Container> Busy(Vector3 origin, HashSet<string> groups)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			List<Container> list = new List<Container>();
			if (groups == null || groups.Count == 0)
			{
				return list;
			}
			float value = Plugin.Range.Value;
			foreach (Container item in All)
			{
				if ((Object)(object)item != (Object)null && InGroupsAndRange(item, origin, groups, value) && item.IsInUse())
				{
					list.Add(item);
				}
			}
			return list;
		}

		private static bool InGroupsAndRange(Container c, Vector3 origin, HashSet<string> groups, float range)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: 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)
			if ((Object)(object)c == (Object)null)
			{
				return false;
			}
			Vector3 val = origin - ((Component)c).transform.position;
			if (((Vector3)(ref val)).sqrMagnitude > range * range)
			{
				return false;
			}
			ZNetView val2 = Util.NView((Component)(object)c);
			if ((Object)(object)val2 == (Object)null || !val2.IsValid())
			{
				return false;
			}
			if (!Groups.Of(val2).Overlaps(groups))
			{
				return false;
			}
			return c.GetInventory() != null;
		}

		public static string UserOf(Container c)
		{
			try
			{
				ZNetView val = Util.NView((Component)(object)c);
				if ((Object)(object)val == (Object)null || !val.IsValid() || (Object)(object)ZNet.instance == (Object)null)
				{
					return "Someone";
				}
				return PlayerName(val.GetZDO().GetOwner());
			}
			catch
			{
			}
			return "Someone";
		}

		public static string PlayerName(long uid)
		{
			//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_0045: 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_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (uid == 0L)
				{
					return "nobody";
				}
				if (uid == ZNet.GetUID())
				{
					return "You";
				}
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					foreach (PlayerInfo player in ZNet.instance.GetPlayerList())
					{
						ZDOID characterID = player.m_characterID;
						if (((ZDOID)(ref characterID)).UserID == uid && !string.IsNullOrEmpty(player.m_name))
						{
							return player.m_name;
						}
					}
				}
			}
			catch
			{
			}
			return "Someone";
		}

		private static bool HasAccess(Container c, long playerId)
		{
			if (CheckAccessFn == null)
			{
				return true;
			}
			try
			{
				return CheckAccessFn(c, playerId);
			}
			catch
			{
				return true;
			}
		}

		public static bool KeepsOne(Container c)
		{
			return Groups.GetInt(Util.NView((Component)(object)c), "cfcg_keep1") != 0;
		}

		public static int Spare(Container c, string itemName)
		{
			if ((Object)(object)c == (Object)null || c.GetInventory() == null)
			{
				return 0;
			}
			int num = c.GetInventory().CountItems(itemName, -1, true);
			if (num <= 0 || !KeepsOne(c))
			{
				return num;
			}
			return num - 1;
		}

		public static int Count(Vector3 origin, HashSet<string> groups, string itemName)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			CacheEntry cacheEntry = Entry(origin, groups);
			if (cacheEntry.Counts.TryGetValue(itemName, out var value))
			{
				return value;
			}
			int num = 0;
			foreach (Container chest in cacheEntry.Chests)
			{
				num += Spare(chest, itemName);
			}
			cacheEntry.Counts[itemName] = num;
			return num;
		}
	}
	internal static class Compat
	{
		private static readonly FieldInfo SelectedRecipeField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private static readonly FieldInfo MultiCraftingField = AccessTools.Field(typeof(InventoryGui), "m_multiCrafting");

		private static readonly FieldInfo HoveredPieceField = AccessTools.Field(typeof(Hud), "m_hoveredPiece");

		private static PropertyInfo _recipeProp;

		private static PropertyInfo _itemProp;

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

		public static void LateUpdate()
		{
			if (!Plugin.CompatChestAmounts.Value || !Plugin.ModEnabled.Value)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				if (Context.IsCraftingMenuOpen())
				{
					Crafting(localPlayer);
				}
				else if (((Character)localPlayer).InPlaceMode())
				{
					Building(localPlayer);
				}
			}
		}

		private static void Crafting(Player p)
		{
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance == (Object)null || SelectedRecipeField == null || instance.m_recipeRequirementList == null)
			{
				return;
			}
			object value = SelectedRecipeField.GetValue(instance);
			if (value == null)
			{
				return;
			}
			if (_recipeProp == null)
			{
				Type type = value.GetType();
				_recipeProp = AccessTools.Property(type, "Recipe");
				_itemProp = AccessTools.Property(type, "ItemData");
				if (_recipeProp == null)
				{
					return;
				}
			}
			object? value2 = _recipeProp.GetValue(value, null);
			Recipe val = (Recipe)((value2 is Recipe) ? value2 : null);
			if (!((Object)(object)val == (Object)null) && val.m_resources != null)
			{
				ItemData val2 = (ItemData)((_itemProp != null) ? /*isinst with value type is only supported in some contexts*/: null);
				int quality = ((val2 == null) ? 1 : (val2.m_quality + 1));
				int multiplier = ((!(MultiCraftingField != null) || !(bool)MultiCraftingField.GetValue(instance)) ? 1 : instance.m_multiCraftAmount);
				Apply(instance.m_recipeRequirementList, val.m_resources, p, quality, multiplier);
			}
		}

		private static void Building(Player p)
		{
			Hud instance = Hud.instance;
			if (!((Object)(object)instance == (Object)null) && instance.m_requirementItems != null)
			{
				Piece val = (Piece)((HoveredPieceField != null) ? /*isinst with value type is only supported in some contexts*/: null);
				if ((Object)(object)val == (Object)null)
				{
					val = p.GetSelectedPiece();
				}
				if (!((Object)(object)val == (Object)null) && val.m_resources != null)
				{
					Apply(instance.m_requirementItems, val.m_resources, p, 0, 1);
				}
			}
		}

		private static void Apply(GameObject[] lines, Requirement[] reqs, Player p, int quality, int multiplier)
		{
			foreach (GameObject val in lines)
			{
				if ((Object)(object)val == (Object)null || !val.activeInHierarchy)
				{
					continue;
				}
				Transform val2 = val.transform.Find("res_name");
				TMP_Text val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent<TMP_Text>() : null);
				if ((Object)(object)val3 == (Object)null || string.IsNullOrEmpty(val3.text))
				{
					continue;
				}
				foreach (Requirement val4 in reqs)
				{
					if (val4 != null && !((Object)(object)val4.m_resItem == (Object)null) && !(Localize(val4.m_resItem.m_itemData.m_shared.m_name) != val3.text))
					{
						Patches.StyleAmount(val.transform, val4, p, quality, multiplier);
						break;
					}
				}
			}
		}

		private static string Localize(string token)
		{
			if (Localized.TryGetValue(token, out var value))
			{
				return value;
			}
			if (Localized.Count > 2048)
			{
				Localized.Clear();
			}
			value = GroupMenu.Localize(token);
			Localized[token] = value;
			return value;
		}
	}
	internal static class Context
	{
		private static readonly FieldRef<InventoryGui, Container> CurrentContainer = LinkCurrentContainer();

		private static readonly FieldRef<BuildUi, List<BuildUiPieceButton>> PieceButtons = LinkPieceButtons();

		private static readonly Action<InventoryGui, bool> RedrawCrafting = Util.Link<Action<InventoryGui, bool>>(AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", (Type[])null, (Type[])null));

		private static string _menuSig;

		private static bool _forceRedraw;

		private static bool _gameRedrew;

		public static int MadeAvailableByChests;

		private static bool _menuWasCrafting;

		private static float _nextMenuCheck;

		private static int _frame = -1;

		private static Vector3 _framePos;

		private static HashSet<string> _frameGroups = Groups.Empty();

		private static FieldRef<InventoryGui, Container> LinkCurrentContainer()
		{
			try
			{
				return AccessTools.FieldRefAccess<InventoryGui, Container>("m_currentContainer");
			}
			catch
			{
				return null;
			}
		}

		public static bool IsCraftingMenuOpen()
		{
			if (!InventoryGui.IsVisible() || (Object)(object)InventoryGui.instance == (Object)null)
			{
				return false;
			}
			if (CurrentContainer != null)
			{
				return (Object)(object)CurrentContainer.Invoke(InventoryGui.instance) == (Object)null;
			}
			return true;
		}

		public static bool TryGet(out Vector3 origin, out HashSet<string> groups)
		{
			//IL_0001: 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)
			//IL_003a: 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_0072: Unknown result type (might be due to invalid IL or missing references)
			origin = default(Vector3);
			groups = null;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return false;
			}
			if (Plugin.PullForCrafting.Value && IsCraftingMenuOpen())
			{
				origin = ((Component)localPlayer).transform.position;
				groups = GroupsForCrafting(localPlayer);
				return groups.Count > 0;
			}
			if (Plugin.PullForBuilding.Value && ((Character)localPlayer).InPlaceMode())
			{
				origin = ((Component)localPlayer).transform.position;
				groups = GroupsForBuilding(localPlayer);
				return groups.Count > 0;
			}
			return false;
		}

		public static HashSet<string> GroupsForCrafting(Player p)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			CraftingStation currentCraftingStation = p.GetCurrentCraftingStation();
			if (!((Object)(object)currentCraftingStation != (Object)null))
			{
				return GroupsOfStationsAround(((Component)p).transform.position);
			}
			return Groups.Of((Component)(object)currentCraftingStation);
		}

		public static HashSet<string> GroupsForBuilding(Player p)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return GroupsOfStationsAround(((Component)p).transform.position);
		}

		private static FieldRef<BuildUi, List<BuildUiPieceButton>> LinkPieceButtons()
		{
			try
			{
				return AccessTools.FieldRefAccess<BuildUi, List<BuildUiPieceButton>>("m_pieceButtons");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Build menu refresh is off: " + ex.Message));
				return null;
			}
		}

		private static int RecolorBuildMenu()
		{
			BuildUi val = (((Object)(object)Hud.instance != (Object)null) ? Hud.instance.m_buildUi : null);
			if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeSelf || PieceButtons == null)
			{
				return 0;
			}
			List<BuildUiPieceButton> list = PieceButtons.Invoke(val);
			if (list == null)
			{
				return 0;
			}
			int num = 0;
			foreach (BuildUiPieceButton item in list)
			{
				if ((Object)(object)item != (Object)null && ((Component)item).gameObject.activeSelf)
				{
					item.UpdateRequirements();
					num++;
				}
			}
			return num;
		}

		public static void GameRedrewCrafting()
		{
			_gameRedrew = true;
		}

		public static void RedrawSoon()
		{
			_forceRedraw = true;
			_nextMenuCheck = 0f;
		}

		public static void Tick()
		{
			if (Time.time < _nextMenuCheck)
			{
				return;
			}
			_nextMenuCheck = Time.time + 0.25f;
			Player localPlayer = Player.m_localPlayer;
			bool flag = (Object)(object)localPlayer != (Object)null && IsCraftingMenuOpen();
			bool flag2 = (Object)(object)localPlayer != (Object)null && ((Character)localPlayer).InPlaceMode();
			if ((Object)(object)localPlayer == (Object)null || !Plugin.ModEnabled.Value || (!flag && !flag2))
			{
				_menuSig = null;
				return;
			}
			string text = MenuSignature(localPlayer, flag);
			bool forceRedraw = _forceRedraw;
			_forceRedraw = false;
			bool gameRedrew = _gameRedrew;
			_gameRedrew = false;
			if (!forceRedraw && flag && gameRedrew && _menuSig != null && flag == _menuWasCrafting)
			{
				_menuSig = text;
			}
			else
			{
				if (!forceRedraw && text == _menuSig && flag == _menuWasCrafting)
				{
					return;
				}
				bool num = !forceRedraw && (_menuSig == null || flag != _menuWasCrafting);
				_menuSig = text;
				_menuWasCrafting = flag;
				if (num)
				{
					return;
				}
				try
				{
					MadeAvailableByChests = 0;
					string text2 = null;
					if (flag && RedrawCrafting != null && (Object)(object)InventoryGui.instance != (Object)null)
					{
						RedrawCrafting(InventoryGui.instance, arg2: false);
						text2 = "crafting list";
					}
					else if (flag2 && RecolorBuildMenu() > 0)
					{
						text2 = "build menu";
					}
					if (text2 != null && Plugin.LogTransfers.Value)
					{
						Plugin.Log.LogInfo((object)("[Menu] Redrew the " + text2 + (forceRedraw ? " after crafting/building" : "") + ": " + $"{MadeAvailableByChests} makeable thanks to chests in range"));
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Menu refresh: " + ex.Message));
				}
			}
		}

		private static string MenuSignature(Player p, bool crafting)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = (crafting ? GroupsForCrafting(p) : GroupsForBuilding(p));
			StringBuilder stringBuilder = new StringBuilder();
			int num = 0;
			if (hashSet.Count > 0)
			{
				foreach (Container item in Chests.Eligible(((Component)p).transform.position, hashSet))
				{
					if ((Object)(object)item != (Object)null)
					{
						stringBuilder.Append(((Object)item).GetInstanceID()).Append(',');
						num++;
					}
				}
			}
			if (crafting && num == 0)
			{
				return "none";
			}
			stringBuilder.Append('|').Append(Chests.Version).Append('|')
				.Append(Util.InventorySig(((Humanoid)p).GetInventory()));
			return stringBuilder.ToString();
		}

		public static HashSet<string> GroupsOfStationsAround(Vector3 point)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: 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_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: 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)
			if (_frame == Time.frameCount && _framePos == point)
			{
				return _frameGroups;
			}
			HashSet<string> hashSet = Groups.Empty();
			Registry.Stations.RemoveWhere((CraftingStation s) => (Object)(object)s == (Object)null);
			Vector3 val = default(Vector3);
			foreach (CraftingStation station in Registry.Stations)
			{
				Vector3 position = ((Component)station).transform.position;
				((Vector3)(ref val))..ctor(point.x, position.y, point.z);
				if (Vector3.Distance(position, val) < station.GetStationBuildRange())
				{
					hashSet.UnionWith(Groups.Of((Component)(object)station));
				}
			}
			_frame = Time.frameCount;
			_framePos = point;
			_frameGroups = hashSet;
			return hashSet;
		}
	}
	internal static class EpicLootCompat
	{
		private static PropertyInfo _uiInstance;

		private static PropertyInfo _uiSource;

		private static bool _registered;

		private static float _nextTick;

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

		public static Type TableType { get; private set; }

		public static Type GroupableTable
		{
			get
			{
				if (!Plugin.CompatEpicLoot.Value)
				{
					return null;
				}
				return TableType;
			}
		}

		public static void Init(Harmony h)
		{
			if (!Plugin.CompatEpicLoot.Value)
			{
				return;
			}
			Type type = AccessTools.TypeByName("EpicLoot.API");
			Type type2 = AccessTools.TypeByName("EpicLoot_UnityLib.EnchantingTable");
			Type type3 = AccessTools.TypeByName("EpicLoot_UnityLib.EnchantingTableUI");
			if (type == null || type2 == null || type3 == null)
			{
				return;
			}
			TableType = type2;
			_uiInstance = AccessTools.Property(type3, "instance");
			_uiSource = AccessTools.Property(type3, "SourceTable");
			Util.Patch(h, "EnchantingTable.Start", AccessTools.Method(type2, "Start", (Type[])null, (Type[])null), typeof(EpicLootCompat), null, "TableStart");
			MethodInfo methodInfo = AccessTools.Method(type, "RegisterInventoryProvider", new Type[5]
			{
				typeof(string),
				typeof(Func<List<ItemData>>),
				typeof(Func<string, int>),
				typeof(Func<string, int, int>),
				typeof(Func<ItemData, int, int>)
			}, (Type[])null);
			if (methodInfo == null || _uiInstance == null || _uiSource == null)
			{
				Plugin.Log.LogWarning((object)"Epic Loot found, but not the parts this mod uses; the enchanting table won't pull from chests.");
				return;
			}
			try
			{
				object obj = methodInfo.Invoke(null, new object[5]
				{
					"craftfromcontainergroups",
					null,
					new Func<string, int>(Count),
					new Func<string, int, int>(Remove),
					null
				});
				bool flag = default(bool);
				int num;
				if (obj is bool)
				{
					flag = (bool)obj;
					num = 1;
				}
				else
				{
					num = 0;
				}
				_registered = (byte)((uint)num & (flag ? 1u : 0u)) != 0;
				Plugin.Log.LogInfo((object)(_registered ? "Epic Loot: the enchanting table can use chests in its groups." : "Epic Loot didn't accept the chest provider; the enchanting table won't pull from chests."));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Epic Loot: couldn't register the chest provider: " + ex.Message));
			}
		}

		private static void TableStart(MonoBehaviour __instance)
		{
			Groups.RegisterRpc(Util.NView((Component)(object)__instance));
		}

		private static Component OpenTable()
		{
			if (_uiInstance == null)
			{
				return null;
			}
			object? value = _uiInstance.GetValue(null, null);
			MonoBehaviour val = (MonoBehaviour)((value is MonoBehaviour) ? value : null);
			if ((Object)(object)val == (Object)null || !((Behaviour)val).isActiveAndEnabled)
			{
				return null;
			}
			object? value2 = _uiSource.GetValue(val, null);
			return (Component)((value2 is Component) ? value2 : null);
		}

		private static bool TryContext(out Player p, out HashSet<string> groups)
		{
			p = Player.m_localPlayer;
			groups = null;
			if ((Object)(object)p == (Object)null || !Plugin.ModEnabled.Value || !Plugin.CompatEpicLoot.Value)
			{
				return false;
			}
			Component val = OpenTable();
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			groups = Groups.Of(val);
			return groups.Count > 0;
		}

		private static int Count(string name)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(name) || !TryContext(out var p, out var groups))
			{
				return 0;
			}
			Wanted.Add(name);
			int num = 0;
			foreach (Container item in Chests.Eligible(((Component)p).transform.position, groups))
			{
				if (Ownership.IsReady(item))
				{
					num += Chests.Spare(item, name);
				}
			}
			return num;
		}

		private static int Remove(string name, int amount)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(name) || amount <= 0 || !TryContext(out var p, out var groups))
			{
				return 0;
			}
			int num = 0;
			foreach (Container item2 in Chests.Fresh(((Component)p).transform.position, groups))
			{
				if (num >= amount)
				{
					break;
				}
				if (!Ownership.IsReady(item2))
				{
					continue;
				}
				Ownership.Refresh(item2);
				Inventory inventory = item2.GetInventory();
				int num2 = Chests.Spare(item2, name);
				int num3 = 0;
				int num4 = 0;
				while (num < amount && num2 > 0 && num4++ < 64)
				{
					ItemData item = inventory.GetItem(name, -1, false);
					if (item == null || item.m_stack <= 0)
					{
						break;
					}
					int num5 = Mathf.Min(Mathf.Min(item.m_stack, amount - num), num2);
					int num6 = inventory.CountItems(name, -1, true);
					if (!inventory.RemoveItem(item, num5))
					{
						break;
					}
					int num7 = num6 - inventory.CountItems(name, -1, true);
					if (num7 <= 0)
					{
						break;
					}
					num += num7;
					num3 += num7;
					num2 -= num7;
				}
				if (num3 > 0 && Plugin.LogTransfers.Value)
				{
					Plugin.Log.LogInfo((object)$"[Transfer] Enchanting table used {num3}x {Transfer.Name(name)} from {Transfer.Describe(item2)}");
				}
			}
			if (num > 0)
			{
				Chests.InvalidateCache();
			}
			if (num < amount)
			{
				Plugin.Log.LogWarning((object)$"[Transfer] Enchanting table asked for {amount}x {Transfer.Name(name)}, chests gave {num}");
			}
			return num;
		}

		public static void Tick()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if (!_registered || Time.time < _nextTick)
			{
				return;
			}
			_nextTick = Time.time + 1f;
			if (!TryContext(out var p, out var groups))
			{
				Wanted.Clear();
			}
			else
			{
				if (Wanted.Count == 0)
				{
					return;
				}
				foreach (Container item in Chests.Eligible(((Component)p).transform.position, groups))
				{
					if (Ownership.IsReady(item))
					{
						continue;
					}
					foreach (string item2 in Wanted)
					{
						if (Chests.Spare(item, item2) > 0)
						{
							Ownership.Request(item);
							break;
						}
					}
				}
			}
		}
	}
	internal static class GroundPickup
	{
		private static readonly FieldInfo InstancesField = AccessTools.Field(typeof(ItemDrop), "s_instances");

		private static readonly Action<ItemDrop> SaveFn = Util.Link<Action<ItemDrop>>(AccessTools.Method(typeof(ItemDrop), "Save", (Type[])null, (Type[])null));

		private static readonly Func<ItemDrop, double> AgeFn = Util.Link<Func<ItemDrop, double>>(AccessTools.Method(typeof(ItemDrop), "GetTimeSinceSpawned", (Type[])null, (Type[])null));

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

		private static readonly List<ItemDrop> _nearDrops = new List<ItemDrop>();

		private static readonly List<ItemDrop> _candidates = new List<ItemDrop>();

		private static readonly Dictionary<string, List<Container>> _holders = new Dictionary<string, List<Container>>();

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

		private const float MinAgeSeconds = 2f;

		private static float _next;

		private static int _cDroppedByPlayer;

		private static int _cTooYoung;

		private static int _cNoChest;

		private static int _cWaitChest;

		private static int _cWaitItem;

		private static int _cOther;

		private static int _cStored;

		private static string _lastSummary;

		private static readonly HashSet<Container> _receivers = new HashSet<Container>();

		private static float _lastSummaryTime;

		public static void Tick()
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: 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)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.ModEnabled.Value || !Plugin.GroundPickupEnabled.Value || Time.time < _next)
			{
				return;
			}
			_next = Time.time + Mathf.Max(0.5f, Plugin.GroundPickupInterval.Value);
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null || !(InstancesField?.GetValue(null) is List<ItemDrop> { Count: not 0 } list))
			{
				return;
			}
			float value = Plugin.GroundPickupRadius.Value;
			float num = Plugin.VisualRange.Value + value;
			float num2 = num * num;
			Vector3 position = ((Component)localPlayer).transform.position;
			List<Container> chests = _chests;
			chests.Clear();
			Vector3 val;
			foreach (Container item in Chests.AllLoaded())
			{
				if (!((Object)(object)item == (Object)null))
				{
					val = position - ((Component)item).transform.position;
					if (!(((Vector3)(ref val)).sqrMagnitude > num2) && !((Object)(object)Util.PieceOf((Component)(object)item) == (Object)null) && (!Plugin.GroundPickupOnlyMyChests.Value || Groups.BuiltByMe((Component)(object)item)) && (!Plugin.GroundPickupOnlyGroupedChests.Value || Groups.Of((Component)(object)item).Count != 0) && item.GetInventory() != null && !item.IsInUse() && !Util.VehicleBusy((Component)(object)item))
					{
						chests.Add(item);
					}
				}
			}
			_cDroppedByPlayer = (_cTooYoung = (_cNoChest = (_cWaitChest = (_cWaitItem = (_cOther = (_cStored = 0))))));
			int num3 = 0;
			if (chests.Count == 0)
			{
				if (Plugin.LogTransfers.Value)
				{
					Summary("no chests qualify (built chests within range" + (Plugin.GroundPickupOnlyGroupedChests.Value ? ", in a group" : "") + (Plugin.GroundPickupOnlyMyChests.Value ? ", built by you" : "") + ")");
				}
				return;
			}
			_receivers.Clear();
			_nearDrops.Clear();
			foreach (ItemDrop item2 in list)
			{
				if ((Object)(object)item2 != (Object)null)
				{
					val = ((Component)item2).transform.position - position;
					if (((Vector3)(ref val)).sqrMagnitude <= num2)
					{
						_nearDrops.Add(item2);
					}
				}
			}
			num3 = _nearDrops.Count;
			_candidates.Clear();
			foreach (ItemDrop nearDrop in _nearDrops)
			{
				try
				{
					if (Candidate(nearDrop))
					{
						_candidates.Add(nearDrop);
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)("[Ground] " + ex.Message));
				}
			}
			_nearDrops.Clear();
			if (_candidates.Count > 0)
			{
				IndexHolders(chests);
				foreach (ItemDrop candidate in _candidates)
				{
					try
					{
						TryStore(candidate, value);
					}
					catch (Exception ex2)
					{
						Plugin.Log.LogError((object)("[Ground] " + ex2.Message));
					}
				}
			}
			_candidates.Clear();
			if (_cStored > 0)
			{
				Chests.InvalidateCache();
			}
			PickupFx.BatchSound(_receivers);
			if (Plugin.LogTransfers.Value)
			{
				if (num3 == 0)
				{
					Summary($"{chests.Count} chests ready, no items on the ground nearby");
				}
				else
				{
					Summary($"{chests.Count} chests, {num3} ground items: stored {_cStored}, " + $"player-dropped (setting off) {_cDroppedByPlayer}, just landed {_cTooYoung}, " + $"no matching chest in range {_cNoChest}, waiting for chest {_cWaitChest}, " + $"waiting for item {_cWaitItem}, other {_cOther}");
				}
			}
		}

		private static void Summary(string text)
		{
			if (Plugin.LogTransfers.Value && (!(text == _lastSummary) || !(Time.time - _lastSummaryTime < 30f)))
			{
				_lastSummary = text;
				_lastSummaryTime = Time.time;
				Plugin.Log.LogInfo((object)("[Ground] " + text));
			}
		}

		private static void IndexHolders(List<Container> chests)
		{
			foreach (List<Container> value2 in _holders.Values)
			{
				value2.Clear();
				_spareLists.Push(value2);
			}
			_holders.Clear();
			foreach (Container chest in chests)
			{
				foreach (ItemData allItem in chest.GetInventory().GetAllItems())
				{
					string name = allItem.m_shared.m_name;
					if (!_holders.TryGetValue(name, out var value))
					{
						value = ((_spareLists.Count > 0) ? _spareLists.Pop() : new List<Container>());
						_holders[name] = value;
					}
					if (value.Count == 0 || (Object)(object)value[value.Count - 1] != (Object)(object)chest)
					{
						value.Add(chest);
					}
				}
			}
		}

		private static bool Candidate(ItemDrop drop)
		{
			if ((Object)(object)drop == (Object)null)
			{
				return false;
			}
			if (!drop.m_autoPickup && !Plugin.GroundPickupPlayerDropped.Value)
			{
				_cDroppedByPlayer++;
				return false;
			}
			ZNetView component = ((Component)drop).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				_cOther++;
				return false;
			}
			if (drop.IsPiece() || drop.InTar())
			{
				_cOther++;
				return false;
			}
			if ((Object)(object)((Component)drop).GetComponent<Fish>() != (Object)null)
			{
				_cOther++;
				return false;
			}
			ItemData itemData = drop.m_itemData;
			if (itemData == null || itemData.m_shared == null)
			{
				_cOther++;
				return false;
			}
			if (itemData.m_shared.m_questItem)
			{
				_cOther++;
				return false;
			}
			if (Plugin.GroundPickupStackablesOnly.Value && itemData.m_shared.m_maxStackSize <= 1)
			{
				_cOther++;
				return false;
			}
			if (Age(drop) < 2.0)
			{
				_cTooYoung++;
				return false;
			}
			return true;
		}

		private static void TryStore(ItemDrop drop, float radius)
		{
			//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_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)drop == (Object)null)
			{
				return;
			}
			ItemData itemData = drop.m_itemData;
			if (itemData == null || itemData.m_shared == null)
			{
				return;
			}
			string name = itemData.m_shared.m_name;
			Container val = null;
			if (_holders.TryGetValue(name, out var value))
			{
				float num = radius * radius;
				Vector3 position = ((Component)drop).transform.position;
				foreach (Container item in value)
				{
					if ((Object)(object)item == (Object)null)
					{
						continue;
					}
					Vector3 val2 = ((Component)item).transform.position - position;
					float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude;
					if (!(sqrMagnitude > num))
					{
						Inventory inventory = item.GetInventory();
						if (inventory.CanAddItem(itemData, itemData.m_stack) || inventory.CanAddItem(itemData, 1))
						{
							val = item;
							num = sqrMagnitude;
						}
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				_cNoChest++;
				return;
			}
			if (!Ownership.IsReady(val))
			{
				_cWaitChest++;
				Ownership.Request(val);
				return;
			}
			if (!drop.CanPickup(true))
			{
				_cWaitItem++;
				drop.RequestOwn();
				return;
			}
			Ownership.Refresh(val);
			Vector3 position2 = ((Component)drop).transform.position;
			drop.Load();
			itemData = drop.m_itemData;
			int stack = itemData.m_stack;
			if (stack <= 0 || !val.GetInventory().HaveItem(name, false))
			{
				return;
			}
			int num2 = Transfer.AddCounted(val.GetInventory(), itemData, stack);
			if (num2 <= 0)
			{
				_cOther++;
				return;
			}
			_cStored++;
			_receivers.Add(val);
			List<string> list = new List<string>(Groups.Of((Component)(object)val));
			list.Sort(StringComparer.OrdinalIgnoreCase);
			PickupFx.Fly(position2, val, (list.Count > 0) ? list[0] : "");
			if (num2 >= stack)
			{
				ZNetScene.instance.Destroy(((Component)drop).gameObject);
			}
			else
			{
				itemData.m_stack = stack - num2;
				SaveFn?.Invoke(drop);
			}
			if (Plugin.LogTransfers.Value)
			{
				Plugin.Log.LogInfo((object)($"[Ground] {num2}x {Transfer.Name(name)} -> {Transfer.Describe(val)}" + ((num2 < stack) ? $" ({stack - num2} left on the ground, chest full)" : "")));
			}
		}

		private static double Age(ItemDrop drop)
		{
			if (AgeFn == null)
			{
				return 2.0;
			}
			try
			{
				return AgeFn(drop);
			}
			catch
			{
				return 2.0;
			}
		}
	}
	internal static class GroupMenu
	{
		private enum Kind
		{
			Containers,
			Stations,
			Smelters,
			Cooking,
			Fires
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static WindowFunction <0>__Draw;

			public static Func<string, string> <1>__Name;
		}

		private static int _closedFrame = -10;

		private const int WindowId = 1262700103;

		private const float ObjectWidth = 380f;

		private const float SideWidth = 96f;

		private const float OverviewWidth = 260f;

		private static Rect _rect = new Rect(0f, 0f, 380f, 10f);

		private static bool _needsCenter = true;

		private static ZNetView _target;

		private static Component _targetComp;

		private static string _targetName;

		private static Player _player;

		private static string _playerName;

		private static bool _hadPlayer;

		private static string _newGroup = "";

		private static bool _hadTarget;

		private static readonly float[] Radii = new float[3] { 3f, 6f, 10f };

		private static int _radiusIndex = 1;

		private static string _defaultEdit;

		private static readonly Dictionary<string, KeyValuePair<bool, float>> Optimistic = new Dictionary<string, KeyValuePair<bool, float>>(StringComparer.OrdinalIgnoreCase);

		private static MethodInfo _localizeMethod;

		private static Func<object> _localizationInstance;

		private static bool _localizeLookedUp;

		private static GUIStyle _hint;

		private static bool _manage;

		private static string _renaming;

		private static string _renameText = "";

		private static string _deleting;

		private static readonly KeyValuePair<Kind, string>[] Kinds = new KeyValuePair<Kind, string>[5]
		{
			new KeyValuePair<Kind, string>(Kind.Containers, "Containers"),
			new KeyValuePair<Kind, string>(Kind.Stations, "Crafting stations"),
			new KeyValuePair<Kind, string>(Kind.Smelters, "Smelters & machines"),
			new KeyValuePair<Kind, string>(Kind.Cooking, "Cooking & brewing"),
			new KeyValuePair<Kind, string>(Kind.Fires, "Fires & lights")
		};

		private static int _nearbyFrame = -1;

		private static readonly Dictionary<Kind, List<Component>> _nearby = new Dictionary<Kind, List<Component>>();

		private static bool _planOptimistic;

		private static float _planOptimisticTime = -10f;

		private static GUIStyle _wrapToggle;

		private static bool _autoOptimistic;

		private static float _autoOptimisticTime = -10f;

		private static string _orderText = "";

		private static int _orderOptimistic = -1;

		private static float _orderOptimisticTime = -10f;

		private static bool _keepOneOptimistic;

		private static float _keepOneOptimisticTime = -10f;

		private static bool _dropOptimistic;

		private static float _dropOptimisticTime = -10f;

		public static bool IsOpen { get; private set; }

		public static bool BlockingInput
		{
			get
			{
				if (!IsOpen)
				{
					return Time.frameCount <= _closedFrame + 1;
				}
				return true;
			}
		}

		private static float Width
		{
			get
			{
				if (!((Object)(object)_target != (Object)null) && !((Object)(object)_player != (Object)null))
				{
					return 260f;
				}
				return 380f;
			}
		}

		private static float Scale
		{
			get
			{
				float num = Plugin.UiScale.Value;
				if (num <= 0.01f)
				{
					num = Mathf.Max(1f, (float)Screen.height / 1080f);
				}
				return Mathf.Clamp(num, 0.5f, 4f);
			}
		}

		public static void Update()
		{
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: 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_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			KeyboardShortcut value;
			if (IsOpen)
			{
				bool flag = (_hadTarget && ((Object)(object)_target == (Object)null || !_target.IsValid())) || (_hadPlayer && (Object)(object)_player == (Object)null);
				bool flag2 = ((Object)(object)_target != (Object)null && (Object)(object)localPlayer != (Object)null && Vector3.Distance(((Component)localPlayer).transform.position, ((Component)_target).transform.position) > localPlayer.m_maxInteractDistance + 2f) || ((Object)(object)_player != (Object)null && (Object)(object)localPlayer != (Object)null && Vector3.Distance(((Component)localPlayer).transform.position, ((Component)_player).transform.position) > 10f);
				if ((Object)(object)localPlayer == (Object)null || Input.GetKeyDown((KeyCode)27) || flag || flag2)
				{
					Close();
					return;
				}
				value = Plugin.MenuKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown() && GUIUtility.keyboardControl == 0)
				{
					Close();
				}
			}
			else if (!((Object)(object)localPlayer == (Object)null) && Plugin.ModEnabled.Value && !IsTypingOrInMenu())
			{
				value = Plugin.MenuKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					Open(localPlayer);
				}
			}
		}

		private static bool IsTypingOrInMenu()
		{
			if ((!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !TextInput.IsVisible() && !InventoryGui.IsVisible() && !Minimap.IsOpen() && !Menu.IsVisible())
			{
				return StoreGui.IsVisible();
			}
			return true;
		}

		private static void Open(Player p)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			_target = null;
			_targetComp = null;
			_targetName = null;
			_player = null;
			GameObject val = ((Humanoid)p).GetHoverObject();
			Player val2 = (((Object)(object)val != (Object)null) ? val.GetComponentInParent<Player>() : null);
			if ((Object)(object)val2 == (Object)null)
			{
				Character hoverCreature = p.GetHoverCreature();
				Player val3 = (Player)(object)((hoverCreature is Player) ? hoverCreature : null);
				if (val3 != null && Vector3.Distance(((Component)p).transform.position, ((Component)val3).transform.position) < 8f)
				{
					val2 = val3;
				}
			}
			if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)p)
			{
				_player = val2;
				_playerName = val2.GetPlayerName();
				val = null;
			}
			if ((Object)(object)val != (Object)null)
			{
				Component val4 = FindTaggable(val);
				if ((Object)(object)val4 != (Object)null)
				{
					_target = Util.NView(val4);
					_targetComp = val4;
					Piece componentInParent = val4.GetComponentInParent<Piece>();
					_targetName = (((Object)(object)componentInParent != (Object)null) ? Localize(componentInParent.m_name) : ((Object)val4.gameObject).name.Replace("(Clone)", ""));
				}
				else
				{
					Plugin.Log.LogInfo((object)("[Menu] Looking at something with no chest/station/smelter: " + Describe(val)));
				}
			}
			if ((Object)(object)_target != (Object)null && !_target.IsValid())
			{
				_target = null;
			}
			_hadTarget = (Object)(object)_target != (Object)null;
			_hadPlayer = (Object)(object)_player != (Object)null;
			Optimistic.Clear();
			_autoOptimisticTime = -10f;
			_orderOptimisticTime = -10f;
			_keepOneOptimisticTime = -10f;
			_dropOptimisticTime = -10f;
			_orderText = "";
			_renaming = null;
			_deleting = null;
			_newGroup = "";
			_defaultEdit = null;
			_needsCenter = true;
			IsOpen = true;
		}

		internal static Component FindTaggable(GameObject hover)
		{
			if ((Object)(object)hover == (Object)null)
			{
				return null;
			}
			Component componentInParent = (Component)(object)hover.GetComponentInParent<Container>();
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)hover.GetComponentInParent<Smelter>();
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)hover.GetComponentInParent<CraftingStation>();
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)hover.GetComponentInParent<CookingStation>();
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)hover.GetComponentInParent<Fermenter>();
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)hover.GetComponentInParent<Fireplace>();
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)hover.GetComponentInParent<ShieldGenerator>();
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)hover.GetComponentInParent<Turret>();
			}
			Type groupableTable = EpicLootCompat.GroupableTable;
			if ((Object)(object)componentInParent == (Object)null && groupableTable != null)
			{
				componentInParent = hover.GetComponentInParent(groupableTable);
			}
			if ((Object)(object)componentInParent != (Object)null)
			{
				return componentInParent;
			}
			ZNetView componentInParent2 = hover.GetComponentInParent<ZNetView>();
			if ((Object)(object)componentInParent2 == (Object)null)
			{
				return null;
			}
			componentInParent = (Component)(object)((Component)componentInParent2).GetComponentInChildren<Smelter>(true);
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)((Component)componentInParent2).GetComponentInChildren<CraftingStation>(true);
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)((Component)componentInParent2).GetComponentInChildren<CookingStation>(true);
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)((Component)componentInParent2).GetComponentInChildren<Fermenter>(true);
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)((Component)componentInParent2).GetComponentInChildren<Fireplace>(true);
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)((Component)componentInParent2).GetComponentInChildren<ShieldGenerator>(true);
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)((Component)componentInParent2).GetComponentInChildren<Turret>(true);
			}
			if ((Object)(object)componentInParent == (Object)null && groupableTable != null)
			{
				componentInParent = ((Component)componentInParent2).GetComponentInChildren(groupableTable, true);
			}
			if ((Object)(object)componentInParent == (Object)null)
			{
				componentInParent = (Component)(object)((Component)componentInParent2).GetComponentInChildren<Container>(true);
			}
			return componentInParent;
		}

		private static string Describe(GameObject go)
		{
			StringBuilder stringBuilder = new StringBuilder();
			Transform val = go.transform;
			int num = 0;
			while ((Object)(object)val != (Object)null && num < 6)
			{
				if (num > 0)
				{
					stringBuilder.Append(" <- ");
				}
				stringBuilder.Append(((Object)val).name).Append(" [");
				Component[] components = ((Component)val).GetComponents<Component>();
				foreach (Component val2 in components)
				{
					if ((Object)(object)val2 != (Object)null && !(val2 is Transform))
					{
						stringBuilder.Append(((object)val2).GetType().Name).Append(' ');
					}
				}
				stringBuilder.Append(']');
				num++;
				val = val.parent;
			}
			return stringBuilder.ToString();
		}

		private static void Close()
		{
			IsOpen = false;
			_closedFrame = Time.frameCount;
			_target = null;
			_targetComp = null;
			_player = null;
			GUIUtility.keyboardControl = 0;
		}

		internal static string Localize(string token)
		{
			try
			{
				if (!_localizeLookedUp)
				{
					_localizeLookedUp = true;
					Type type = AccessTools.TypeByName("Localization");
					if (type != null)
					{
						_localizeMethod = AccessTools.Method(type, "Localize", new Type[1] { typeof(string) }, (Type[])null);
						PropertyInfo prop = AccessTools.Property(type, "instance");
						FieldInfo field = AccessTools.Field(type, "m_instance");
						if (prop != null)
						{
							_localizationInstance = () => prop.GetValue(null, null);
						}
						else if (field != null)
						{
							_localizationInstance = () => field.GetValue(null);
						}
					}
				}
				object obj = _localizationInstance?.Invoke();
				if (_localizeMethod != null && obj != null)
				{
					return (string)_localizeMethod.Invoke(obj, new object[1] { token });
				}
			}
			catch
			{
			}
			return token.TrimStart(new char[1] { '$' });
		}

		public static void OnGUI()
		{
			//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)
			//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)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Expected O, but got Unknown
			if (IsOpen)
			{
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
				float scale = Scale;
				Matrix4x4 matrix = GUI.matrix;
				GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(scale, scale, 1f));
				float num = (float)Screen.width / scale;
				float num2 = (float)Screen.height / scale;
				if (_needsCenter)
				{
					_rect = new Rect(num / 2f - Width / 2f, num2 * 0.25f, Width, 10f);
					_needsCenter = false;
				}
				((Rect)(ref _rect)).width = Width;
				((Rect)(ref _rect)).height = 10f;
				string text = (((Object)(object)_player != (Object)null) ? "Add to groups" : (((Object)(object)_target != (Object)null) ? _targetName : "Craft From Container Groups"));
				Rect rect = _rect;
				object obj = <>O.<0>__Draw;
				if (obj == null)
				{
					WindowFunction val = Draw;
					<>O.<0>__Draw = val;
					obj = (object)val;
				}
				_rect = GUILayout.Window(1262700103, rect, (WindowFunction)obj, text, Array.Empty<GUILayoutOption>());
				((Rect)(ref _rect)).x = Mathf.Clamp(((Rect)(ref _rect)).x, 0f, Mathf.Max(0f, num - ((Rect)(ref _rect)).width));
				((Rect)(ref _rect)).y = Mathf.Clamp(((Rect)(ref _rect)).y, 0f, Mathf.Max(0f, num2 - 40f));
				GUI.matrix = matrix;
			}
		}

		private static void Draw(int id)
		{
			//IL_0011: 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_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			if (_hint == null)
			{
				_hint = new GUIStyle(GUI.skin.label)
				{
					wordWrap = true,
					richText = true
				};
			}
			if ((Object)(object)_player != (Object)null)
			{
				DrawPlayer();
			}
			else if ((Object)(object)_target != (Object)null)
			{
				DrawObject();
			}
			else
			{
				DrawOverview();
			}
			GUI.DragWindow();
		}

		private static void DrawOverview()
		{
			if (GUILayout.Button((Plugin.ShowMarkers.Value ? "[x]" : "[ ]") + " Show group markers", Array.Empty<GUILayoutOption>()))
			{
				Plugin.ShowMarkers.Value = !Plugin.ShowMarkers.Value;
				Visuals.ForceRefresh();
			}
			if (GUILayout.Button((Plugin.ShowLinks.Value ? "[x]" : "[ ]") + " Show what pulls from what", Array.Empty<GUILayoutOption>()))
			{
				Plugin.ShowLinks.Value = !Plugin.ShowLinks.Value;
				Visuals.ForceRefresh();
			}
			if (GUILayout.Button((Plugin.GroundPickupEnabled.Value ? "[x]" : "[ ]") + " Chests pick up matching items nearby", Array.Empty<GUILayoutOption>()))
			{
				Plugin.GroundPickupEnabled.Value = !Plugin.GroundPickupEnabled.Value;
			}
			GUILayout.Space(6f);
			GUILayout.Label("New chests I build go in:", _hint, Array.Empty<GUILayoutOption>());
			if (_defaultEdit == null)
			{
				_defaultEdit = string.Join(", ", Groups.Parse(Plugin.DefaultGroups.Value).Select(Groups.Name).ToArray());
			}
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			_defaultEdit = GUILayout.TextField(_defaultEdit, 48, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			if (GUILayout.Button("Save", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }))
			{
				List<string> list = new List<string>();
				string[] array = _defaultEdit.Split(new char[1] { ',' });
				for (int i = 0; i < array.Length; i++)
				{
					string text = Groups.Create(array[i]);
					if (text.Length > 0 && !list.Contains(text))
					{
						list.Add(text);
					}
				}
				Plugin.DefaultGroups.Value = string.Join(",", list.ToArray());
				_defaultEdit = null;
				GUIUtility.keyboardControl = 0;
			}
			GUILayout.EndHorizontal();
			HashSet<string> hashSet = Groups.Parse(Plugin.DefaultGroups.Value);
			if (hashSet.Count > 0)
			{
				List<Container> list2 = new List<Container>();
				foreach (Container item in Chests.AllLoaded())
				{
					if (Groups.BuiltByMe((Component)(object)item) && !Groups.Of((Component)(object)item).IsSupersetOf(hashSet))
					{
						list2.Add(item);
					}
				}
				GUI.enabled = list2.Count > 0;
				if (GUILayout.Button($"Add my {list2.Count} loaded chests to it", Array.Empty<GUILayoutOption>()))
				{
					int num = Groups.Bulk(list2.Cast<Component>(), hashSet, add: true);
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)2, $"Updated {num} chests", 0, (Sprite)null, false);
					}
					Chests.InvalidateCache();
					Visuals.ForceRefresh();
				}
				GUI.enabled = true;
			}
			GUILayout.Space(6f);
			if (GUILayout.Button((_manage ? "[-]" : "[+]") + " Manage groups", Array.Empty<GUILayoutOption>()))
			{
				_manage = !_manage;
				_renaming = null;
				_deleting = null;
			}
			if (_manage)
			{
				DrawManage();
			}
		}

		private static void DrawManage()
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Invalid comparison between Unknown and I4
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Invalid comparison between Unknown and I4
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = Groups.Known();
			if (list.Count == 0)
			{
				GUILayout.Label("No groups yet.", _hint, Array.Empty<GUILayoutOption>());
				return;
			}
			string text = null;
			string text2 = null;
			foreach (string item in list)
			{
				string text3 = Groups.Name(item);
				string text4 = Groups.SharedBy(item);
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (text4 != null)
				{
					GUILayout.Label("<color=#" + ColorUtility.ToHtmlStringRGB(Brighten(Visuals.ColorFor(item))) + ">" + text3 + "</color> <color=#999999>from " + text4 + "</color>", _hint, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
				}
				else if (_renaming == item)
				{
					GUI.SetNextControlName("cfcg_rename");
					_renameText = GUILayout.TextField(_renameText, 24, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
					bool flag = (int)Event.current.type == 4 && (int)Event.current.keyCode == 13 && GUI.GetNameOfFocusedControl() == "cfcg_rename";
					if (GUILayout.Button("OK", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(36f) }) || flag)
					{
						text = item;
					}
					if (GUILayout.Button("X", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(26f) }))
					{
						_renaming = null;
					}
				}
				else if (_deleting == item)
				{
					GUILayout.Label("Delete " + text3 + "?", _hint, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
					if (GUILayout.Button("Yes", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }))
					{
						text2 = item;
					}
					if (GUILayout.Button("No", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(36f) }))
					{
						_deleting = null;
					}
				}
				else
				{
					Color c = Visuals.ColorFor(item);
					string text5 = (Groups.IsPrivate(item) ? "" : " <color=#999999>(public)</color>");
					GUILayout.Label("<color=#" + ColorUtility.ToHtmlStringRGB(Brighten(c)) + ">" + text3 + "</color>" + text5, _hint, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
					if (GUILayout.Button("Rename", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(62f) }))
					{
						_renaming = item;
						_renameText = text3;
						_deleting = null;
					}
					if (GUILayout.Button("Delete", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) }))
					{
						_deleting = item;
						_renaming = null;
					}
				}
				GUILayout.EndHorizontal();
			}
			if (Groups.HasPublicGroups())
			{
				GUILayout.Label("<color=#999999>Public groups can be seen and used by everyone with this mod.</color>", _hint, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Make my public groups private", Array.Empty<GUILayoutOption>()))
				{
					int num = Groups.MakePublicGroupsPrivate();
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)2, $"Made {num} groups private", 0, (Sprite)null, false);
					}
					_defaultEdit = null;
				}
			}
			if (text != null)
			{
				string text6 = _renameText.Trim();
				if (text6.Length > 0 && !text6.Equals(Groups.Name(text), StringComparison.OrdinalIgnoreCase))
				{
					bool flag2 = Groups.IsMine(text);
					int num2 = Groups.Rename(text, text6);
					Player localPlayer2 = Player.m_localPlayer;
					if (localPlayer2 != null)
					{
						((Character)localPlayer2).Message((MessageType)2, flag2 ? ("Renamed to " + text6) : $"Renamed on {num2} things nearby", 0, (Sprite)null, false);
					}
					_defaultEdit = null;
				}
				_renaming = null;
				GUIUtility.keyboardControl = 0;
			}
			if (text2 != null)
			{
				int num3 = Groups.Rename(text2, "");
				Player localPlayer3 = Player.m_localPlayer;
				if (localPlayer3 != null)
				{
					((Character)localPlayer3).Message((MessageType)2, $"Deleted from {num3} things nearby", 0, (Sprite)null, false);
				}
				_deleting = null;
				_defaultEdit = null;
			}
		}

		private static void DrawNearbyRow(HashSet<string> current)
		{
			//IL_00ce: 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_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (string item in Groups.Known())
			{
				if (IsIn(current, item))
				{
					hashSet.Add(item);
				}
			}
			GUILayout.Space(8f);
			if (hashSet.Count == 0)
			{
				GUILayout.Label("<color=#999999>Give this a group to add nearby things to it.</color>", _hint, Array.Empty<GUILayoutOption>());
				return;
			}
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("Add nearby to " + string.Join(", ", hashSet.Select(Groups.Name).ToArray()) + ":", _hint, Array.Empty<GUILayoutOption>());
			for (int i = 0; i < Radii.Length; i++)
			{
				Color backgroundColor = GUI.backgroundColor;
				if (i == _radiusIndex)
				{
					GUI.backgroundColor = new Color(0.35f, 0.85f, 0.45f);
				}
				if (GUILayout.Button($"{Radii[i]:0}m", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }))
				{
					_radiusIndex = i;
				}
				GUI.backgroundColor = backgroundColor;
			}
			GUILayout.EndHorizontal();
			KeyValuePair<Kind, string>[] kinds = Kinds;
			for (int j = 0; j < kinds.Length; j++)
			{
				KeyValuePair<Kind, string> keyValuePair = kinds[j];
				Kind key = keyValuePair.Key;
				string value = keyValuePair.Value;
				List<Component> list = Nearby(key);
				int num = 0;
				int num2 = 0;
				foreach (Component item2 in list)
				{
					HashSet<string> hashSet2 = Groups.Of(item2);
					if (!hashSet2.IsSupersetOf(hashSet))
					{
						num++;
					}
					if (hashSet2.Overlaps(hashSet))
					{
						num2++;
					}
				}
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label($"{value} ({list.Count})", _hint, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
				GUI.enabled = num > 0;
				if (GUILayout.Button($"+{num}", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) }))
				{
					Report(Groups.Bulk(list, hashSet, add: true), "Added", value);
				}
				GUI.enabled = num2 > 0;
				if (GUILayout.Button($"-{num2}", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) }))
				{
					Report(Groups.Bulk(list, hashSet, add: false), "Removed", value);
				}
				GUI.enabled = true;
				GUILayout.EndHorizontal();
			}
			bool flag = GUILayout.Toggle(Plugin.BulkOnlyMine.Value, " Only things I built", Array.Empty<GUILayoutOption>());
			if (flag != Plugin.BulkOnlyMine.Value)
			{
				Plugin.BulkOnlyMine.Value = flag;
			}
		}

		private static List<Component> Nearby(Kind kind)
		{
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			if (_nearbyFrame != Time.frameCount)
			{
				_nearbyFrame = Time.frameCount;
				_nearby.Clear();
			}
			if (_nearby.TryGetValue(kind, out var value))
			{
				return value;
			}
			List<Component> list = new List<Component>();
			_nearby[kind] = list;
			if ((Object)(object)_target == (Object)null)
			{
				return list;
			}
			IEnumerable<Component> enumerable;
			switch (kind)
			{
			case Kind.Containers:
				enumerable = Chests.AllLoaded().Cast<Component>();
				break;
			case Kind.Stations:
				Registry.Stations.RemoveWhere((CraftingStation x) => (Object)(object)x == (Object)null);
				enumerable = Registry.Stations.Cast<Component>();
				break;
			case Kind.Smelters:
				Registry.Smelters.RemoveWhere((MonoBehaviour x) => (Object)(object)x == (Object)null);
				enumerable = Registry.Smelters.Cast<Component>();
				break;
			case Kind.Cooking:
				Registry.Cooking.RemoveWhere((MonoBehaviour x) => (Object)(object)x == (Object)null);
				enumerable = Registry.Cooking.Cast<Component>();
				break;
			default:
				Registry.Fires.RemoveWhere((MonoBehaviour x) => (Object)(object)x == (Object)null);
				enumerable = Registry.Fires.Cast<Component>();
				break;
			}
			Vector3 position = ((Component)_target).transform.position;
			float num = Radii[Mathf.Clamp(_radiusIndex, 0, Radii.Length - 1)];
			foreach (Component item in enumerable)
			{
				if (!((Object)(object)item == (Object)null) && !((Object)(object)Util.NView(item) == (Object)(object)_target) && !(Vector3.Distance(position, item.transform.position) > num) && !((Object)(object)Util.PieceOf(item) == (Object)null) && (!Plugin.BulkOnlyMine.Value || Groups.BuiltByMe(item)))
				{
					list.Add(item);
				}
			}
			return list;
		}

		private static void Report(int n, string verb, string what)
		{
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message((MessageType)2, $"{verb}: {n} {what.ToLowerInvariant()}", 0, (Sprite)null, false);
			}
			Chests.InvalidateCache();
			Visuals.ForceRefresh();
			_nearbyFrame = -1;
		}

		private static void DrawObject()
		{
			//IL_0060: 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_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Invalid comparison between Unknown and I4
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Invalid comparison between Unknown and I4
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bb: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> current = Groups.Of(_target);
			List<string> list = Groups.Known();
			int num = Mathf.Max(1, Plugin.ButtonsPerRow.Value);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(260f) });
			if (list.Count == 0)
			{
				GUILayout.Label("No groups yet: type a name below.", _hint, Array.Empty<GUILayoutOption>());
			}
			Color backgroundColor = GUI.backgroundColor;
			for (int i = 0; i < list.Count; i += num)
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				for (int j = i; j < i + num; j++)
				{
					if (j >= list.Count)
					{
						GUILayout.Label("", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
						continue;
					}
					string text = list[j];
					bool flag = IsIn(current, text);
					GUI.backgroundColor = (Color)(flag ? new Color(0.35f, 0.85f, 0.45f) : backgroundColor);
					GUI.enabled = Groups.CanEdit(text, _targetComp);
					if (GUILayout.Button(Groups.Name(text), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
					{
						SetMembership(text, !flag);
					}
					GUI.enabled = true;
				}
				GUI.backgroundColor = backgroundColor;
				GUILayout.EndHorizontal();
			}
			GUI.backgroundColor = backgroundColor;
			GUILayout.Space(4f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUI.SetNextControlName("cfcg_new");
			_newGroup = GUILayout.TextField(_newGroup, 24, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			bool flag2 = (int)Event.current.type == 4 && (int)Event.current.keyCode == 13 && GUI.GetNameOfFocusedControl() == "cfcg_new";
			if ((GUILayout.Button("+ New", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }) || flag2) && _newGroup.Trim().Length > 0)
			{
				string text2 = Groups.Create(_newGroup);
				if (text2.Length > 0)
				{
					SetMembership(text2, on: true);
				}
				_newGroup = "";
				GUIUtility.keyboardControl = 0;
			}
			GUILayout.EndHorizontal();
			DrawKeepFilled();
			DrawLeaveOne();
			DrawDropFood();
			DrawNearbyRow(current);
			GUILayout.EndVertical();
			GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(96f) });
			GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
			GUILayout.Label("In:", _hint, Array.Empty<GUILayoutOption>());
			bool flag3 = false;
			foreach (string item in list)
			{
				if (IsIn(current, item))
				{
					flag3 = true;
					Color c = Visuals.ColorFor(item);
					GUILayout.Label("<color=#" + ColorUtility.ToHtmlStringRGB(Brighten(c)) + ">" + Groups.Name(item) + "</color>", _hint, Array.Empty<GUILayoutOption>());
				}
			}
			if (!flag3)
			{
				GUILayout.Label("<color=#999999>none</color>", _hint, Array.Empty<GUILayoutOption>());
			}
			if (Groups.IsWaiting(_target))
			{
				string text3 = Chests.PlayerName(_target.GetZDO().GetOwner());
				GUILayout.Label("<color=#d8b04a>Saves when " + text3 + " leaves</color>", _hint, Array.Empty<GUILayoutOption>());
			}
			GUILayout.EndVertical();
			DrawPlanBuildSwitch();
			GUILayout.EndVertical();
			GUILayout.EndHorizontal();
		}

		private static void DrawPlanBuildSwitch()
		{
			//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_003c: Expected O, but got Unknown
			if (!PlanBuildCompat.Active || (Object)(object)((Component)_target).GetComponent<Container>() == (Object)null)
			{
				return;
			}
			if (_wrapToggle == null)
			{
				_wrapToggle = new GUIStyle(GUI.skin.toggle)
				{
					wordWrap = true
				};
			}
			bool flag = ((Time.time - _planOptimisticTime < 3f) ? _planOptimistic : (Groups.GetInt(_target, "cfcg_planbuild") != 0));
			GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
			bool flag2 = GUILayout.Toggle(flag, " Pullable from PlanBuild", _wrapToggle, Array.Empty<GUILayoutOption>());
			GUILayout.EndVertical();
			if (flag2 != flag)
			{
				if (Groups.SetInt(_target, "cfcg_planbuild", flag2 ? 1 : 0))
				{
					_planOptimisticTime = -10f;
					return;
				}
				_planOptimistic = flag2;
				_planOptimisticTime = Time.time;
			}
		}

		private static void DrawPlayer()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Invalid comparison between Unknown and I4
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Invalid comparison between Unknown and I4
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			long playerID = _player.GetPlayerID();
			List<string> list = Groups.MyPrivate();
			int num = Mathf.Max(1, Plugin.ButtonsPerRow.Value);
			GUILayout.Label("<b>" + _playerName + "</b>", _hint, Array.Empty<GUILayoutOption>());
			if (list.Count == 0)
			{
				GUILayout.Label("No private groups yet: type a name below.", _hint, Array.Empty<GUILayoutOption>());
			}
			string text = null;
			Color backgroundColor = GUI.backgroundColor;
			for (int i = 0; i < list.Count; i += num)
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				for (int j = i; j < i + num; j++)
				{
					if (j >= list.Count)
					{
						GUILayout.Label("", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
						continue;
					}
					string text2 = list[j];
					GUI.backgroundColor = (Color)(Groups.IsSharedWith(text2, playerID) ? new Color(0.35f, 0.85f, 0.45f) : backgroundColor);
					if (GUILayout.Button(Groups.Name(text2), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
					{
						text = text2;
					}
				}
				GUI.backgroundColor = backgroundColor;
				GUILayout.EndHorizontal();
			}
			GUI.backgroundColor = backgroundColor;
			GUILayout.Space(4f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUI.SetNextControlName("cfcg_new");
			_newGroup = GUILayout.TextField(_newGroup, 24, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			bool flag = (int)Event.current.type == 4 && (int)Event.current.keyCode == 13 && GUI.GetNameOfFocusedControl() == "cfcg_new";
			string text3 = null;
			if ((GUILayout.Button("+ New", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }) || flag) && _newGroup.Trim().Length > 0)
			{
				text3 = Groups.Create(_newGroup);
				_newGroup = "";
				GUIUtility.keyboardControl = 0;
			}
			GUILayout.EndHorizontal();
			if (Groups.HasPublicGroups())
			{
				GUILayout.Label("<color=#999999>Public groups are already shared with everyone. To share them one by one, make them private under Manage groups (G at nothing).</color>", _hint, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Label("<color=#999999>" + _playerName + " needs this mod to use them.</color>", _hint, Array.Empty<GUILayoutOption>());
			if (text != null)
			{
				bool flag2 = !Groups.IsSharedWith(text, playerID);
				Sharing.SetShared(_player, text, flag2);
				Player localPlayer = Player.m_localPlayer;
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, flag2 ? ("Shared " + Groups.Name(text) + " with " + _playerName) : ("Stopped sharing " + Groups.Name(text) + " with " + _playerName), 0, (Sprite)null, false);
				}
			}
			if (text3 != null && Groups.IsMine(text3) && !Groups.IsSharedWith(text3, playerID))
			{
				Sharing.SetShared(_player, text3, on: true);
				Player localPlayer2 = Player.m_localPlayer;
				if (localPlayer2 != null)
				{
					((Character)localPlayer2).Message((MessageType)2, "Shared " + Groups.Name(text3) + " with " + _playerName, 0, (Sprite)null, false);
				}
			}
		}

		private static void DrawKeepFilled()
		{
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Invalid comparison between Unknown and I4
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Invalid comparison between Unknown and I4
			if (!Machines.CanKeepFilled(_target))
			{
				return;
			}
			bool flag = ((Time.time - _autoOptimisticTime < 3f) ? _autoOptimistic : Groups.AutoFill(_target));
			bool flag2 = GUILayout.Toggle(flag, " Keep filled from chests", Array.Empty<GUILayoutOption>());
			if (flag2 != flag)
			{
				if (Groups.SetAutoFill(_target, flag2))
				{
					_autoOptimisticTime = -10f;
				}
				else
				{
					_autoOptimistic = flag2;
					_autoOptimisticTime = Time.time;
				}
			}
			int num = ((Time.time - _orderOptimisticTime < 3f) ? _orderOptimistic : Machines.OrderLeft(_target));
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (num > 0)
			{
				GUILayout.Label($"Ordered: {num} more to pull", _hint, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
				if (GUILayout.Button("Cancel", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }))
				{
					SetOrder(0);
				}
			}
			else
			{
				GUILayout.Label("Pull amount:", _hint, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) });
				GUI.SetNextControlName("cfcg_order");
				_orderText = GUILayout.TextField(_orderText, 4, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) });
				_orderText = new string(_orderText.Where((char ch) => char.IsDigit(ch)).ToArray());
				bool flag3 = (int)Event.current.type == 4 && (int)Event.current.keyCode == 13 && GUI.GetNameOfFocusedControl() == "cfcg_order";
				if ((GUILayout.Button("Pull", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) }) || flag3) && int.TryParse(_orderText, out var result) && result > 0)
				{
					SetOrder(Mathf.Min(result, 9999));
					_orderText = "";
					GUIUtility.keyboardControl = 0;
				}
			}
			GUILayout.EndHorizontal();
			if (Groups.Of(_target).Count == 0)
			{
				GUILayout.Label("<color=#999999>Put it in a group to pull from chests.</color>", _hint, Array.Empty<GUILayoutOption>());
			}
		}

		private static void SetOrder(int amount)
		{
			Machines.SetOrder(_target, amount);
			_orderOptimistic = amount;
			_orderOptimisticTime = Time.time;
		}

		private static void DrawLeaveOne()
		{
			if ((Object)(object)((Component)_target).GetComponent<Container>() == (Object)null)
			{
				return;
			}
			bool flag = ((Time.time - _keepOneOptimisticTime < 3f) ? _keepOneOptimistic : (Groups.GetInt(_target, "cfcg_keep1") != 0));
			bool flag2 = GUILayout.Toggle(flag, " Leave 1 of each item in this chest", Array.Empty<GUILayoutOption>());
			if (flag2 != flag)
			{
				if (Groups.SetInt(_target, "cfcg_keep1", flag2 ? 1 : 0))
				{
					_keepOneOptimisticTime = -10f;
				}
				else
				{
					_keepOneOptimistic = flag2;
					_keepOneOptimisticTime = Time.time;
				}
				Chests.InvalidateCache();
			}
		}

		private static void DrawDropFood()
		{
			if ((Object)(object)((Component)_target).GetComponentInChildren<CookingStation>(true) == (Object)null)
			{
				return;
			}
			bool flag = ((Time.time - _dropOptimisticTime < 3f) ? _dropOptimistic : (Groups.GetInt(_target, "cfcg_dropfood") != 0));
			bool flag2 = GUILayout.Toggle(flag, " Drop food when done", Array.Empty<GUILayoutOption>());
			if (flag2 != flag)
			{
				bool flag3 = Groups.SetInt(_target, "cfcg_dropfood", flag2 ? 1 : 0);
				if (flag3)
				{
					_dropOptimisticTime = -10f;
				}
				else
				{
					_dropOptimistic = flag2;
					_dropOptimisticTime = Time.time;
				}
				if (Plugin.LogTransfers.Value)
				{
					Plugin.Log.LogInfo((object)("[Machine] " + Util.At(_targetComp) + ": Drop food when done " + (flag2 ? "on" : "off") + (flag3 ? "" : (" (asked " + Chests.PlayerName(_target.GetZDO().GetOwner()) + "'s game to save it)"))));
				}
			}
		}

		private static Color Brighten(Color c)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			float num = default(float);
			float num2 = default(float);
			float num3 = default(float);
			Color.RGBToHSV(c, ref num, ref num2, ref num3);
			return Color.HSVToRGB(num, Mathf.Min(1f, num2 + 0.1f), 0.95f);
		}

		private static bool IsIn(HashSet<string> current, string g)
		{
			if (Optimistic.TryGetValue(g, out var value) && Time.time - value.Value < 3f)
			{
				return value.Key;
			}
			return current.Contains(g);
		}

		private static void SetMembership(string group, bool on)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Groups.CanEdit(group, _targetComp))
			{
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, "Only on things you built", 0, (Sprite)null, false);
				}
				return;
			}
			Container component = ((Component)_target).GetComponent<Container>();
			if ((Object)(object)component != (Object)null && component.IsInUse())
			{
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, "Someone has that chest open", 0, (Sprite)null, false);
				}
				return;
			}
			List<string> list = new List<string>();
			foreach (string item in Groups.Known())
			{
				if (item.Equals(group, StringComparison.OrdinalIgnoreCase) ? on : IsIn(Groups.Of(_target), item))
				{
					list.Add(item);
				}
			}
			if (Groups.SetAll(_target, list))
			{
				Optimistic.Clear();
			}
			else
			{
				Optimistic[group] = new KeyValuePair<bool, float>(on, Time.time);
			}
			Chests.InvalidateCache();
			Visuals.ForceRefresh();
		}
	}
	internal static class Groups
	{
		private sealed class Waiting
		{
			public ZDOID Id;

			public string Key;

			public string Str;

			public int Num;

			public string BeforeStr;

			public int BeforeNum;

			public float Sent;

			public int Resent;
		}

		private sealed class SharedGroup
		{
			public string Name;

			public long OwnerId;

			public string OwnerName;
		}

		public const string ZdoKey = "cfcg_groups";

		public const string SetGroupRpc = "cfcg_SetGroup";

		public const string AutoFillKey = "cfcg_autofill";

		public const string SetAutoFillRpc = "cfcg_SetAutoFill";

		public const string Keep1Key = "cfcg_keep1";

		public const string OrderKey = "cfcg_order";

		public const string DropFoodKey = "cfcg_dropfood";

		public const string PlanBuildKey = "cfcg_planbuild";

		public const string SetIntRpc = "cfcg_SetInt";

		private static readonly HashSet<string> IntKeys = new HashSet<string> { "cfcg_keep1", "cfcg_order", "cfcg_dropfood", "cfcg_planbuild" };

		private static readonly StringComparer Cmp = StringComparer.OrdinalIgnoreCase;

		private static readonly Dictionary<string, HashSet<string>> ParseCache = new Dictionary<string, HashSet<string>>();

		private static readonly HashSet<string> EmptySet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, Waiting> WaitingWrites = new Dictionary<string, Waiting>();

		private static float _nextWaitCheck;

		private const int MaxPending = 50;

		private static string _opsRaw;

		private static List<KeyValuePair<string, string>> _ops = new List<KeyValuePair<string, string>>();

		private static float _nextSweep;

		public const char PrivateMark = '~';

		private static string _mineRaw;

		private static string _sharedRaw;

		private static string _sharesRaw;

		private static Dictionary<string, string> _mine = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static Dictionary<string, SharedGroup> _shared = new Dictionary<string, SharedGroup>(StringComparer.OrdinalIgnoreCase);

		private static Dictionary<string, HashSet<long>> _shares = new Dictionary<string, HashSet<long>>(StringComparer.OrdinalIgnoreCase);

		public static string Raw(ZNetView nview)
		{
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return "";
			}
			ZDO zDO = nview.GetZDO();
			if (TryWaiting(zDO, "cfcg_groups", out var w))
			{
				return w.Str;
			}
			return RawZdo(zDO);
		}

		private static string RawZdo(ZDO zdo)
		{
			return zdo.GetString("cfcg_groups", "") ?? "";
		}

		public static H