Decompiled source of GearHeim v1.0.0

plugins/GearHeim.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("GearHeim")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("GearHeim")]
[assembly: AssemblyTitle("GearHeim")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace GearHeim
{
	public enum RangeMode
	{
		Workbench,
		Distance
	}
	internal class BaseArea
	{
		private const string WorkbenchName = "$piece_workbench";

		private readonly Vector3 _origin;

		private readonly float _distance;

		private readonly List<CraftingStation> _workbenches;

		public bool IsValid
		{
			get
			{
				if (_workbenches != null)
				{
					return _workbenches.Count > 0;
				}
				return true;
			}
		}

		private BaseArea(Vector3 origin, float distance, List<CraftingStation> workbenches)
		{
			//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)
			_origin = origin;
			_distance = distance;
			_workbenches = workbenches;
		}

		public static BaseArea For(Vector3 position)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (GearHeimPlugin.Mode.Value == RangeMode.Distance)
			{
				return new BaseArea(position, GearHeimPlugin.Range.Value, null);
			}
			return new BaseArea(position, 0f, ConnectedWorkbenches(position));
		}

		public bool Contains(Vector3 point)
		{
			//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_0016: 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 (_workbenches == null)
			{
				return Vector3.Distance(_origin, point) <= _distance;
			}
			return _workbenches.Any((CraftingStation w) => (Object)(object)w != (Object)null && Vector3.Distance(((Component)w).transform.position, point) <= w.GetStationBuildRange());
		}

		public float DistanceFrom(Vector3 point)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return Vector3.Distance(_origin, point);
		}

		public string Describe()
		{
			if (_workbenches != null)
			{
				return "in this base";
			}
			return $"within {GearHeimPlugin.Range.Value:0} m";
		}

		private static List<CraftingStation> ConnectedWorkbenches(Vector3 position)
		{
			//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_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			List<CraftingStation> list = CraftingStation.m_allStations.Where((CraftingStation s) => (Object)(object)s != (Object)null && s.m_name == "$piece_workbench").ToList();
			List<CraftingStation> list2 = list.Where((CraftingStation s) => Vector3.Distance(((Component)s).transform.position, position) <= s.GetStationBuildRange()).ToList();
			Queue<CraftingStation> queue = new Queue<CraftingStation>(list2);
			while (queue.Count > 0)
			{
				CraftingStation val = queue.Dequeue();
				foreach (CraftingStation item in list)
				{
					if (!list2.Contains(item))
					{
						float num = val.GetStationBuildRange() + item.GetStationBuildRange();
						if (Vector3.Distance(((Component)val).transform.position, ((Component)item).transform.position) <= num)
						{
							list2.Add(item);
							queue.Enqueue(item);
						}
					}
				}
			}
			return list2;
		}
	}
	internal static class ChestSupply
	{
		public static int Take(Player player, BaseArea area, LoadoutEntry entry, int worldLevel, int amount, out ItemData template)
		{
			template = null;
			if (amount <= 0)
			{
				return 0;
			}
			int num = 0;
			foreach (Container item in Sources(player, area))
			{
				Inventory inventory = item.GetInventory();
				if (!inventory.m_inventory.Any((ItemData i) => Matches(i, entry, worldLevel)))
				{
					continue;
				}
				if (!item.m_nview.IsOwner())
				{
					item.m_nview.ClaimOwnership();
					item.Load();
				}
				foreach (ItemData item2 in inventory.m_inventory.ToList())
				{
					if (Matches(item2, entry, worldLevel))
					{
						int num2 = Mathf.Min(amount - num, item2.m_stack);
						if (template == null)
						{
							template = item2.Clone();
						}
						inventory.RemoveItem(item2, num2);
						num += num2;
						if (num >= amount)
						{
							return num;
						}
					}
				}
			}
			return num;
		}

		private static bool Matches(ItemData item, LoadoutEntry entry, int worldLevel)
		{
			if ((Object)(object)item.m_dropPrefab != (Object)null && ((Object)item.m_dropPrefab).name == entry.Prefab && item.m_quality == entry.Quality && (worldLevel < 0 || item.m_worldLevel == worldLevel))
			{
				return Tags.LoadoutOf(item) == null;
			}
			return false;
		}

		private static IEnumerable<Container> Sources(Player player, BaseArea area)
		{
			long playerId = player.GetPlayerID();
			return from c in Object.FindObjectsByType<Container>((FindObjectsSortMode)0)
				where IsUsable(c, playerId) && area.Contains(((Component)c).transform.position)
				orderby area.DistanceFrom(((Component)c).transform.position)
				select c;
		}

		private static bool IsUsable(Container c, long playerId)
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)c == (Object)null || (Object)(object)c.m_nview == (Object)null || !c.m_nview.IsValid())
			{
				return false;
			}
			if (c.GetInventory() == null)
			{
				return false;
			}
			if ((Object)(object)((Component)c).GetComponent<TombStone>() != (Object)null)
			{
				return false;
			}
			if ((Object)(object)c.m_rootObjectOverride != (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)c).GetComponentInParent<Ship>() != (Object)null || (Object)(object)((Component)c).GetComponentInParent<Vagon>() != (Object)null)
			{
				return false;
			}
			if (c.IsInUse() || c.m_nview.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1)
			{
				return false;
			}
			if (!c.CheckAccess(playerId))
			{
				return false;
			}
			if (c.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)c).transform.position, 0f, false, false))
			{
				return false;
			}
			return true;
		}
	}
	[BepInPlugin("mbaya.GearHeim", "GearHeim", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class GearHeimPlugin : BaseUnityPlugin
	{
		public const string Guid = "mbaya.GearHeim";

		public const string Name = "GearHeim";

		public const string Version = "1.0.0";

		public const string ChestPrefab = "piece_gearheim_loadoutchest";

		private const string ChestBasePrefab = "piece_chest";

		internal static ManualLogSource Log;

		internal static ConfigEntry<RangeMode> Mode;

		internal static ConfigEntry<float> Range;

		internal static ConfigEntry<bool> RefillFromChests;

		internal static ConfigEntry<KeyboardShortcut> MenuKey;

		internal static ConfigEntry<KeyboardShortcut>[] QuickKeys;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Mode = ((BaseUnityPlugin)this).Config.Bind<RangeMode>("General", "RangeMode", RangeMode.Workbench, "Where GearHeim works. Workbench = inside your base: within build range of the workbench you're near (workbenches with overlapping ranges count as one base). Distance = within 'Range' meters of you.");
			Range = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Range", 50f, "Used when RangeMode is Distance: how far (in meters, straight line including height) Loadout Chests and restocking chests can be.");
			RefillFromChests = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RefillFromChests", true, "Top up food, potions, ammo and other stackables to their saved amounts from ordinary chests in the same base.");
			MenuKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "OpenMenu", new KeyboardShortcut((KeyCode)108, Array.Empty<KeyCode>()), "Open the loadout menu (works anywhere in a base with a Loadout Chest).");
			QuickKeys = new ConfigEntry<KeyboardShortcut>[5];
			for (int i = 0; i < QuickKeys.Length; i++)
			{
				QuickKeys[i] = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", $"SwitchToLoadout{i + 1}", new KeyboardShortcut((KeyCode)(49 + i), (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), $"Switch straight to loadout #{i + 1} as listed in the loadout menu.");
			}
			PrefabManager.OnVanillaPrefabsAvailable += AddLoadoutChest;
			_harmony = new Harmony("mbaya.GearHeim");
			_harmony.PatchAll(typeof(GearHeimPlugin).Assembly);
			Log.LogInfo((object)"GearHeim 1.0.0 loaded");
		}

		private void AddLoadoutChest()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			PrefabManager.OnVanillaPrefabsAvailable -= AddLoadoutChest;
			PieceConfig val = new PieceConfig();
			val.Name = "Loadout Chest";
			val.Description = "Holds your gear loadouts. Switch loadouts from anywhere within range.";
			val.PieceTable = "Hammer";
			val.Category = "Furniture";
			val.Requirements = (RequirementConfig[])(object)new RequirementConfig[2]
			{
				new RequirementConfig("FineWood", 10, 0, true),
				new RequirementConfig("Bronze", 2, 0, true)
			};
			CustomPiece val2 = new CustomPiece("piece_gearheim_loadoutchest", "piece_chest", val);
			GameObject piecePrefab = val2.PiecePrefab;
			if ((Object)(object)piecePrefab == (Object)null)
			{
				Log.LogError((object)"Could not clone piece_chest; Loadout Chest disabled.");
				return;
			}
			Container component = piecePrefab.GetComponent<Container>();
			if ((Object)(object)component != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component);
			}
			piecePrefab.AddComponent<LoadoutChest>();
			PieceManager.Instance.AddPiece(val2);
		}

		private void Update()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			LoadoutMenu.Update();
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || LoadoutMenu.IsTyping())
			{
				return;
			}
			KeyboardShortcut value = MenuKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				LoadoutMenu.Toggle();
			}
			else
			{
				if (LoadoutMenu.IsOpen)
				{
					return;
				}
				for (int i = 0; i < QuickKeys.Length; i++)
				{
					value = QuickKeys[i].Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						LoadoutService.SwitchToListed(localPlayer, i);
						break;
					}
				}
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	public class LoadoutChest : MonoBehaviour, Hoverable, Interactable
	{
		private const string DataKey = "GearHeim.Loadouts";

		private const string LockKey = "GearHeim.LockHolder";

		private const string LockTimeKey = "GearHeim.LockTime";

		private const string RpcRequest = "GearHeim_RequestAccess";

		private const string RpcResponse = "GearHeim_AccessResponse";

		private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(15.0);

		private const float ResponseTimeout = 10f;

		internal static readonly List<LoadoutChest> All = new List<LoadoutChest>();

		private ZNetView _nview;

		private Action _pending;

		private float _pendingSince;

		private bool _granted;

		private uint _grantedRevision;

		private bool _corrupt;

		internal bool IsValid
		{
			get
			{
				if ((Object)(object)_nview != (Object)null)
				{
					return _nview.IsValid();
				}
				return false;
			}
		}

		internal Vector3 Position => ((Component)this).transform.position;

		internal bool IsCorrupt => _corrupt;

		private void Awake()
		{
			_nview = ((Component)this).GetComponent<ZNetView>();
			if (!((Object)(object)_nview == (Object)null) && _nview.GetZDO() != null)
			{
				All.Add(this);
				_nview.Register<long>("GearHeim_RequestAccess", (Action<long, long>)RPC_RequestAccess);
				_nview.Register<bool, long>("GearHeim_AccessResponse", (Action<long, bool, long>)RPC_AccessResponse);
				WearNTear component = ((Component)this).GetComponent<WearNTear>();
				if ((Object)(object)component != (Object)null)
				{
					component.m_onDestroyed = (Action)Delegate.Combine(component.m_onDestroyed, new Action(OnDestroyed));
				}
			}
		}

		private void OnDestroy()
		{
			All.Remove(this);
		}

		private void Update()
		{
			if (_pending == null)
			{
				return;
			}
			if (_granted && IsValid && _nview.IsOwner() && _nview.GetZDO().DataRevision >= _grantedRevision)
			{
				RunPending();
			}
			else if (Time.time - _pendingSince > 10f)
			{
				_pending = null;
				_granted = false;
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					LoadoutService.Message(Player.m_localPlayer, "The Loadout Chest didn't respond. Try again.");
				}
			}
		}

		internal void WithAccess(Action action)
		{
			if (IsValid)
			{
				if (_pending != null)
				{
					LoadoutService.Message(Player.m_localPlayer, "Still waiting on the Loadout Chest...");
					return;
				}
				_pending = action;
				_pendingSince = Time.time;
				_granted = false;
				_nview.InvokeRPC("GearHeim_RequestAccess", new object[1] { Game.instance.GetPlayerProfile().GetPlayerID() });
			}
		}

		private void RPC_RequestAccess(long sender, long playerId)
		{
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			if (_nview.IsOwner())
			{
				ZDO zDO = _nview.GetZDO();
				long num = zDO.GetLong("GearHeim.LockHolder", 0L);
				DateTime time = ZNet.instance.GetTime();
				DateTime dateTime = new DateTime(zDO.GetLong("GearHeim.LockTime", 0L));
				if (num != 0L && num != sender && time - dateTime < LockTimeout)
				{
					_nview.InvokeRPC(sender, "GearHeim_AccessResponse", new object[2] { false, 0L });
					return;
				}
				zDO.Set("GearHeim.LockHolder", sender);
				zDO.Set("GearHeim.LockTime", time.Ticks);
				ZDOMan.instance.ForceSendZDO(sender, zDO.m_uid);
				zDO.SetOwner(sender);
				_nview.InvokeRPC(sender, "GearHeim_AccessResponse", new object[2]
				{
					true,
					(long)zDO.DataRevision
				});
			}
		}

		private void RPC_AccessResponse(long sender, bool granted, long revision)
		{
			if (_pending == null)
			{
				return;
			}
			if (!granted || !IsValid)
			{
				_pending = null;
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					LoadoutService.Message(Player.m_localPlayer, "Someone else is using that Loadout Chest.");
				}
			}
			else
			{
				_granted = true;
				_grantedRevision = (uint)revision;
				Update();
			}
		}

		private void RunPending()
		{
			Action pending = _pending;
			_pending = null;
			_granted = false;
			try
			{
				pending();
			}
			catch (Exception arg)
			{
				GearHeimPlugin.Log.LogError((object)$"Loadout action failed: {arg}");
			}
			finally
			{
				if (IsValid && _nview.IsOwner())
				{
					_nview.GetZDO().Set("GearHeim.LockHolder", 0L);
				}
				LoadoutMenu.Refresh();
			}
		}

		internal List<StoredLoadout> Load()
		{
			return Read(withItems: true);
		}

		internal List<StoredLoadout> Peek()
		{
			return Read(withItems: false);
		}

		private List<StoredLoadout> Read(bool withItems)
		{
			if (!IsValid)
			{
				return new List<StoredLoadout>();
			}
			try
			{
				return LoadoutStorage.Read(_nview.GetZDO().GetByteArray("GearHeim.Loadouts", (byte[])null), withItems);
			}
			catch (Exception arg)
			{
				if (!_corrupt)
				{
					GearHeimPlugin.Log.LogError((object)$"Loadout Chest data is unreadable; leaving it untouched. {arg}");
				}
				_corrupt = true;
				return new List<StoredLoadout>();
			}
		}

		internal bool Save(List<StoredLoadout> loadouts)
		{
			if (!IsValid || !_nview.IsOwner() || _corrupt)
			{
				return false;
			}
			_nview.GetZDO().Set("GearHeim.Loadouts", LoadoutStorage.Write(loadouts));
			return true;
		}

		internal bool HasStoredItems()
		{
			if (!_corrupt)
			{
				return Peek().Any((StoredLoadout l) => l.ItemCount > 0);
			}
			return true;
		}

		internal bool CanAccess(bool flash)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return PrivateArea.CheckAccess(Position, 0f, flash, false);
		}

		internal static List<LoadoutChest> InArea(BaseArea area)
		{
			return (from c in All
				where (Object)(object)c != (Object)null && c.IsValid && area.Contains(c.Position)
				orderby area.DistanceFrom(c.Position)
				select c).ToList();
		}

		public string GetHoverText()
		{
			if (!IsValid)
			{
				return "";
			}
			if (!CanAccess(flash: false))
			{
				return Localization.instance.Localize("Loadout Chest\n$piece_noaccess");
			}
			int count = Peek().Count;
			string text = (_corrupt ? "data unreadable" : ((count == 1) ? "1 loadout" : $"{count} loadouts"));
			return Localization.instance.Localize("Loadout Chest ( " + text + " )\n[<color=yellow><b>$KEY_Use</b></color>] Manage loadouts");
		}

		public string GetHoverName()
		{
			return "Loadout Chest";
		}

		public float GetHoverOffset()
		{
			return 0f;
		}

		public bool Interact(Humanoid user, bool hold, bool alt)
		{
			if (hold || !IsValid)
			{
				return false;
			}
			if (!CanAccess(flash: true))
			{
				return true;
			}
			LoadoutMenu.Open(this);
			return true;
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			return false;
		}

		private void OnDestroyed()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			if (!IsValid || !_nview.IsOwner() || _corrupt)
			{
				return;
			}
			foreach (StoredLoadout item in Load())
			{
				foreach (ItemData item2 in item.Items)
				{
					Tags.Clear(item2);
					Vector3 val = Random.insideUnitSphere * 0.5f;
					val.y = Mathf.Abs(val.y) + 0.5f;
					ItemDrop.DropItem(item2, item2.m_stack, Position + val, Quaternion.identity);
				}
			}
			_nview.GetZDO().Set("GearHeim.Loadouts", new byte[0]);
		}
	}
	internal class LoadoutEntry
	{
		public string Prefab;

		public int Quality;

		public int Variant;

		public bool Equipped;

		public int HotbarX = -1;

		public int Target;

		public bool Stackable;

		public void Write(ZPackage pkg)
		{
			pkg.Write(Prefab ?? "");
			pkg.Write(Quality);
			pkg.Write(Variant);
			pkg.Write(Equipped);
			pkg.Write(HotbarX);
			pkg.Write(Target);
			pkg.Write(Stackable);
		}

		public static LoadoutEntry Read(ZPackage pkg)
		{
			return new LoadoutEntry
			{
				Prefab = pkg.ReadString(),
				Quality = pkg.ReadInt(),
				Variant = pkg.ReadInt(),
				Equipped = pkg.ReadBool(),
				HotbarX = pkg.ReadInt(),
				Target = pkg.ReadInt(),
				Stackable = pkg.ReadBool()
			};
		}
	}
	internal class LoadoutDef
	{
		public string Id;

		public string Name;

		public long Owner;

		public List<LoadoutEntry> Entries = new List<LoadoutEntry>();

		public static string NewId()
		{
			return Guid.NewGuid().ToString("N").Substring(0, 12);
		}

		public void Write(ZPackage pkg)
		{
			pkg.Write(Id);
			pkg.Write(Name);
			pkg.Write(Owner);
			pkg.Write(Entries.Count);
			foreach (LoadoutEntry entry in Entries)
			{
				entry.Write(pkg);
			}
		}

		public static LoadoutDef Read(ZPackage pkg)
		{
			LoadoutDef loadoutDef = new LoadoutDef
			{
				Id = pkg.ReadString(),
				Name = pkg.ReadString(),
				Owner = pkg.ReadLong()
			};
			int num = pkg.ReadInt();
			for (int i = 0; i < num; i++)
			{
				loadoutDef.Entries.Add(LoadoutEntry.Read(pkg));
			}
			return loadoutDef;
		}

		public string ToBase64()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			Write(val);
			return Convert.ToBase64String(val.GetArray());
		}

		public static LoadoutDef FromBase64(string s)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			if (string.IsNullOrEmpty(s))
			{
				return null;
			}
			try
			{
				return Read(new ZPackage(Convert.FromBase64String(s)));
			}
			catch (Exception ex)
			{
				GearHeimPlugin.Log.LogWarning((object)("Could not read worn loadout: " + ex.Message));
				return null;
			}
		}
	}
	internal class StoredLoadout
	{
		public LoadoutDef Def;

		public List<ItemData> Items = new List<ItemData>();

		public bool CheckedOut;

		public int ItemCount;
	}
	internal static class Tags
	{
		public const string Loadout = "GearHeim.Loadout";

		public const string Entry = "GearHeim.Entry";

		public static string LoadoutOf(ItemData item)
		{
			if (!item.m_customData.TryGetValue("GearHeim.Loadout", out var value))
			{
				return null;
			}
			return value;
		}

		public static int EntryOf(ItemData item)
		{
			if (!item.m_customData.TryGetValue("GearHeim.Entry", out var value) || !int.TryParse(value, out var result))
			{
				return -1;
			}
			return result;
		}

		public static void Set(ItemData item, string loadoutId, int entry)
		{
			item.m_customData["GearHeim.Loadout"] = loadoutId;
			item.m_customData["GearHeim.Entry"] = entry.ToString();
		}

		public static void Clear(ItemData item)
		{
			item.m_customData.Remove("GearHeim.Loadout");
			item.m_customData.Remove("GearHeim.Entry");
		}
	}
	internal static class LoadoutStorage
	{
		private const int Version = 1;

		private const int ScratchWidth = 8;

		private const int ScratchHeight = 32;

		public static byte[] Write(List<StoredLoadout> loadouts)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(loadouts.Count);
			foreach (StoredLoadout loadout in loadouts)
			{
				loadout.Def.Write(val);
				val.Write(loadout.CheckedOut);
				val.Write(SaveItems(loadout.Items));
			}
			return val.GetArray();
		}

		public static List<StoredLoadout> Read(byte[] data, bool withItems = true)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			List<StoredLoadout> list = new List<StoredLoadout>();
			if (data == null || data.Length == 0)
			{
				return list;
			}
			ZPackage val = new ZPackage(data);
			int num = val.ReadInt();
			if (num != 1)
			{
				throw new InvalidOperationException($"Unknown loadout data version {num} (made by a newer GearHeim?)");
			}
			int num2 = val.ReadInt();
			for (int i = 0; i < num2; i++)
			{
				LoadoutDef def = LoadoutDef.Read(val);
				bool checkedOut = val.ReadBool();
				byte[] data2 = val.ReadByteArray();
				List<ItemData> list2 = (withItems ? LoadItems(data2) : new List<ItemData>());
				list.Add(new StoredLoadout
				{
					Def = def,
					CheckedOut = checkedOut,
					Items = list2,
					ItemCount = (withItems ? list2.Count : CountItems(data2))
				});
			}
			return list;
		}

		private static byte[] SaveItems(List<ItemData> items)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			//IL_002e: 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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			Inventory val = new Inventory("GearHeim", (Sprite)null, 8, 32);
			for (int i = 0; i < items.Count; i++)
			{
				ItemData val2 = items[i].Clone();
				val2.m_equipped = false;
				val2.m_gridPos = new Vector2i(i % 8, i / 8);
				val.m_inventory.Add(val2);
			}
			ZPackage val3 = new ZPackage();
			val.Save(val3);
			return val3.GetArray();
		}

		private static int CountItems(byte[] data)
		{
			//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)
			if (data == null || data.Length < 6)
			{
				return 0;
			}
			ZPackage val = new ZPackage(data);
			val.ReadInt();
			return val.ReadUShort();
		}

		private static List<ItemData> LoadItems(byte[] data)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			Inventory val = new Inventory("GearHeim", (Sprite)null, 8, 32);
			if (data != null && data.Length != 0)
			{
				val.Load(new ZPackage(data));
			}
			return val.m_inventory.ToList();
		}
	}
	internal static class LoadoutMenu
	{
		private const float Width = 600f;

		private const float RowHeight = 40f;

		private const int MaxRows = 8;

		private static GameObject _panel;

		private static InputField _nameField;

		private static LoadoutChest _chest;

		private static string _confirmDeleteId;

		public static bool IsOpen
		{
			get
			{
				if ((Object)(object)_panel != (Object)null)
				{
					return _panel.activeSelf;
				}
				return false;
			}
		}

		public static void Toggle()
		{
			if (IsOpen)
			{
				Close();
			}
			else
			{
				Open(null);
			}
		}

		public static void Open(LoadoutChest chest)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)GUIManager.CustomGUIFront == (Object)null)
			{
				return;
			}
			BaseArea baseArea = BaseArea.For(((Component)localPlayer).transform.position);
			if ((Object)(object)chest == (Object)null)
			{
				if (!baseArea.IsValid)
				{
					LoadoutService.Message(localPlayer, "You need to be in your base (near a workbench) to change loadouts.");
					return;
				}
				List<LoadoutChest> list = LoadoutChest.InArea(baseArea);
				if (list.Count == 0)
				{
					LoadoutService.Message(localPlayer, "No Loadout Chest in this base.");
					return;
				}
				chest = list[0];
			}
			_chest = chest;
			_confirmDeleteId = null;
			Rebuild();
			GUIManager.BlockInput(true);
		}

		public static void Close()
		{
			if ((Object)(object)_panel != (Object)null)
			{
				Object.Destroy((Object)(object)_panel);
			}
			_panel = null;
			_nameField = null;
			_chest = null;
			GUIManager.BlockInput(false);
		}

		public static void Refresh()
		{
			if (IsOpen)
			{
				Rebuild();
			}
		}

		public static void ClearName()
		{
			if ((Object)(object)_nameField != (Object)null)
			{
				_nameField.text = "";
			}
		}

		public static void Update()
		{
			if (IsOpen && (Input.GetKeyDown((KeyCode)27) || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)_chest == (Object)null || !_chest.IsValid))
			{
				Close();
			}
		}

		public static bool IsTyping()
		{
			if ((!((Object)(object)_nameField != (Object)null) || !_nameField.isFocused) && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !TextInput.IsVisible())
			{
				return Menu.IsVisible();
			}
			return true;
		}

		private static void Rebuild()
		{
			//IL_004c: 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_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_034c: Unknown result type (might be due to invalid IL or missing references)
			//IL_035b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0367: Unknown result type (might be due to invalid IL or missing references)
			//IL_0318: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			string text = (((Object)(object)_nameField != (Object)null) ? _nameField.text : "");
			if ((Object)(object)_panel != (Object)null)
			{
				Object.Destroy((Object)(object)_panel);
			}
			BaseArea baseArea = BaseArea.For(((Component)localPlayer).transform.position);
			List<LoadoutListing> list = (baseArea.IsValid ? LoadoutService.List(localPlayer, baseArea) : new List<LoadoutListing>());
			LoadoutDef worn = LoadoutService.GetWorn(localPlayer);
			int num = Mathf.Max(1, Mathf.Min(list.Count, 8));
			float num2 = 190f + (float)num * 40f + 150f;
			GUIManager instance = GUIManager.Instance;
			_panel = instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 600f, num2, false);
			float num3 = -30f;
			Text("Loadouts", num3, 28, instance.ValheimOrange);
			num3 -= 34f;
			Text((worn != null) ? ("Wearing: " + worn.Name) : "Not wearing a saved loadout", num3, 18, instance.ValheimBeige);
			num3 -= 44f;
			if (list.Count == 0)
			{
				Text("No loadouts yet. Name one below and save your current gear.", num3, 16, Color.white);
				num3 -= 40f;
			}
			for (int i = 0; i < list.Count && i < 8; i++)
			{
				LoadoutListing listing = list[i];
				string text2 = ((i < GearHeimPlugin.QuickKeys.Length) ? ((object)GearHeimPlugin.QuickKeys[i].Value/*cast due to .constrained prefix*/).ToString() : "");
				string text3 = (listing.Worn ? "worn" : (listing.Stored ? $"{listing.ItemCount} items" : "elsewhere"));
				RowText(listing.Def.Name + "  <color=#a0a0a0>(" + text3 + ((text2.Length > 0) ? (", " + text2) : "") + ")</color>", num3);
				if (listing.Stored)
				{
					Button("Equip", 110f, num3, 90f, delegate
					{
						LoadoutService.Switch(Player.m_localPlayer, listing.Def.Id);
					});
				}
				Button((_confirmDeleteId == listing.Def.Id) ? "Sure?" : "Delete", 210f, num3, 90f, delegate
				{
					if (_confirmDeleteId != listing.Def.Id)
					{
						_confirmDeleteId = listing.Def.Id;
					}
					else
					{
						_confirmDeleteId = null;
						LoadoutService.Delete(Player.m_localPlayer, listing);
					}
					Rebuild();
				});
				num3 -= 40f;
			}
			if (list.Count > 8)
			{
				Text($"+{list.Count - 8} more (use the hotkeys or delete some)", num3, 14, Color.gray);
				num3 -= 40f;
			}
			num3 -= 16f;
			_nameField = instance.CreateInputField(_panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(-95f, num3), (ContentType)0, "New loadout name", 18, 360f, 34f).GetComponent<InputField>();
			_nameField.characterLimit = 32;
			_nameField.text = text;
			Button("Save gear as new", 195f, num3, 180f, delegate
			{
				LoadoutService.SaveNew(Player.m_localPlayer, _chest, _nameField.text);
			});
			num3 -= 48f;
			if (worn != null)
			{
				Button("Save changes to '" + Shorten(worn.Name) + "'", -140f, num3, 270f, delegate
				{
					LoadoutService.UpdateWorn(Player.m_localPlayer, _chest);
				});
				Button("Store it", 120f, num3, 150f, delegate
				{
					LoadoutService.StoreWorn(Player.m_localPlayer, _chest);
				});
			}
			Button("Close", 235f, num3, 100f, Close);
		}

		private static string Shorten(string s)
		{
			if (s.Length > 14)
			{
				return s.Substring(0, 13) + "…";
			}
			return s;
		}

		private static void Text(string text, float y, int size, Color color)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			GUIManager.Instance.CreateText(text, _panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, y), GUIManager.Instance.AveriaSerifBold, size, color, true, Color.black, 560f, 30f, false).GetComponent<Text>().alignment = (TextAnchor)4;
		}

		private static void RowText(string text, float y)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			Text component = GUIManager.Instance.CreateText(text, _panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(-100f, y), GUIManager.Instance.AveriaSerifBold, 18, Color.white, true, Color.black, 360f, 32f, false).GetComponent<Text>();
			component.alignment = (TextAnchor)3;
			component.supportRichText = true;
		}

		private static void Button(string label, float x, float y, float width, Action onClick)
		{
			//IL_0028: 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)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			((UnityEvent)GUIManager.Instance.CreateButton(label, _panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(x, y), width, 34f).GetComponent<Button>().onClick).AddListener((UnityAction)delegate
			{
				onClick();
			});
		}
	}
	internal class LoadoutListing
	{
		public LoadoutDef Def;

		public LoadoutChest Chest;

		public bool Stored;

		public bool Worn;

		public int ItemCount;
	}
	internal static class LoadoutService
	{
		private class Placement
		{
			public readonly List<(ItemData item, Vector2i pos)> Moves = new List<(ItemData, Vector2i)>();

			public readonly List<(ItemData item, Vector2i pos)> Incoming = new List<(ItemData, Vector2i)>();
		}

		private const string WornKey = "GearHeim.Worn";

		private const string LastWornName = "Last worn";

		public static LoadoutDef GetWorn(Player player)
		{
			if (!player.m_customData.TryGetValue("GearHeim.Worn", out var value))
			{
				return null;
			}
			return LoadoutDef.FromBase64(value);
		}

		private static void SetWorn(Player player, LoadoutDef def)
		{
			if (def == null)
			{
				player.m_customData.Remove("GearHeim.Worn");
			}
			else
			{
				player.m_customData["GearHeim.Worn"] = def.ToBase64();
			}
		}

		public static List<LoadoutListing> List(Player player, BaseArea area)
		{
			long playerID = player.GetPlayerID();
			Dictionary<string, LoadoutListing> dictionary = new Dictionary<string, LoadoutListing>();
			foreach (LoadoutChest item in LoadoutChest.InArea(area))
			{
				foreach (StoredLoadout item2 in item.Peek())
				{
					if (item2.Def.Owner == playerID)
					{
						bool flag = !item2.CheckedOut;
						if (!dictionary.TryGetValue(item2.Def.Id, out var value) || (!value.Stored && flag))
						{
							dictionary[item2.Def.Id] = new LoadoutListing
							{
								Def = item2.Def,
								Chest = item,
								Stored = flag,
								ItemCount = item2.ItemCount
							};
						}
					}
				}
			}
			LoadoutDef worn = GetWorn(player);
			if (worn != null)
			{
				if (!dictionary.TryGetValue(worn.Id, out var value2))
				{
					value2 = (dictionary[worn.Id] = new LoadoutListing());
				}
				value2.Def = worn;
				value2.Worn = true;
				value2.Stored = false;
			}
			return dictionary.Values.OrderBy<LoadoutListing, string>((LoadoutListing l) => l.Def.Name, StringComparer.CurrentCultureIgnoreCase).ToList();
		}

		public static void SwitchToListed(Player player, int index)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			BaseArea area = BaseArea.For(((Component)player).transform.position);
			if (RequireArea(player, area))
			{
				List<LoadoutListing> list = List(player, area);
				if (index < 0 || index >= list.Count)
				{
					Message(player, $"No loadout #{index + 1} here.");
				}
				else
				{
					Switch(player, list[index].Def.Id);
				}
			}
		}

		public static void Switch(Player player, string targetId)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!CanAct(player))
			{
				return;
			}
			BaseArea area = BaseArea.For(((Component)player).transform.position);
			if (!RequireArea(player, area))
			{
				return;
			}
			LoadoutDef worn = GetWorn(player);
			if (worn != null && worn.Id == targetId)
			{
				Message(player, "You're already wearing '" + worn.Name + "'.");
				return;
			}
			LoadoutListing loadoutListing = List(player, area).FirstOrDefault((LoadoutListing l) => l.Def.Id == targetId && l.Stored);
			if (loadoutListing == null)
			{
				Message(player, "That loadout isn't stored in a Loadout Chest in this base.");
				return;
			}
			LoadoutChest chest = loadoutListing.Chest;
			chest.WithAccess(delegate
			{
				DoSwitch(player, chest, targetId);
			});
		}

		private static void DoSwitch(Player player, LoadoutChest chest, string targetId)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if (!CanAct(player))
			{
				return;
			}
			BaseArea baseArea = BaseArea.For(((Component)player).transform.position);
			if (!baseArea.IsValid || !baseArea.Contains(chest.Position))
			{
				Message(player, "You've left the base.");
				return;
			}
			List<StoredLoadout> list = chest.Load();
			StoredLoadout storedLoadout = list.FirstOrDefault((StoredLoadout l) => l.Def.Id == targetId && !l.CheckedOut && l.Def.Owner == player.GetPlayerID());
			if (chest.IsCorrupt || storedLoadout == null)
			{
				Message(player, "That loadout is no longer in the chest.");
				return;
			}
			LoadoutDef worn = GetWorn(player);
			ClearOrphanTags(player, worn);
			Inventory inventory = ((Humanoid)player).GetInventory();
			List<ItemData> items = storedLoadout.Items;
			LoadoutDef loadoutDef;
			List<ItemData> list2;
			if (worn != null)
			{
				loadoutDef = worn;
				list2 = inventory.m_inventory.Where((ItemData i) => Tags.LoadoutOf(i) == worn.Id).ToList();
			}
			else
			{
				list2 = CurrentGear(player);
				loadoutDef = ((list2.Count > 0) ? NewDef(player, UniqueName(player, baseArea, "Last worn")) : null);
			}
			if (!PlanPlacement(player, storedLoadout.Def, items, list2, MissingStackEntries(storedLoadout.Def, items), out var _, out var why))
			{
				Message(player, why);
				return;
			}
			if (loadoutDef != null && worn == null)
			{
				Capture(loadoutDef, list2);
			}
			List<ItemData> list3 = new List<ItemData>();
			List<string> missing = new List<string>();
			if (loadoutDef != null)
			{
				Restock(player, baseArea, loadoutDef, list2, missing, list3, trackTopUps: false);
			}
			List<string> missing2 = new List<string>();
			Restock(player, baseArea, storedLoadout.Def, items, missing2, list3, trackTopUps: true);
			Placement plan2 = null;
			bool flag = PlanPlacement(player, storedLoadout.Def, items, list2, 0, out plan2, out why);
			storedLoadout.CheckedOut = true;
			storedLoadout.Items = new List<ItemData>();
			if (loadoutDef != null)
			{
				Upsert(list, loadoutDef, list2);
			}
			if (!flag || !chest.Save(list))
			{
				if (loadoutDef != null && worn == null)
				{
					foreach (ItemData item in list2)
					{
						Tags.Clear(item);
					}
				}
				GiveOrDrop(player, list3);
				Message(player, flag ? "Couldn't write to the Loadout Chest; nothing was changed." : why);
				return;
			}
			SetWorn(player, storedLoadout.Def);
			try
			{
				RemoveFromPlayer(player, list2);
				((Humanoid)player).ClearActionQueue();
				Apply(player, storedLoadout.Def, plan2);
			}
			catch (Exception arg)
			{
				GearHeimPlugin.Log.LogError((object)$"Switch failed after saving: {arg}");
				GiveOrDrop(player, items.Where((ItemData i) => !((Humanoid)player).GetInventory().m_inventory.Contains(i)).ToList());
			}
			Message(player, "Switched to '" + storedLoadout.Def.Name + "'.");
			Report(player, loadoutDef, missing, "stored");
			Report(player, storedLoadout.Def, missing2, "equipped");
		}

		public static void SaveNew(Player player, LoadoutChest chest, string name)
		{
			if (!CanAct(player))
			{
				return;
			}
			LoadoutDef worn = GetWorn(player);
			if (worn != null)
			{
				Message(player, "You're wearing '" + worn.Name + "'. Store it or save your changes to it first.");
				return;
			}
			name = (name ?? "").Trim();
			if (name.Length == 0)
			{
				Message(player, "Give the loadout a name first.");
				return;
			}
			chest.WithAccess(delegate
			{
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				if (CanAct(player))
				{
					ClearOrphanTags(player, null);
					List<ItemData> list = CurrentGear(player);
					if (list.Count == 0)
					{
						Message(player, "Equip some gear or fill your hotbar first.");
					}
					else
					{
						BaseArea area = BaseArea.For(((Component)player).transform.position);
						LoadoutDef loadoutDef = NewDef(player, UniqueName(player, area, name));
						Capture(loadoutDef, list);
						List<StoredLoadout> loadouts = chest.Load();
						Upsert(loadouts, loadoutDef, list);
						if (!chest.Save(loadouts))
						{
							foreach (ItemData item in list)
							{
								Tags.Clear(item);
							}
							Message(player, "Couldn't write to the Loadout Chest; nothing was changed.");
						}
						else
						{
							RemoveFromPlayer(player, list);
							LoadoutMenu.ClearName();
							Message(player, $"Saved '{loadoutDef.Name}' ({list.Count} items).");
						}
					}
				}
			});
		}

		public static void StoreWorn(Player player, LoadoutChest chest)
		{
			if (!CanAct(player))
			{
				return;
			}
			if (GetWorn(player) == null)
			{
				Message(player, "You aren't wearing a saved loadout.");
				return;
			}
			chest.WithAccess(delegate
			{
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				if (CanAct(player))
				{
					LoadoutDef worn = GetWorn(player);
					if (worn != null)
					{
						ClearOrphanTags(player, worn);
						BaseArea area = BaseArea.For(((Component)player).transform.position);
						List<ItemData> items = ((Humanoid)player).GetInventory().m_inventory.Where((ItemData i) => Tags.LoadoutOf(i) == worn.Id).ToList();
						List<string> missing = new List<string>();
						List<ItemData> list = new List<ItemData>();
						Restock(player, area, worn, items, missing, list, trackTopUps: false);
						List<StoredLoadout> loadouts = chest.Load();
						Upsert(loadouts, worn, items);
						if (!chest.Save(loadouts))
						{
							GiveOrDrop(player, list);
							Message(player, "Couldn't write to the Loadout Chest; nothing was changed.");
						}
						else
						{
							RemoveFromPlayer(player, items);
							SetWorn(player, null);
							Message(player, "Stored '" + worn.Name + "'.");
							Report(player, worn, missing, "stored");
						}
					}
				}
			});
		}

		public static void UpdateWorn(Player player, LoadoutChest chest)
		{
			if (!CanAct(player))
			{
				return;
			}
			if (GetWorn(player) == null)
			{
				Message(player, "Equip a loadout first, change your gear, then save the changes here.");
				return;
			}
			chest.WithAccess(delegate
			{
				if (CanAct(player))
				{
					LoadoutDef worn = GetWorn(player);
					if (worn != null)
					{
						ClearOrphanTags(player, worn);
						List<ItemData> list = CurrentGear(player);
						Dictionary<ItemData, Dictionary<string, string>> dictionary = list.ToDictionary((ItemData i) => i, (ItemData i) => new Dictionary<string, string>(i.m_customData));
						LoadoutDef loadoutDef = new LoadoutDef
						{
							Id = worn.Id,
							Name = worn.Name,
							Owner = worn.Owner
						};
						Capture(loadoutDef, list);
						List<StoredLoadout> loadouts = chest.Load();
						Upsert(loadouts, loadoutDef, list);
						if (!chest.Save(loadouts))
						{
							foreach (KeyValuePair<ItemData, Dictionary<string, string>> item in dictionary)
							{
								item.Key.m_customData = item.Value;
							}
							Message(player, "Couldn't write to the Loadout Chest; nothing was changed.");
						}
						else
						{
							foreach (ItemData item2 in ((Humanoid)player).GetInventory().m_inventory)
							{
								if (Tags.LoadoutOf(item2) == worn.Id && !list.Contains(item2))
								{
									Tags.Clear(item2);
								}
							}
							RemoveFromPlayer(player, list);
							SetWorn(player, null);
							Message(player, $"Updated and stored '{loadoutDef.Name}' ({list.Count} items).");
						}
					}
				}
			});
		}

		public static void Delete(Player player, LoadoutListing listing)
		{
			if (!CanAct(player))
			{
				return;
			}
			if ((Object)(object)listing.Chest == (Object)null)
			{
				ForgetWorn(player, listing.Def);
				Message(player, "Deleted '" + listing.Def.Name + "'.");
				return;
			}
			LoadoutChest chest = listing.Chest;
			chest.WithAccess(delegate
			{
				//IL_0109: Unknown result type (might be due to invalid IL or missing references)
				//IL_010e: Unknown result type (might be due to invalid IL or missing references)
				Inventory inventory = ((Humanoid)player).GetInventory();
				List<StoredLoadout> list = chest.Load();
				StoredLoadout storedLoadout = list.FirstOrDefault((StoredLoadout l) => l.Def.Id == listing.Def.Id && l.Def.Owner == player.GetPlayerID());
				if (storedLoadout == null)
				{
					ForgetWorn(player, listing.Def);
				}
				else
				{
					List<Vector2i> list2 = FreeBagSlots(inventory, new HashSet<ItemData>());
					if (list2.Count < storedLoadout.Items.Count)
					{
						Message(player, $"Need {storedLoadout.Items.Count - list2.Count} more free inventory slots to take '{listing.Def.Name}' apart.");
					}
					else
					{
						List<ItemData> items = storedLoadout.Items;
						list.Remove(storedLoadout);
						if (!chest.Save(list))
						{
							Message(player, "Couldn't write to the Loadout Chest; nothing was changed.");
						}
						else
						{
							for (int num = 0; num < items.Count; num++)
							{
								ItemData val = items[num];
								Tags.Clear(val);
								val.m_equipped = false;
								val.m_gridPos = list2[num];
								inventory.m_inventory.Add(val);
							}
							inventory.Changed(false, false);
							ForgetWorn(player, listing.Def);
							Message(player, "Deleted '" + listing.Def.Name + "'.");
						}
					}
				}
			});
		}

		private static void ForgetWorn(Player player, LoadoutDef def)
		{
			LoadoutDef worn = GetWorn(player);
			if (worn == null || worn.Id != def.Id)
			{
				return;
			}
			foreach (ItemData item in ((Humanoid)player).GetInventory().m_inventory)
			{
				if (Tags.LoadoutOf(item) == def.Id)
				{
					Tags.Clear(item);
				}
			}
			SetWorn(player, null);
		}

		private static IEnumerable<ItemData> EquippedItems(Player p)
		{
			return ((IEnumerable<ItemData>)(object)new ItemData[11]
			{
				((Humanoid)p).m_rightItem,
				((Humanoid)p).m_leftItem,
				((Humanoid)p).m_hiddenRightItem,
				((Humanoid)p).m_hiddenLeftItem,
				((Humanoid)p).m_helmetItem,
				((Humanoid)p).m_chestItem,
				((Humanoid)p).m_legItem,
				((Humanoid)p).m_shoulderItem,
				((Humanoid)p).m_utilityItem,
				((Humanoid)p).m_trinketItem,
				((Humanoid)p).m_ammoItem
			}).Where((ItemData i) => i != null).Distinct();
		}

		private static List<ItemData> CurrentGear(Player player)
		{
			List<ItemData> list = EquippedItems(player).ToList();
			foreach (ItemData item in ((Humanoid)player).GetInventory().m_inventory)
			{
				if (item.m_gridPos.y == 0 && !list.Contains(item))
				{
					list.Add(item);
				}
			}
			list.RemoveAll((ItemData i) => (Object)(object)i.m_dropPrefab == (Object)null);
			return list;
		}

		private static void ClearOrphanTags(Player player, LoadoutDef worn)
		{
			foreach (ItemData item in ((Humanoid)player).GetInventory().m_inventory)
			{
				string text = Tags.LoadoutOf(item);
				if (text != null && (worn == null || text != worn.Id))
				{
					Tags.Clear(item);
				}
			}
		}

		private static LoadoutDef NewDef(Player player, string name)
		{
			return new LoadoutDef
			{
				Id = LoadoutDef.NewId(),
				Name = name,
				Owner = player.GetPlayerID()
			};
		}

		private static void Capture(LoadoutDef def, List<ItemData> items)
		{
			def.Entries.Clear();
			foreach (ItemData item2 in items)
			{
				LoadoutEntry item = new LoadoutEntry
				{
					Prefab = (((Object)(object)item2.m_dropPrefab != (Object)null) ? ((Object)item2.m_dropPrefab).name : ""),
					Quality = item2.m_quality,
					Variant = item2.m_variant,
					Equipped = (item2.m_equipped || IsHidden(item2)),
					HotbarX = ((item2.m_gridPos.y == 0) ? item2.m_gridPos.x : (-1)),
					Target = item2.m_stack,
					Stackable = (item2.m_shared.m_maxStackSize > 1)
				};
				Tags.Set(item2, def.Id, def.Entries.Count);
				def.Entries.Add(item);
			}
		}

		private static bool IsHidden(ItemData item)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				if (item != ((Humanoid)localPlayer).m_hiddenLeftItem)
				{
					return item == ((Humanoid)localPlayer).m_hiddenRightItem;
				}
				return true;
			}
			return false;
		}

		private static void RemoveFromPlayer(Player player, List<ItemData> items)
		{
			if (items.Count == 0)
			{
				return;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			foreach (ItemData item in items)
			{
				if (inventory.m_inventory.Contains(item))
				{
					if (((Humanoid)player).IsItemEquiped(item))
					{
						((Humanoid)player).UnequipItem(item, false);
					}
					if (item == ((Humanoid)player).m_hiddenLeftItem)
					{
						((Humanoid)player).m_hiddenLeftItem = null;
					}
					if (item == ((Humanoid)player).m_hiddenRightItem)
					{
						((Humanoid)player).m_hiddenRightItem = null;
					}
					item.m_equipped = false;
					inventory.m_inventory.Remove(item);
				}
			}
			inventory.Changed(false, false);
		}

		private static void Upsert(List<StoredLoadout> loadouts, LoadoutDef def, List<ItemData> items)
		{
			StoredLoadout storedLoadout = loadouts.FirstOrDefault((StoredLoadout l) => l.Def.Id == def.Id);
			if (storedLoadout == null)
			{
				loadouts.Add(storedLoadout = new StoredLoadout());
			}
			storedLoadout.Def = def;
			storedLoadout.Items = items;
			storedLoadout.CheckedOut = false;
		}

		private static string UniqueName(Player player, BaseArea area, string name)
		{
			HashSet<string> hashSet = new HashSet<string>(from l in List(player, area)
				select l.Def.Name, StringComparer.CurrentCultureIgnoreCase);
			if (!hashSet.Contains(name))
			{
				return name;
			}
			int num;
			for (num = 2; hashSet.Contains($"{name} {num}"); num++)
			{
			}
			return $"{name} {num}";
		}

		private static void GiveOrDrop(Player player, List<ItemData> items)
		{
			//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)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			foreach (ItemData item in items)
			{
				Tags.Clear(item);
				if (!((Humanoid)player).GetInventory().AddItem(item))
				{
					ItemDrop.DropItem(item, item.m_stack, ((Component)player).transform.position + Vector3.up, Quaternion.identity);
				}
			}
		}

		private static int MissingStackEntries(LoadoutDef def, List<ItemData> items)
		{
			if (!GearHeimPlugin.RefillFromChests.Value)
			{
				return 0;
			}
			HashSet<int> present = new HashSet<int>(items.Select(Tags.EntryOf));
			return def.Entries.Where((LoadoutEntry e, int i) => e.Stackable && !present.Contains(i)).Count();
		}

		private static void Restock(Player player, BaseArea area, LoadoutDef def, List<ItemData> items, List<string> missing, List<ItemData> created, bool trackTopUps)
		{
			int index;
			for (index = 0; index < def.Entries.Count; index++)
			{
				LoadoutEntry loadoutEntry = def.Entries[index];
				List<ItemData> list = items.Where((ItemData i) => Tags.EntryOf(i) == index).ToList();
				if (!loadoutEntry.Stackable)
				{
					if (list.Count == 0)
					{
						missing.Add(DisplayName(loadoutEntry.Prefab));
					}
					continue;
				}
				int num = loadoutEntry.Target - list.Sum((ItemData i) => i.m_stack);
				if (num <= 0)
				{
					continue;
				}
				if (GearHeimPlugin.RefillFromChests.Value)
				{
					int worldLevel = ((list.Count > 0) ? list[0].m_worldLevel : (-1));
					ItemData template;
					int num2 = ChestSupply.Take(player, area, loadoutEntry, worldLevel, num, out template);
					if (num2 > 0)
					{
						if (list.Count > 0)
						{
							ItemData obj = list[0];
							obj.m_stack += num2;
							if (trackTopUps)
							{
								ItemData val = list[0].Clone();
								val.m_stack = num2;
								created.Add(val);
							}
						}
						else
						{
							ItemData val2 = template.Clone();
							val2.m_stack = num2;
							val2.m_equipped = false;
							Tags.Set(val2, def.Id, index);
							items.Add(val2);
							created.Add(val2);
						}
						num -= num2;
					}
				}
				if (num > 0)
				{
					missing.Add($"{num} {DisplayName(loadoutEntry.Prefab)}");
				}
			}
		}

		private static string DisplayName(string prefab)
		{
			GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(prefab) : null);
			ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
			if (!((Object)(object)val2 != (Object)null))
			{
				return prefab;
			}
			return Localization.instance.Localize(val2.m_itemData.m_shared.m_name);
		}

		private static void Report(Player player, LoadoutDef def, List<string> missing, string verb)
		{
			if (def != null && missing.Count != 0)
			{
				((Character)player).Message((MessageType)1, "'" + def.Name + "' " + verb + " incomplete. Missing: " + string.Join(", ", missing), 0, (Sprite)null, false);
			}
		}

		private static bool PlanPlacement(Player player, LoadoutDef def, List<ItemData> incoming, List<ItemData> outgoing, int extraStacks, out Placement plan, out string why)
		{
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Unknown result type (might be due to invalid IL or missing references)
			plan = new Placement();
			why = null;
			Inventory inventory = ((Humanoid)player).GetInventory();
			HashSet<ItemData> leaving = new HashSet<ItemData>(outgoing);
			List<ItemData> source = inventory.m_inventory.Where((ItemData i) => !leaving.Contains(i)).ToList();
			Dictionary<int, ItemData> hotbarClaims = new Dictionary<int, ItemData>();
			List<ItemData> list = new List<ItemData>();
			foreach (ItemData item in incoming)
			{
				int num = Tags.EntryOf(item);
				int num2 = ((num >= 0 && num < def.Entries.Count) ? def.Entries[num].HotbarX : (-1));
				if (num2 >= 0 && num2 < inventory.GetWidth() && !hotbarClaims.ContainsKey(num2))
				{
					hotbarClaims[num2] = item;
				}
				else
				{
					list.Add(item);
				}
			}
			List<ItemData> list2 = source.Where((ItemData i) => i.m_gridPos.y == 0 && hotbarClaims.ContainsKey(i.m_gridPos.x)).ToList();
			List<Vector2i> list3 = FreeBagSlots(inventory, leaving);
			int num3 = list.Count + list2.Count + extraStacks;
			if (list3.Count < num3)
			{
				why = $"Not enough inventory space: free up {num3 - list3.Count} more slot(s).";
				return false;
			}
			int num4 = 0;
			foreach (ItemData item2 in list2)
			{
				plan.Moves.Add((item2, list3[num4++]));
			}
			foreach (KeyValuePair<int, ItemData> item3 in hotbarClaims)
			{
				plan.Incoming.Add((item3.Value, new Vector2i(item3.Key, 0)));
			}
			foreach (ItemData item4 in list)
			{
				plan.Incoming.Add((item4, list3[num4++]));
			}
			return true;
		}

		private static List<Vector2i> FreeBagSlots(Inventory inventory, HashSet<ItemData> leaving)
		{
			//IL_0068: 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)
			HashSet<Vector2i> hashSet = new HashSet<Vector2i>(from i in inventory.m_inventory
				where !leaving.Contains(i)
				select i.m_gridPos);
			List<Vector2i> list = new List<Vector2i>();
			Vector2i item = default(Vector2i);
			for (int num = 1; num < inventory.GetHeight(); num++)
			{
				for (int num2 = 0; num2 < inventory.GetWidth(); num2++)
				{
					((Vector2i)(ref item))..ctor(num2, num);
					if (!hashSet.Contains(item))
					{
						list.Add(item);
					}
				}
			}
			return list;
		}

		private static void Apply(Player player, LoadoutDef def, Placement plan)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			Inventory inventory = ((Humanoid)player).GetInventory();
			foreach (var move in plan.Moves)
			{
				ItemData item = move.item;
				Vector2i item2 = move.pos;
				item.m_gridPos = item2;
			}
			foreach (var item6 in plan.Incoming)
			{
				ItemData item3 = item6.item;
				Vector2i item4 = item6.pos;
				item3.m_equipped = false;
				item3.m_gridPos = item4;
				inventory.m_inventory.Add(item3);
			}
			inventory.Changed(false, false);
			foreach (var item7 in plan.Incoming)
			{
				ItemData item5 = item7.item;
				int num = Tags.EntryOf(item5);
				if (num >= 0 && num < def.Entries.Count && def.Entries[num].Equipped)
				{
					((Humanoid)player).EquipItem(item5, true);
				}
			}
		}

		private static bool CanAct(Player player)
		{
			if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || ((Character)player).IsTeleporting())
			{
				return false;
			}
			if (((Character)player).InAttack() || ((Character)player).InDodge())
			{
				Message(player, "Can't change loadouts while fighting.");
				return false;
			}
			if (((Character)player).IsSwimming())
			{
				Message(player, "Can't change loadouts while swimming.");
				return false;
			}
			if ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.instance.m_dragItem != null)
			{
				Message(player, "Put down the item you're holding first.");
				return false;
			}
			return true;
		}

		private static bool RequireArea(Player player, BaseArea area)
		{
			if (area.IsValid)
			{
				return true;
			}
			Message(player, "You need to be in your base (near a workbench) to change loadouts.");
			return false;
		}

		internal static void Message(Player player, string text)
		{
			if ((Object)(object)player != (Object)null)
			{
				((Character)player).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
		}
	}
	[HarmonyPatch(typeof(Piece), "CanBeRemoved")]
	internal static class PieceCanBeRemovedPatch
	{
		private static void Postfix(Piece __instance, ref bool __result)
		{
			if (__result)
			{
				LoadoutChest component = ((Component)__instance).GetComponent<LoadoutChest>();
				if ((Object)(object)component != (Object)null && component.HasStoredItems())
				{
					__result = false;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "UseHotbarItem")]
	internal static class UseHotbarItemPatch
	{
		private static bool Prefix(int index)
		{
			//IL_0004: 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_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)
			KeyCode val = (KeyCode)(48 + index);
			ConfigEntry<KeyboardShortcut>[] quickKeys = GearHeimPlugin.QuickKeys;
			for (int i = 0; i < quickKeys.Length; i++)
			{
				KeyboardShortcut value = quickKeys[i].Value;
				if (((KeyboardShortcut)(ref value)).MainKey == val && ((KeyboardShortcut)(ref value)).Modifiers.Any() && ((KeyboardShortcut)(ref value)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey))
				{
					return false;
				}
			}
			return true;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}