Decompiled source of ChestNetwork v0.4.0

plugins/ChestNetwork.Plugin.dll

Decompiled 13 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Numerics;
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 ChestNetwork.Core;
using ChestNetwork.Core.Armor;
using ChestNetwork.Core.Deposit;
using ChestNetwork.Core.Filling;
using ChestNetwork.Core.Multiplayer;
using ChestNetwork.Plugin.Adapters;
using ChestNetwork.Plugin.Armor;
using ChestNetwork.Plugin.Interaction;
using ChestNetwork.Plugin.Machines;
using ChestNetwork.Plugin.Net;
using ChestNetwork.Plugin.Ownership;
using ChestNetwork.Plugin.Patches;
using ChestNetwork.Plugin.Storage;
using ChestNetwork.Plugin.Texts;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ChestNetwork.Plugin")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.0.0")]
[assembly: AssemblyInformationalVersion("0.4.0+6e7ba34d720892abcc32bfe576c2fa62aea456e7")]
[assembly: AssemblyProduct("ChestNetwork.Plugin")]
[assembly: AssemblyTitle("ChestNetwork.Plugin")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.4.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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 ChestNetwork.Plugin
{
	[BepInPlugin("jordan.chestnetwork", "ChestNetwork", "0.4.0")]
	public sealed class ChestNetworkPlugin : BaseUnityPlugin
	{
		public const string Guid = "jordan.chestnetwork";

		public const string Name = "ChestNetwork";

		public const string Version = "0.4.0";

		private Harmony? _harmony;

		internal static ManualLogSource Log { get; private set; }

		internal static PluginSettings Settings { get; private set; }

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new PluginSettings(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("jordan.chestnetwork");
			_harmony.PatchAll(typeof(ChestNetworkPlugin).Assembly);
			AltInteraction.Register(MachinePatches.TryFillHovered);
			AltInteraction.RegisterSwitch(MachinePatches.TryFillSwitch, MachinePatches.FillSwitchHover);
			AltInteraction.RegisterSwitch(ArmorStandPatches.TrySwitch, ArmorStandCoordinator.HoverAction);
			AltInteraction.Register(ArmorStandPatches.TryHovered);
			AltInteraction.Register(ContainerPatches.TryStoreHovered);
			ModTexts.RegisterIfReady();
			Log.LogInfo((object)"ChestNetwork 0.4.0 loaded.");
		}

		private void Update()
		{
			OwnershipCoordinator.Tick();
			NetworkTransaction.Tick();
			AltInteraction.Tick();
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal sealed class PluginSettings
	{
		private readonly ConfigEntry<bool> _enabled;

		private readonly ConfigEntry<ConnectionRule> _connectionRule;

		private readonly ConfigEntry<bool> _inclusiveBoundaries;

		private readonly ConfigEntry<string> _allowedStationTypes;

		private readonly ConfigEntry<bool> _useCarts;

		private readonly ConfigEntry<bool> _useShips;

		private readonly ConfigEntry<bool> _allowEquippedItems;

		private readonly ConfigEntry<float> _refreshInterval;

		private readonly ConfigEntry<bool> _showAvailableAmount;

		private readonly ConfigEntry<string> _availableAmountFormat;

		private readonly ConfigEntry<bool> _fillEnabled;

		private readonly ConfigEntry<bool> _useAltInteract;

		private readonly ConfigEntry<KeyboardShortcut> _altShortcut;

		private readonly ConfigEntry<FillOrigin> _fillOrigin;

		private readonly ConfigEntry<InventoryUse> _fillInventoryUse;

		private readonly ConfigEntry<int> _fillMinimumReserve;

		private readonly ConfigEntry<ReserveScope> _fillReserveScope;

		private readonly ConfigEntry<int> _fillMaxPerAction;

		private readonly ConfigEntry<string> _fillForbiddenItems;

		private readonly ConfigEntry<OrePriority> _fillOrePriority;

		private readonly ConfigEntry<bool> _quickStackEnabled;

		private readonly ConfigEntry<string> _quickStackCategories;

		private readonly ConfigEntry<string> _quickStackForcedItems;

		private readonly ConfigEntry<string> _quickStackExcludedItems;

		private readonly ConfigEntry<bool> _armorEnabled;

		private readonly ConfigEntry<bool> _armorProtectHotbar;

		private readonly ConfigEntry<float> _armorSetWeight;

		private readonly ConfigEntry<float> _armorTierWeight;

		private readonly ConfigEntry<float> _armorQualityWeight;

		private readonly ConfigEntry<string> _armorSets;

		public bool Enabled => _enabled.Value;

		public float RefreshInterval => Math.Max(0f, _refreshInterval.Value);

		public bool ShowAvailableAmount => _showAvailableAmount.Value;

		public bool FillEnabled => _fillEnabled.Value;

		public bool UseAltInteract => _useAltInteract.Value;

		public KeyboardShortcut AltShortcut => _altShortcut.Value;

		public FillOrigin FillOrigin => _fillOrigin.Value;

		public OrePriority FillOrePriority => _fillOrePriority.Value;

		public FillSettings FillSettings { get; private set; }

		public bool QuickStackEnabled => _quickStackEnabled.Value;

		public bool ArmorStandEnabled => _armorEnabled.Value;

		public ArmorFillSettings ArmorFillSettings { get; private set; } = ArmorFillSettings.Default;

		public ArmorSetCatalog ArmorSets { get; private set; } = ArmorSetCatalog.Vanilla;

		public DepositSettings DepositSettings { get; private set; }

		public string AvailableAmountFormat => _availableAmountFormat.Value;

		public ModConfig LocalModConfig { get; private set; }

		public PluginSettings(ConfigFile config)
		{
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Expected O, but got Unknown
			//IL_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Expected O, but got Unknown
			_enabled = config.Bind<bool>("General", "Enabled", true, "Use the resources of containers in the crafting station network.");
			_connectionRule = config.Bind<ConnectionRule>("Network", "ConnectionRule", (ConnectionRule)0, "OverlappingZones: two stations are connected when their zones overlap. StationInsideZone: when one stands inside the other's zone.");
			_inclusiveBoundaries = config.Bind<bool>("Network", "InclusiveBoundaries", true, "Whether a point exactly on a zone border is inside the zone.");
			_allowedStationTypes = config.Bind<string>("Network", "AllowedStationTypes", string.Empty, "Comma-separated station names (e.g. $piece_workbench,$piece_forge). Empty: every station type.");
			_useCarts = config.Bind<bool>("Containers", "UseCarts", false, "Use the content of carts.");
			_useShips = config.Bind<bool>("Containers", "UseShips", false, "Use the content of ships.");
			_allowEquippedItems = config.Bind<bool>("Containers", "AllowEquippedItems", false, "Allow consuming items the player wears.");
			_refreshInterval = config.Bind<float>("Performance", "RefreshInterval", 0.5f, "Seconds between two scans of the network for the build and craft menus. Consumption always rescans.");
			_showAvailableAmount = config.Bind<bool>("Display", "ShowAvailableAmount", true, "Show the available amount (inventory + network containers) next to each required amount.");
			_availableAmountFormat = config.Bind<string>("Display", "AvailableAmountFormat", "{0}/{1}", "Requirement text. {0} = required amount, {1} = available amount.");
			_fillEnabled = config.Bind<bool>("Filling", "Enabled", true, "Fill smelters and fires from the network containers.");
			_useAltInteract = config.Bind<bool>("Interaction", "UseAltInteract", true, "Act on the aimed object (fill a machine...) with the vanilla alternate use: AltPlace + Use (Shift + E by default; gamepad: its alt keys + use). Rebind them in the game settings.");
			_altShortcut = config.Bind<KeyboardShortcut>("Interaction", "Shortcut", KeyboardShortcut.Empty, "Additional key doing the same on the object under the crosshair. Empty: none.");
			_fillOrigin = config.Bind<FillOrigin>("Filling", "NetworkOrigin", FillOrigin.Player, "Where the station network is resolved from: the player (as for building) or the machine.");
			_fillInventoryUse = config.Bind<InventoryUse>("Filling", "InventoryUse", (InventoryUse)0, "Whether the player's inventory is used: Never, First (before containers) or Last (after containers).");
			_fillMinimumReserve = config.Bind<int>("Filling", "MinimumReserve", 0, new ConfigDescription("Units of each item always left in the containers.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>()));
			_fillReserveScope = config.Bind<ReserveScope>("Filling", "ReserveScope", (ReserveScope)0, "Network: the containers together keep the reserve. PerContainer: each container keeps it.");
			_fillMaxPerAction = config.Bind<int>("Filling", "MaxPerAction", 0, new ConfigDescription("Most units inserted by one action. 0: no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>()));
			_fillForbiddenItems = config.Bind<string>("Filling", "ForbiddenItems", string.Empty, "Comma-separated item names never used for filling (e.g. $item_finewood).");
			_fillOrePriority = config.Bind<OrePriority>("Filling", "OrePriority", OrePriority.QueuedFirst, "When a smelter accepts several items: QueuedFirst (what it is already processing) or MachineOrder.");
			_quickStackEnabled = config.Bind<bool>("QuickStack", "Enabled", true, "Alternate use on a chest of the network stores your items into the network chests that already hold them, instead of opening it.");
			_quickStackCategories = config.Bind<string>("QuickStack", "IncludedCategories", "Material,Trophy", "Comma-separated item types stored (game names: Material, Trophy, Consumable, Ammo, Fish...). The hotbar row and equipped items are never stored.");
			_quickStackForcedItems = config.Bind<string>("QuickStack", "ForcedItems", string.Empty, "Comma-separated items always stored, whatever their type (e.g. $item_raspberries).");
			_quickStackExcludedItems = config.Bind<string>("QuickStack", "ExcludedItems", string.Empty, "Comma-separated items never stored, whatever their type (e.g. $item_finewood). Wins over ForcedItems.");
			_armorEnabled = config.Bind<bool>("ArmorStand", "Enabled", true, "Alternate use on an armor stand: dress it from your inventory and the network when it holds no armor, else swap armor with it.");
			_armorProtectHotbar = config.Bind<bool>("ArmorStand", "ProtectHotbar", true, "Never take armor from the hotbar row when dressing a stand.");
			_armorSetWeight = config.Bind<float>("ArmorStand", "SetWeight", 100f, "Dressing: cost of a piece outside the chosen set (dominant).");
			_armorTierWeight = config.Bind<float>("ArmorStand", "TierWeight", 10f, "Dressing: cost per tier of difference with the chosen set.");
			_armorQualityWeight = config.Bind<float>("ArmorStand", "QualityWeight", 1f, "Dressing: cost per quality level of difference with the chosen set.");
			_armorSets = config.Bind<string>("ArmorStand", "Sets", "rags:1:ArmorRagsChest,ArmorRagsLegs;leather:1:ArmorLeatherChest,ArmorLeatherLegs,HelmetLeather,CapeDeerHide;troll:2:ArmorTrollLeatherChest,ArmorTrollLeatherLegs,HelmetTrollLeather,CapeTrollHide;bronze:2:ArmorBronzeChest,ArmorBronzeLegs,HelmetBronze;root:3:ArmorRootChest,ArmorRootLegs,HelmetRoot;iron:3:ArmorIronChest,ArmorIronLegs,HelmetIron;wolf:4:ArmorWolfChest,ArmorWolfLegs,HelmetDrake,CapeWolf;fenris:4:ArmorFenringChest,ArmorFenringLegs,HelmetFenring;padded:5:ArmorPaddedCuirass,ArmorPaddedGreaves,HelmetPadded;carapace:6:ArmorCarapaceChest,ArmorCarapaceLegs,HelmetCarapace,CapeFeather;mage:6:ArmorMageChest,ArmorMageLegs,HelmetMage;flametal:7:ArmorFlametalChest,ArmorFlametalLegs;askvin:7:ArmorAshlandsMediumChest,ArmorAshlandsMediumlegs,HelmetAshlandsMediumHood,CapeAsksvin;deepnorthheavy:8:ArmorDeepNorthHeavyChest,ArmorDeepNorthHeavylegs,HelmetDNHeavy;deepnorthmedium:8:ArmorDeepNorthMediumChest,ArmorDeepNorthMediumlegs,HelmetDNMedium;deepnorthmage:8:ArmorDeepNorthMageChest,ArmorDeepNorthMagelegs,HelmetDNMage", "Armor sets, used for items without a game set name: set:tier:prefab,prefab;... Tiers follow the biomes (1 = Meadows). Add armors of other mods here.");
			LocalModConfig = Build();
			FillSettings = BuildFillSettings();
			DepositSettings = BuildDepositSettings();
			BuildArmorSettings();
			config.SettingChanged += delegate
			{
				LocalModConfig = Build();
				FillSettings = BuildFillSettings();
				DepositSettings = BuildDepositSettings();
				BuildArmorSettings();
				HandshakePatches.BroadcastConfig();
			};
		}

		private FillSettings BuildFillSettings()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			FillSettings obj = FillSettings.Default.<Clone>$();
			obj.set_InventoryUse(_fillInventoryUse.Value);
			obj.set_MinimumReserve(Math.Max(0, _fillMinimumReserve.Value));
			obj.set_ReserveScope(_fillReserveScope.Value);
			obj.set_MaxPerAction((_fillMaxPerAction.Value > 0) ? new int?(_fillMaxPerAction.Value) : ((int?)null));
			obj.set_ForbiddenItems((IReadOnlyCollection<string>)(object)SplitList(_fillForbiddenItems.Value));
			return obj;
		}

		private DepositSettings BuildDepositSettings()
		{
			DepositSettings obj = DepositSettings.Default.<Clone>$();
			obj.set_IncludedCategories((IReadOnlyCollection<string>)(object)SplitList(_quickStackCategories.Value));
			obj.set_ForcedItems((IReadOnlyCollection<string>)(object)SplitList(_quickStackForcedItems.Value));
			obj.set_ExcludedItems((IReadOnlyCollection<string>)(object)SplitList(_quickStackExcludedItems.Value));
			return obj;
		}

		private void BuildArmorSettings()
		{
			ArmorFillSettings obj = ArmorFillSettings.Default.<Clone>$();
			obj.set_SetWeight(Weight(_armorSetWeight.Value, ArmorFillSettings.Default.SetWeight));
			obj.set_TierWeight(Weight(_armorTierWeight.Value, ArmorFillSettings.Default.TierWeight));
			obj.set_QualityWeight(Weight(_armorQualityWeight.Value, ArmorFillSettings.Default.QualityWeight));
			obj.set_ProtectHotbar(_armorProtectHotbar.Value);
			ArmorFillSettings = obj;
			ArmorSetCatalog armorSets = default(ArmorSetCatalog);
			string text = default(string);
			if (ArmorSetCatalog.TryParse(_armorSets.Value, ref armorSets, ref text))
			{
				ArmorSets = armorSets;
				return;
			}
			ChestNetworkPlugin.Log.LogWarning((object)("[ArmorStand] Sets: " + text + " Using the vanilla table."));
			ArmorSets = ArmorSetCatalog.Vanilla;
		}

		private static double Weight(float value, double fallback)
		{
			if (!(value >= 0f) || float.IsNaN(value) || float.IsInfinity(value))
			{
				return fallback;
			}
			return value;
		}

		private static string[] SplitList(string value)
		{
			return (from entry in value.Split(',')
				select entry.Trim() into entry
				where entry.Length > 0
				select entry).ToArray();
		}

		private ModConfig Build()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			string[] array = SplitList(_allowedStationTypes.Value);
			List<ContainerKind> list = new List<ContainerKind>();
			if (!_useCarts.Value)
			{
				list.Add((ContainerKind)1);
			}
			if (!_useShips.Value)
			{
				list.Add((ContainerKind)2);
			}
			ModConfig obj = ModConfig.Default.<Clone>$();
			obj.set_ConnectionRule(_connectionRule.Value);
			obj.set_InclusiveBoundaries(_inclusiveBoundaries.Value);
			obj.set_AllowedStationTypes((IReadOnlyCollection<string>)(object)((array.Length == 0) ? null : array));
			obj.set_ExcludedContainerKinds((IReadOnlyCollection<ContainerKind>)list);
			obj.set_AllowEquippedItems(_allowEquippedItems.Value);
			obj.set_QualityMatching((QualityMatching)1);
			return obj;
		}
	}
	internal static class PluginVersion
	{
		public const string Value = "0.4.0";
	}
}
namespace ChestNetwork.Plugin.Texts
{
	internal static class ModTexts
	{
		[HarmonyPatch(typeof(Localization), "SetupLanguage")]
		private static class SetupLanguagePatch
		{
			private static void Postfix(Localization __instance, string language)
			{
				Register(__instance, language);
			}
		}

		public const string FillPreparing = "$chestnetwork_fill_preparing";

		public const string FillMachineBusy = "$chestnetwork_fill_machine_busy";

		public const string FillAlreadyFull = "$chestnetwork_fill_already_full";

		public const string FillNothingToAdd = "$chestnetwork_fill_nothing_to_add";

		public const string FillHoverAction = "$chestnetwork_fill_hover";

		public const string WaitingForContainers = "$chestnetwork_waiting_for_containers";

		public const string StoreHoverAction = "$chestnetwork_store_hover";

		public const string StoreNotInNetwork = "$chestnetwork_store_not_in_network";

		public const string StorePreparing = "$chestnetwork_store_preparing";

		public const string StoreNothing = "$chestnetwork_store_nothing";

		public const string StoreFull = "$chestnetwork_store_full";

		public const string StoreDone = "$chestnetwork_store_done";

		public const string ArmorFillHover = "$chestnetwork_armor_fill_hover";

		public const string ArmorSwapHover = "$chestnetwork_armor_swap_hover";

		public const string ArmorNothing = "$chestnetwork_armor_nothing";

		public const string ArmorPreparing = "$chestnetwork_armor_preparing";

		public const string ArmorBusy = "$chestnetwork_armor_busy";

		private const string Fallback = "English";

		private static readonly Dictionary<string, Dictionary<string, string>> ByLanguage = new Dictionary<string, Dictionary<string, string>>
		{
			["English"] = new Dictionary<string, string>
			{
				["$chestnetwork_fill_preparing"] = "Preparing to fill...",
				["$chestnetwork_fill_machine_busy"] = "Machine used by another player, try again",
				["$chestnetwork_fill_already_full"] = "Already full",
				["$chestnetwork_fill_nothing_to_add"] = "Nothing to add",
				["$chestnetwork_fill_hover"] = "Fill from chests",
				["$chestnetwork_waiting_for_containers"] = "Chest network: waiting for containers held by other players...",
				["$chestnetwork_store_hover"] = "Store in network (items already there)",
				["$chestnetwork_store_not_in_network"] = "This chest is not in a crafting station network",
				["$chestnetwork_store_preparing"] = "Preparing to store...",
				["$chestnetwork_store_nothing"] = "Nothing to store",
				["$chestnetwork_store_full"] = "$1 chest full",
				["$chestnetwork_store_done"] = "Stored $1 item(s) in $2 chest(s)",
				["$chestnetwork_armor_fill_hover"] = "Dress the stand",
				["$chestnetwork_armor_swap_hover"] = "Swap armor",
				["$chestnetwork_armor_nothing"] = "No armor found",
				["$chestnetwork_armor_preparing"] = "Preparing the armor stand...",
				["$chestnetwork_armor_busy"] = "Armor stand used by another player, try again"
			},
			["French"] = new Dictionary<string, string>
			{
				["$chestnetwork_fill_preparing"] = "Préparation du remplissage...",
				["$chestnetwork_fill_machine_busy"] = "Machine utilisée par un autre joueur, réessayez",
				["$chestnetwork_fill_already_full"] = "Déjà plein",
				["$chestnetwork_fill_nothing_to_add"] = "Rien à ajouter",
				["$chestnetwork_fill_hover"] = "Remplir depuis les coffres",
				["$chestnetwork_waiting_for_containers"] = "Réseau de coffres : attente des coffres détenus par d'autres joueurs...",
				["$chestnetwork_store_hover"] = "Ranger dans le réseau (items déjà présents)",
				["$chestnetwork_store_not_in_network"] = "Ce coffre n'est pas dans un réseau d'établis",
				["$chestnetwork_store_preparing"] = "Préparation du rangement...",
				["$chestnetwork_store_nothing"] = "Rien à ranger",
				["$chestnetwork_store_full"] = "Coffre de $1 plein",
				["$chestnetwork_store_done"] = "$1 objet(s) rangé(s) dans $2 coffre(s)",
				["$chestnetwork_armor_fill_hover"] = "Garnir",
				["$chestnetwork_armor_swap_hover"] = "Échanger l'armure",
				["$chestnetwork_armor_nothing"] = "Aucune armure trouvée",
				["$chestnetwork_armor_preparing"] = "Préparation du porte-armure...",
				["$chestnetwork_armor_busy"] = "Porte-armure utilisé par un autre joueur, réessayez"
			},
			["German"] = new Dictionary<string, string>
			{
				["$chestnetwork_fill_preparing"] = "Befüllung wird vorbereitet...",
				["$chestnetwork_fill_machine_busy"] = "Maschine wird von einem anderen Spieler benutzt, versuche es erneut",
				["$chestnetwork_fill_already_full"] = "Bereits voll",
				["$chestnetwork_fill_nothing_to_add"] = "Nichts hinzuzufügen",
				["$chestnetwork_fill_hover"] = "Aus Truhen befüllen",
				["$chestnetwork_waiting_for_containers"] = "Truhennetzwerk: warte auf Truhen anderer Spieler...",
				["$chestnetwork_store_hover"] = "Im Netzwerk einlagern (bereits vorhandene Gegenstände)",
				["$chestnetwork_store_not_in_network"] = "Diese Truhe gehört zu keinem Werkbanknetzwerk",
				["$chestnetwork_store_preparing"] = "Einlagerung wird vorbereitet...",
				["$chestnetwork_store_nothing"] = "Nichts einzulagern",
				["$chestnetwork_store_full"] = "Truhe für $1 voll",
				["$chestnetwork_store_done"] = "$1 Gegenstände in $2 Truhe(n) eingelagert",
				["$chestnetwork_armor_fill_hover"] = "Ständer ausstatten",
				["$chestnetwork_armor_swap_hover"] = "Rüstung tauschen",
				["$chestnetwork_armor_nothing"] = "Keine Rüstung gefunden",
				["$chestnetwork_armor_preparing"] = "Rüstungsständer wird vorbereitet...",
				["$chestnetwork_armor_busy"] = "Rüstungsständer wird von einem anderen Spieler benutzt, versuche es erneut"
			},
			["Spanish"] = new Dictionary<string, string>
			{
				["$chestnetwork_fill_preparing"] = "Preparando el llenado...",
				["$chestnetwork_fill_machine_busy"] = "Máquina en uso por otro jugador, inténtalo de nuevo",
				["$chestnetwork_fill_already_full"] = "Ya está lleno",
				["$chestnetwork_fill_nothing_to_add"] = "Nada que añadir",
				["$chestnetwork_fill_hover"] = "Llenar desde los cofres",
				["$chestnetwork_waiting_for_containers"] = "Red de cofres: esperando cofres de otros jugadores...",
				["$chestnetwork_store_hover"] = "Guardar en la red (objetos ya presentes)",
				["$chestnetwork_store_not_in_network"] = "Este cofre no está en una red de mesas de trabajo",
				["$chestnetwork_store_preparing"] = "Preparando el almacenamiento...",
				["$chestnetwork_store_nothing"] = "Nada que guardar",
				["$chestnetwork_store_full"] = "Cofre de $1 lleno",
				["$chestnetwork_store_done"] = "$1 objeto(s) guardado(s) en $2 cofre(s)",
				["$chestnetwork_armor_fill_hover"] = "Equipar el soporte",
				["$chestnetwork_armor_swap_hover"] = "Intercambiar armadura",
				["$chestnetwork_armor_nothing"] = "No se encontró armadura",
				["$chestnetwork_armor_preparing"] = "Preparando el soporte de armadura...",
				["$chestnetwork_armor_busy"] = "Soporte de armadura en uso por otro jugador, inténtalo de nuevo"
			},
			["Portuguese_Brazilian"] = new Dictionary<string, string>
			{
				["$chestnetwork_fill_preparing"] = "Preparando o abastecimento...",
				["$chestnetwork_fill_machine_busy"] = "Máquina em uso por outro jogador, tente novamente",
				["$chestnetwork_fill_already_full"] = "Já está cheio",
				["$chestnetwork_fill_nothing_to_add"] = "Nada para adicionar",
				["$chestnetwork_fill_hover"] = "Abastecer a partir dos baús",
				["$chestnetwork_waiting_for_containers"] = "Rede de baús: aguardando baús de outros jogadores...",
				["$chestnetwork_store_hover"] = "Guardar na rede (itens já presentes)",
				["$chestnetwork_store_not_in_network"] = "Este baú não está em uma rede de bancadas",
				["$chestnetwork_store_preparing"] = "Preparando o armazenamento...",
				["$chestnetwork_store_nothing"] = "Nada para guardar",
				["$chestnetwork_store_full"] = "Baú de $1 cheio",
				["$chestnetwork_store_done"] = "$1 item(ns) guardado(s) em $2 baú(s)",
				["$chestnetwork_armor_fill_hover"] = "Equipar o suporte",
				["$chestnetwork_armor_swap_hover"] = "Trocar armadura",
				["$chestnetwork_armor_nothing"] = "Nenhuma armadura encontrada",
				["$chestnetwork_armor_preparing"] = "Preparando o suporte de armadura...",
				["$chestnetwork_armor_busy"] = "Suporte de armadura em uso por outro jogador, tente novamente"
			},
			["Polish"] = new Dictionary<string, string>
			{
				["$chestnetwork_fill_preparing"] = "Przygotowywanie napełniania...",
				["$chestnetwork_fill_machine_busy"] = "Maszyna jest używana przez innego gracza, spróbuj ponownie",
				["$chestnetwork_fill_already_full"] = "Już pełne",
				["$chestnetwork_fill_nothing_to_add"] = "Nie ma czego dodać",
				["$chestnetwork_fill_hover"] = "Napełnij ze skrzyń",
				["$chestnetwork_waiting_for_containers"] = "Sieć skrzyń: oczekiwanie na skrzynie innych graczy...",
				["$chestnetwork_store_hover"] = "Odłóż do sieci (przedmioty już obecne)",
				["$chestnetwork_store_not_in_network"] = "Ta skrzynia nie należy do sieci stołów warsztatowych",
				["$chestnetwork_store_preparing"] = "Przygotowywanie odkładania...",
				["$chestnetwork_store_nothing"] = "Nic do odłożenia",
				["$chestnetwork_store_full"] = "Skrzynia na $1 pełna",
				["$chestnetwork_store_done"] = "Odłożono przedmioty: $1, skrzynie: $2",
				["$chestnetwork_armor_fill_hover"] = "Ubierz stojak",
				["$chestnetwork_armor_swap_hover"] = "Zamień zbroję",
				["$chestnetwork_armor_nothing"] = "Nie znaleziono zbroi",
				["$chestnetwork_armor_preparing"] = "Przygotowywanie stojaka na zbroję...",
				["$chestnetwork_armor_busy"] = "Stojak na zbroję jest używany przez innego gracza, spróbuj ponownie"
			},
			["Russian"] = new Dictionary<string, string>
			{
				["$chestnetwork_fill_preparing"] = "Подготовка к заполнению...",
				["$chestnetwork_fill_machine_busy"] = "Машина используется другим игроком, попробуйте ещё раз",
				["$chestnetwork_fill_already_full"] = "Уже заполнено",
				["$chestnetwork_fill_nothing_to_add"] = "Нечего добавить",
				["$chestnetwork_fill_hover"] = "Заполнить из сундуков",
				["$chestnetwork_waiting_for_containers"] = "Сеть сундуков: ожидание сундуков других игроков...",
				["$chestnetwork_store_hover"] = "Сложить в сеть (уже имеющиеся предметы)",
				["$chestnetwork_store_not_in_network"] = "Этот сундук не входит в сеть верстаков",
				["$chestnetwork_store_preparing"] = "Подготовка к размещению...",
				["$chestnetwork_store_nothing"] = "Нечего сложить",
				["$chestnetwork_store_full"] = "Сундук для $1 заполнен",
				["$chestnetwork_store_done"] = "Сложено предметов: $1, сундуков: $2",
				["$chestnetwork_armor_fill_hover"] = "Одеть стойку",
				["$chestnetwork_armor_swap_hover"] = "Обменяться бронёй",
				["$chestnetwork_armor_nothing"] = "Броня не найдена",
				["$chestnetwork_armor_preparing"] = "Подготовка стойки для брони...",
				["$chestnetwork_armor_busy"] = "Стойка для брони используется другим игроком, попробуйте ещё раз"
			},
			["Chinese"] = new Dictionary<string, string>
			{
				["$chestnetwork_fill_preparing"] = "正在准备填充...",
				["$chestnetwork_fill_machine_busy"] = "机器正被其他玩家使用,请重试",
				["$chestnetwork_fill_already_full"] = "已满",
				["$chestnetwork_fill_nothing_to_add"] = "没有可添加的物品",
				["$chestnetwork_fill_hover"] = "从箱子中填充",
				["$chestnetwork_waiting_for_containers"] = "箱子网络:正在等待其他玩家持有的箱子...",
				["$chestnetwork_store_hover"] = "存入网络(已有的物品)",
				["$chestnetwork_store_not_in_network"] = "这个箱子不在工作台网络中",
				["$chestnetwork_store_preparing"] = "正在准备存放...",
				["$chestnetwork_store_nothing"] = "没有可存放的物品",
				["$chestnetwork_store_full"] = "$1箱子已满",
				["$chestnetwork_store_done"] = "已将$1件物品存入$2个箱子",
				["$chestnetwork_armor_fill_hover"] = "装备盔甲架",
				["$chestnetwork_armor_swap_hover"] = "交换盔甲",
				["$chestnetwork_armor_nothing"] = "未找到盔甲",
				["$chestnetwork_armor_preparing"] = "正在准备盔甲架...",
				["$chestnetwork_armor_busy"] = "盔甲架正被其他玩家使用,请重试"
			}
		};

		public static void Register(Localization localization, string language)
		{
			ByLanguage.TryGetValue(language, out Dictionary<string, string> value);
			foreach (KeyValuePair<string, string> item in ByLanguage["English"])
			{
				string value2;
				string text = ((value != null && value.TryGetValue(item.Key, out value2)) ? value2 : item.Value);
				localization.AddWord(item.Key.Substring(1), text);
			}
		}

		public static void RegisterIfReady()
		{
			Localization instance = Localization.m_instance;
			if (instance != null)
			{
				Register(instance, instance.GetSelectedLanguage());
			}
		}
	}
}
namespace ChestNetwork.Plugin.Storage
{
	internal static class QuickStackCoordinator
	{
		public static bool TryStart(Player player, Container target)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkSession.FeaturesEnabled || !ChestNetworkPlugin.Settings.QuickStackEnabled || !IsBuiltChest(target))
			{
				return false;
			}
			ContainerRegistry.Entry entry = ContainerRegistry.Find(GameSnapshots.IdOf(target));
			Container openedByPlayer = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_currentContainer : null);
			ContainerSnapshot val = ((entry != null) ? GameSnapshots.Container(entry, player.GetPlayerID(), openedByPlayer) : null);
			if (val == (ContainerSnapshot)null)
			{
				return false;
			}
			if (!val.IsAccessible)
			{
				((Character)player).Message((MessageType)2, "$msg_cantopen", 0, (Sprite)null, false);
				return true;
			}
			NetworkContext networkContext = NetworkContext.At(player, ((Component)player).transform.position, committable: false);
			ContainerSelector val2 = new ContainerSelector(NetworkSession.Config);
			StationNetwork network = networkContext.Network;
			ContainerSnapshot[] array = new ContainerSnapshot[1];
			ContainerSnapshot obj = val.<Clone>$();
			obj.set_IsInUse(false);
			array[0] = obj;
			if (val2.Select(network, (IEnumerable<ContainerSnapshot>)(object)array).Count == 0)
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_store_not_in_network", 0, (Sprite)null, false);
				return true;
			}
			DepositPlan val3 = Plan(player, networkContext, skipUnavailable: true);
			if (val3.TotalDeposited == 0L)
			{
				Report(player, val3, 0L, 0);
				return true;
			}
			List<ZNetView> needed = ContainerRegistry.ViewsOf(val3.ContainersTouched);
			if (!NetworkTransaction.Begin(player, needed, Commit))
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_store_preparing", 0, (Sprite)null, false);
			}
			return true;
		}

		public static bool IsNetworkChest(Player player, Container container)
		{
			if (IsBuiltChest(container))
			{
				return NetworkContext.Wide(player).Selected.Any((ContainerSnapshot snapshot) => snapshot.Id == GameSnapshots.IdOf(container));
			}
			return false;
		}

		private static bool IsBuiltChest(Container container)
		{
			if ((Object)(object)container == (Object)null || (Object)(object)container.m_nview == (Object)null || !container.m_nview.IsValid())
			{
				return false;
			}
			Piece componentInParent = ((Component)container).GetComponentInParent<Piece>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				return componentInParent.IsPlacedByPlayer();
			}
			return false;
		}

		private static DepositPlan Plan(Player player, NetworkContext context, bool skipUnavailable)
		{
			//IL_0040: 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)
			IEnumerable<ContainerSnapshot> enumerable = context.Selected;
			if (skipUnavailable)
			{
				enumerable = enumerable.Where((ContainerSnapshot c) => !OwnershipCoordinator.IsUnavailable(context.EntriesById[c.Id].Container.m_nview));
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			IReadOnlyList<ItemStack> readOnlyList = new DepositFilter(ChestNetworkPlugin.Settings.DepositSettings).Select((IEnumerable<CategorizedStack>)GameSnapshots.CategorizedItems(inventory), inventory.GetWidth());
			return new DepositPlanner().Plan((IEnumerable<ItemStack>)readOnlyList, enumerable);
		}

		private static void Commit(Player player)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: 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_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			NetworkContext networkContext = NetworkContext.At(player, ((Component)player).transform.position, committable: true);
			DepositPlan val = Plan(player, networkContext, skipUnavailable: false);
			ValidationResult val2 = new PlanValidator(NetworkSession.Config).ValidateDeposit(val, (IEnumerable<ItemStack>)GameSnapshots.Items(((Humanoid)player).GetInventory()), (IEnumerable<ContainerSnapshot>)networkContext.Reread(val.ContainersTouched));
			if (!val2.IsValid)
			{
				ChestNetworkPlugin.Log.LogInfo((object)("Quick stack invalidated before commit: " + string.Join("; ", val2.Issues)));
				return;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			int width = inventory.GetWidth();
			long num = 0L;
			HashSet<string> hashSet = new HashSet<string>();
			foreach (StackDeposit deposit in val.Deposits)
			{
				Vector2i val3 = GameSnapshots.GridPosOf(deposit.SourceSlot, width);
				ItemData itemAt = inventory.GetItemAt(val3.x, val3.y);
				if (itemAt != null && !(GameSnapshots.StackKey(itemAt) != deposit.StackKey))
				{
					Inventory inventory2 = networkContext.EntriesById[deposit.ContainerId].Container.GetInventory();
					Vector2i val4 = GameSnapshots.GridPosOf(deposit.TargetSlot, inventory2.GetWidth());
					int stack = itemAt.m_stack;
					inventory2.MoveItemToThis(inventory, itemAt, (int)Math.Min(deposit.Amount, stack), val4.x, val4.y);
					int num2 = stack - itemAt.m_stack;
					if (num2 > 0)
					{
						num += num2;
						hashSet.Add(deposit.ContainerId);
					}
				}
			}
			OwnershipCoordinator.Reserve(hashSet);
			NetworkContext.Invalidate();
			Report(player, val, num, hashSet.Count);
		}

		private static void Report(Player player, DepositPlan plan, long stored, int chests)
		{
			foreach (DepositRemainder item in plan.NoRoom)
			{
				string text = Localization.instance.Localize(item.ItemId);
				((Character)player).Message((MessageType)1, Localization.instance.Localize("$chestnetwork_store_full", new string[1] { text }), 0, (Sprite)null, false);
			}
			string text2 = ((stored > 0) ? Localization.instance.Localize("$chestnetwork_store_done", new string[2]
			{
				stored.ToString(CultureInfo.InvariantCulture),
				chests.ToString(CultureInfo.InvariantCulture)
			}) : "$chestnetwork_store_nothing");
			((Character)player).Message((MessageType)2, text2, 0, (Sprite)null, false);
		}
	}
}
namespace ChestNetwork.Plugin.Patches
{
	internal static class ArmorStandPatches
	{
		[HarmonyPatch(typeof(ArmorStand), "Awake")]
		private static class ArmorStandAwake
		{
			private static void Postfix(ArmorStand __instance)
			{
				OwnershipCoordinator.RegisterMachine(__instance.m_nview);
			}
		}

		public static bool TrySwitch(Player player, Switch sw)
		{
			ArmorStand componentInParent = ((Component)sw).GetComponentInParent<ArmorStand>();
			if ((Object)(object)componentInParent != (Object)null && componentInParent.m_slots.Exists((ArmorStandSlot slot) => (Object)(object)slot.m_switch == (Object)(object)sw))
			{
				return ArmorStandCoordinator.TryStart(player, componentInParent);
			}
			return false;
		}

		public static bool TryHovered(Player player, GameObject hovering)
		{
			ArmorStand componentInParent = hovering.GetComponentInParent<ArmorStand>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				return ArmorStandCoordinator.TryStart(player, componentInParent);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "ConsumeResources")]
	internal static class ConsumeResourcesPatch
	{
		private static bool Prefix(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected O, but got Unknown
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkSession.FeaturesEnabled || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || requirements == null)
			{
				return true;
			}
			List<Requirement> list = RequirementMapper.ToCore(__instance, requirements, qualityLevel, itemQuality, multiplier);
			Inventory inventory = ((Humanoid)__instance).GetInventory();
			ConsumptionPlanner val = new ConsumptionPlanner(NetworkSession.Config);
			PlanValidator validator = new PlanValidator(NetworkSession.Config);
			NetworkContext networkContext = NetworkContext.Committable(__instance);
			PlanResult val2 = val.Plan((IEnumerable<Requirement>)list, (IEnumerable<ItemStack>)GameSnapshots.Items(inventory), (IEnumerable<ContainerSnapshot>)networkContext.Selected);
			if (val2.IsSuccess && !Revalidate(val2.Plan, inventory, networkContext, validator))
			{
				networkContext = NetworkContext.Committable(__instance, forceRescan: true);
				val2 = val.Plan((IEnumerable<Requirement>)list, (IEnumerable<ItemStack>)GameSnapshots.Items(inventory), (IEnumerable<ContainerSnapshot>)networkContext.Selected);
			}
			if (!val2.IsSuccess)
			{
				ChestNetworkPlugin.Log.LogWarning((object)$"No complete plan ({val2.Status}), falling back to vanilla consumption.");
				return true;
			}
			Commit(val2.Plan, inventory, networkContext);
			NetworkContext.Invalidate();
			return false;
		}

		private static bool Revalidate(ConsumptionPlan plan, Inventory inventory, NetworkContext context, PlanValidator validator)
		{
			List<ContainerSnapshot> list = context.Reread(from w in plan.Withdrawals
				where !w.IsFromInventory
				select w.ContainerId);
			ValidationResult val = validator.Validate(plan, (IEnumerable<ItemStack>)GameSnapshots.Items(inventory), (IEnumerable<ContainerSnapshot>)list);
			if (!val.IsValid)
			{
				ChestNetworkPlugin.Log.LogInfo((object)("Plan invalidated before commit: " + string.Join("; ", val.Issues)));
			}
			return val.IsValid;
		}

		private static void Commit(ConsumptionPlan plan, Inventory playerInventory, NetworkContext context)
		{
			HashSet<string> hashSet = new HashSet<string>();
			bool allowEquippedItems = NetworkSession.Config.AllowEquippedItems;
			foreach (Withdrawal withdrawal in plan.Withdrawals)
			{
				Inventory inventory;
				if (withdrawal.IsFromInventory)
				{
					inventory = playerInventory;
				}
				else
				{
					inventory = context.EntriesById[withdrawal.ContainerId].Container.GetInventory();
					hashSet.Add(withdrawal.ContainerId);
				}
				InventoryOps.Remove(inventory, withdrawal.ItemId, withdrawal.Quality, withdrawal.Amount, allowEquippedItems || !withdrawal.IsFromInventory);
			}
			if (hashSet.Count > 0)
			{
				OwnershipCoordinator.Reserve(hashSet);
				ChestNetworkPlugin.Log.LogInfo((object)$"Resources taken from {hashSet.Count} container(s) of the network.");
			}
		}
	}
	internal static class ContainerPatches
	{
		[HarmonyPatch(typeof(Container), "Interact")]
		private static class ContainerInteract
		{
			private static bool Prefix(Container __instance, Humanoid character, bool hold, bool alt, ref bool __result)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Expected O, but got Unknown
				if (!AltInteraction.IsAltUse(character, hold, alt) || !QuickStackCoordinator.TryStart((Player)character, __instance))
				{
					return true;
				}
				__result = true;
				return false;
			}
		}

		[HarmonyPatch(typeof(Container), "GetHoverText")]
		private static class ContainerHover
		{
			private static void Postfix(Container __instance, ref string __result)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null && AltInteraction.Available && ChestNetworkPlugin.Settings.QuickStackEnabled && QuickStackCoordinator.IsNetworkChest(localPlayer, __instance))
				{
					__result += AltInteraction.HoverLine("$chestnetwork_store_hover");
				}
			}
		}

		public static bool TryStoreHovered(Player player, GameObject hovering)
		{
			Container componentInParent = hovering.GetComponentInParent<Container>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				return QuickStackCoordinator.TryStart(player, componentInParent);
			}
			return false;
		}
	}
	internal static class MachinePatches
	{
		[HarmonyPatch(typeof(Fireplace), "Interact")]
		private static class FireplaceInteract
		{
			private static bool Prefix(Fireplace __instance, Humanoid user, bool hold, bool alt, ref bool __result)
			{
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Expected O, but got Unknown
				if (!AltInteraction.IsAltUse(user, hold, alt))
				{
					return true;
				}
				MachineSlot machineSlot = MachineSlot.FromFireplace(__instance);
				if (machineSlot == null || !FillCoordinator.TryStart((Player)user, machineSlot))
				{
					return true;
				}
				__result = true;
				return false;
			}
		}

		[HarmonyPatch(typeof(Fireplace), "GetHoverText")]
		private static class FireplaceHover
		{
			private static void Postfix(Fireplace __instance, ref string __result)
			{
				if (!string.IsNullOrEmpty(__result) && ShowsHoverLine(MachineSlot.FromFireplace(__instance)))
				{
					__result += AltInteraction.HoverLine("$chestnetwork_fill_hover");
				}
			}
		}

		[HarmonyPatch(typeof(Smelter), "Awake")]
		private static class SmelterAwake
		{
			private static void Postfix(Smelter __instance)
			{
				OwnershipCoordinator.RegisterMachine(__instance.m_nview);
			}
		}

		[HarmonyPatch(typeof(Fireplace), "Awake")]
		private static class FireplaceAwake
		{
			private static void Postfix(Fireplace __instance)
			{
				OwnershipCoordinator.RegisterMachine(__instance.m_nview);
			}
		}

		private static bool ShowsHoverLine(MachineSlot? slot)
		{
			if (slot != null && AltInteraction.Available && ChestNetworkPlugin.Settings.FillEnabled)
			{
				return !double.IsNaN(slot.RemainingCapacity());
			}
			return false;
		}

		public static bool TryFillHovered(Player player, GameObject hovering)
		{
			Switch componentInParent = hovering.GetComponentInParent<Switch>();
			MachineSlot machineSlot = (((Object)(object)componentInParent != (Object)null) ? MachineSlot.FromSwitch(componentInParent) : null);
			if (machineSlot == null)
			{
				Fireplace componentInParent2 = hovering.GetComponentInParent<Fireplace>();
				machineSlot = (((Object)(object)componentInParent2 != (Object)null) ? MachineSlot.FromFireplace(componentInParent2) : null);
			}
			if (machineSlot != null)
			{
				return FillCoordinator.TryStart(player, machineSlot);
			}
			return false;
		}

		public static bool TryFillSwitch(Player player, Switch sw)
		{
			MachineSlot machineSlot = MachineSlot.FromSwitch(sw);
			if (machineSlot != null)
			{
				return FillCoordinator.TryStart(player, machineSlot);
			}
			return false;
		}

		public static string? FillSwitchHover(Player player, Switch sw)
		{
			if (!ShowsHoverLine(MachineSlot.FromSwitch(sw)))
			{
				return null;
			}
			return "$chestnetwork_fill_hover";
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirement")]
	internal static class RequirementDisplayPatch
	{
		private static void Postfix(Transform elementRoot, Requirement req, Player player, int quality, int craftMultiplier, bool __result)
		{
			PluginSettings settings = ChestNetworkPlugin.Settings;
			if (__result && NetworkSession.FeaturesEnabled && settings.ShowAvailableAmount && !((Object)(object)req.m_resItem == (Object)null) && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer))
			{
				int num = req.GetAmount(quality) * craftMultiplier;
				if (num > 0)
				{
					string name = req.m_resItem.m_itemData.m_shared.m_name;
					long available = CountInInventory(((Humanoid)player).GetInventory(), name) + NetworkContext.Wide(player).ContainerStock.GetTotal(name);
					((Component)((Component)elementRoot).transform.Find("res_amount")).GetComponent<TMP_Text>().text = Format(settings.AvailableAmountFormat, num, available);
				}
			}
		}

		private static long CountInInventory(Inventory inventory, string itemName)
		{
			long num = 0L;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem.m_shared.m_name == itemName && allItem.m_worldLevel >= Game.m_worldLevel)
				{
					num += allItem.m_stack;
				}
			}
			return num;
		}

		private static string Format(string format, int required, long available)
		{
			try
			{
				return string.Format(format, required, available);
			}
			catch (FormatException)
			{
				return $"{required}/{available}";
			}
		}
	}
	internal static class RequirementPatches
	{
		[HarmonyPatch]
		private static class CheckScope
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				yield return AccessTools.Method(typeof(Player), "HaveRequirements", new Type[2]
				{
					typeof(Piece),
					typeof(RequirementMode)
				}, (Type[])null);
				yield return AccessTools.Method(typeof(Player), "HaveRequirementItems", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(InventoryGui), "SetupRequirement", (Type[])null, (Type[])null);
			}

			private static void Prefix(out bool __state)
			{
				__state = NetworkSession.FeaturesEnabled;
				if (__state)
				{
					s_checkDepth++;
				}
			}

			private static void Finalizer(bool __state)
			{
				if (__state)
				{
					s_checkDepth--;
				}
			}
		}

		[HarmonyPatch]
		private static class CommitScope
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				yield return AccessTools.Method(typeof(Player), "UpdatePlacement", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(InventoryGui), "DoCrafting", (Type[])null, (Type[])null);
			}

			private static void Prefix()
			{
				s_commitDepth++;
			}

			private static void Finalizer()
			{
				s_commitDepth--;
			}
		}

		[HarmonyPatch(typeof(Player), "HaveRequirements", new Type[]
		{
			typeof(Piece),
			typeof(RequirementMode)
		})]
		private static class ExplainPieceFailure
		{
			private static void Postfix(Player __instance, Piece piece, RequirementMode mode, bool __result)
			{
				//IL_0003: Unknown result type (might be due to invalid IL or missing references)
				if (!__result && (int)mode == 0 && NetworkSession.FeaturesEnabled)
				{
					ExplainCommitFailure(__instance, piece.m_resources);
				}
			}
		}

		[HarmonyPatch(typeof(Player), "HaveRequirementItems")]
		private static class ExplainRecipeFailure
		{
			private static void Postfix(Player __instance, Recipe piece, bool discover, bool __result)
			{
				if (!__result && !discover && NetworkSession.FeaturesEnabled)
				{
					ExplainCommitFailure(__instance, piece.m_resources);
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "CountItems")]
		private static class CountItemsPatch
		{
			private static void Postfix(Inventory __instance, string name, int quality, ref int __result)
			{
				if (name != null)
				{
					ResourceCount val = StockFor(__instance);
					if (val != null)
					{
						long num = ((quality < 0) ? val.GetTotal(name) : val.GetTotal(name, quality));
						__result = (int)Math.Min(2147483647L, __result + num);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "HaveItem", new Type[]
		{
			typeof(string),
			typeof(bool)
		})]
		private static class HaveItemPatch
		{
			private static void Postfix(Inventory __instance, string name, ref bool __result)
			{
				if (!__result && name != null)
				{
					ResourceCount val = StockFor(__instance);
					if (val != null && val.GetTotal(name) > 0)
					{
						__result = true;
					}
				}
			}
		}

		private const string WaitingMessage = "$chestnetwork_waiting_for_containers";

		private const float WaitingMessageInterval = 2f;

		private static int s_checkDepth;

		private static int s_commitDepth;

		private static float s_lastWaitingMessage = float.NegativeInfinity;

		private static ResourceCount? StockFor(Inventory inventory)
		{
			if (s_checkDepth == 0)
			{
				return null;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || inventory != ((Humanoid)localPlayer).GetInventory())
			{
				return null;
			}
			if (s_commitDepth <= 0)
			{
				return NetworkContext.Wide(localPlayer).ContainerStock;
			}
			return NetworkContext.Committable(localPlayer).ContainerStock;
		}

		private static void ExplainCommitFailure(Player player, Requirement[] requirements)
		{
			if (s_commitDepth == 0 || (Object)(object)player != (Object)(object)Player.m_localPlayer || Time.time - s_lastWaitingMessage < 2f)
			{
				return;
			}
			ResourceCount containerStock = NetworkContext.Wide(player).ContainerStock;
			ResourceCount containerStock2 = NetworkContext.Committable(player).ContainerStock;
			foreach (Requirement val in requirements)
			{
				if (!((Object)(object)val.m_resItem == (Object)null))
				{
					string name = val.m_resItem.m_itemData.m_shared.m_name;
					if (containerStock.GetTotal(name) > containerStock2.GetTotal(name))
					{
						s_lastWaitingMessage = Time.time;
						((Character)player).Message((MessageType)2, "$chestnetwork_waiting_for_containers", 0, (Sprite)null, false);
						break;
					}
				}
			}
		}
	}
}
namespace ChestNetwork.Plugin.Ownership
{
	internal static class ActionIntent
	{
		private static readonly List<Requirement> None = new List<Requirement>();

		public static List<Requirement> Current(Player player)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (((Character)player).InPlaceMode())
			{
				Piece selectedPiece = player.GetSelectedPiece();
				if ((Object)(object)selectedPiece == (Object)null || player.m_noPlacementCost || ZoneSystem.instance.GetGlobalKey(selectedPiece.FreeBuildKey()))
				{
					return None;
				}
				return RequirementMapper.ToCore(player, selectedPiece.m_resources, 0, -1, 1);
			}
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance != (Object)null && InventoryGui.IsVisible() && instance.InCraftTab() && (Object)(object)((RecipeDataPair)(ref instance.m_selectedRecipe)).Recipe != (Object)null)
			{
				Recipe recipe = ((RecipeDataPair)(ref instance.m_selectedRecipe)).Recipe;
				ItemData itemData = ((RecipeDataPair)(ref instance.m_selectedRecipe)).ItemData;
				int qualityLevel = ((itemData == null) ? 1 : (itemData.m_quality + 1));
				int multiplier = ((!instance.m_multiCrafting) ? 1 : instance.m_multiCraftAmount);
				return RequirementMapper.ToCore(player, recipe.m_resources, qualityLevel, -1, multiplier);
			}
			return None;
		}
	}
	internal static class NetworkTransaction
	{
		private sealed class Pending
		{
			public Player Player { get; }

			public float Deadline { get; }

			public IReadOnlyList<ZNetView> Needed { get; }

			public Action<Player> Commit { get; }

			public Pending(Player player, float deadline, IReadOnlyList<ZNetView> needed, Action<Player> commit)
			{
				Player = player;
				Deadline = deadline;
				Needed = needed;
				Commit = commit;
			}
		}

		private const float PreparationTimeout = 3.5f;

		private static Pending? s_pending;

		public static bool Begin(Player player, IReadOnlyList<ZNetView> needed, Action<Player> commit)
		{
			s_pending = new Pending(player, Time.time + 3.5f, needed, commit);
			foreach (ZNetView item in needed)
			{
				OwnershipCoordinator.RequestOwnership(item, player);
			}
			return TryComplete();
		}

		public static void Tick()
		{
			if (NetworkSession.FeaturesEnabled)
			{
				TryComplete();
			}
		}

		private static bool TryComplete()
		{
			Pending pending = s_pending;
			if (pending == null)
			{
				return false;
			}
			if (!(Time.time >= pending.Deadline) && !pending.Needed.All(IsSettled))
			{
				return false;
			}
			s_pending = null;
			if ((Object)(object)pending.Player != (Object)null)
			{
				pending.Commit(pending.Player);
			}
			return true;
		}

		private static bool IsSettled(ZNetView view)
		{
			if (!((Object)(object)view == (Object)null) && view.IsValid() && !OwnershipCoordinator.IsCommittable(view))
			{
				return OwnershipCoordinator.IsUnavailable(view);
			}
			return true;
		}
	}
	internal static class OwnershipCoordinator
	{
		private sealed class Shared
		{
			public ZNetView View { get; }

			public Func<bool> IsInUse { get; }

			public Func<long, bool> HasAccess { get; }

			public Action? OnSynced { get; }

			public Shared(ZNetView view, Func<bool> isInUse, Func<long, bool> hasAccess, Action? onSynced)
			{
				View = view;
				IsInUse = isInUse;
				HasAccess = hasAccess;
				OnSynced = onSynced;
			}
		}

		private const string RequestRpc = "ChestNetwork_RequestOwnership";

		private const string ResponseRpc = "ChestNetwork_OwnershipResponse";

		private const double RequestTimeout = 3.0;

		private const double RetryDelay = 4.0;

		private const double ReservationDuration = 2.0;

		private const double IntentInterval = 0.25;

		private const double GrantedOwnershipTimeout = 10.0;

		private static readonly OwnershipTracker Tracker = new OwnershipTracker(3.0, 4.0);

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

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

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

		private static readonly HashSet<int> RegisteredViews = new HashSet<int>();

		private static double s_nextIntentTick;

		private static double Now => Time.time;

		public static string IdOf(ZNetView view)
		{
			return ((object)Unsafe.As<ZDOID, ZDOID>(ref view.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString();
		}

		public static void RegisterContainer(Container container)
		{
			Register(container.m_nview, () => container.IsInUse() || ((Object)(object)container.m_wagon != (Object)null && container.m_wagon.InUse()) || ((Object)(object)InventoryGui.instance != (Object)null && (Object)(object)InventoryGui.instance.m_currentContainer == (Object)(object)container), (long playerId) => GameSnapshots.HasPrivacyAccess(container, playerId), delegate
			{
				container.Load();
			});
		}

		public static void RegisterMachine(ZNetView view)
		{
			Register(view, () => false, (long _) => true, null);
		}

		public static bool IsCommittable(ZNetView view)
		{
			if ((Object)(object)view != (Object)null && view.IsValid() && view.IsOwner())
			{
				return Tracker.IsUsableWhenOwned(IdOf(view), Now);
			}
			return false;
		}

		public static bool PrepareForCommit(Container container)
		{
			if (!IsCommittable(container.m_nview))
			{
				return false;
			}
			container.Load();
			return true;
		}

		public static bool IsUnavailable(ZNetView view)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			return (int)Tracker.StateOf(IdOf(view), Now) == 3;
		}

		public static void Reserve(IEnumerable<string> ids)
		{
			foreach (string id in ids)
			{
				ReservedUntil[id] = Now + 2.0;
			}
		}

		public static void RequestOwnership(ZNetView view, Player player)
		{
			if (!((Object)(object)view == (Object)null) && view.IsValid() && !view.IsOwner())
			{
				if (!view.GetZDO().HasOwner())
				{
					view.ClaimOwnership();
				}
				else if (Tracker.TryBeginRequest(IdOf(view), Now))
				{
					view.InvokeRPC("ChestNetwork_RequestOwnership", new object[1] { player.GetPlayerID() });
				}
			}
		}

		public static void Tick()
		{
			if (!NetworkSession.FeaturesEnabled)
			{
				return;
			}
			SyncGrantedObjects();
			if (!(Now < s_nextIntentTick))
			{
				s_nextIntentTick = Now + 0.25;
				PruneReservations();
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null)
				{
					PrepareIntent(localPlayer);
				}
			}
		}

		private static void Register(ZNetView view, Func<bool> isInUse, Func<long, bool> hasAccess, Action? onSynced)
		{
			if (!((Object)(object)view == (Object)null) && view.GetZDO() != null && RegisteredViews.Add(((Object)view).GetInstanceID()))
			{
				Shared shared = new Shared(view, isInUse, hasAccess, onSynced);
				Registered[IdOf(view)] = shared;
				view.Register<long>("ChestNetwork_RequestOwnership", (Action<long, long>)delegate(long sender, long playerId)
				{
					OnRequest(shared, sender, playerId);
				});
				view.Register<bool, uint>("ChestNetwork_OwnershipResponse", (Action<long, bool, uint>)delegate(long sender, bool granted, uint revision)
				{
					OnResponse(shared, granted, revision);
				});
			}
		}

		private static void PrepareIntent(Player player)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			List<Requirement> list = ActionIntent.Current(player);
			if (list.Count == 0)
			{
				return;
			}
			NetworkContext networkContext = NetworkContext.Wide(player);
			List<ContainerSnapshot> list2 = networkContext.Selected.Where((ContainerSnapshot val2) => (int)Tracker.StateOf(val2.Id, Now) != 3).ToList();
			PlanResult val = new ConsumptionPlanner(NetworkSession.Config).Plan((IEnumerable<Requirement>)list, (IEnumerable<ItemStack>)GameSnapshots.Items(((Humanoid)player).GetInventory()), (IEnumerable<ContainerSnapshot>)list2);
			if (!val.IsSuccess)
			{
				return;
			}
			foreach (string item in (from w in val.Plan.Withdrawals
				where !w.IsFromInventory
				select w.ContainerId).Distinct())
			{
				Container container = networkContext.EntriesById[item].Container;
				if ((Object)(object)container != (Object)null)
				{
					RequestOwnership(container.m_nview, player);
				}
			}
		}

		private static void OnRequest(Shared shared, long sender, long playerId)
		{
			//IL_0051: 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)
			ZNetView view = shared.View;
			if (!((Object)(object)view == (Object)null) && view.IsValid())
			{
				string text = IdOf(view);
				ReservedUntil.TryGetValue(text, out var value);
				if ((int)OwnershipGrantPolicy.Decide(view.IsOwner(), shared.IsInUse(), shared.HasAccess(playerId), value, Now) != 0)
				{
					view.InvokeRPC(sender, "ChestNetwork_OwnershipResponse", new object[2] { false, 0u });
					return;
				}
				ZDO zDO = view.GetZDO();
				ZDOMan.instance.ForceSendZDO(sender, zDO.m_uid);
				zDO.SetOwner(sender);
				Tracker.Forget(text);
				ReservedUntil.Remove(text);
				view.InvokeRPC(sender, "ChestNetwork_OwnershipResponse", new object[2] { true, zDO.DataRevision });
				NetworkContext.Invalidate();
			}
		}

		private static void OnResponse(Shared shared, bool granted, uint revision)
		{
			if (!((Object)(object)shared.View == (Object)null) && shared.View.IsValid())
			{
				string text = IdOf(shared.View);
				if (granted)
				{
					Tracker.OnGranted(text, revision, Now);
					GrantedAt[text] = Now;
				}
				else
				{
					Tracker.OnDenied(text, Now);
				}
				NetworkContext.Invalidate();
			}
		}

		private static void SyncGrantedObjects()
		{
			foreach (string item in Tracker.AwaitingData())
			{
				if (!Registered.TryGetValue(item, out Shared value) || (Object)(object)value.View == (Object)null || !value.View.IsValid())
				{
					Forget(item);
					continue;
				}
				ZNetView view = value.View;
				if (!view.IsOwner())
				{
					GrantedAt.TryGetValue(item, out var value2);
					if (Now - value2 > 10.0)
					{
						Forget(item);
					}
				}
				else if (Tracker.OnDataRevision(item, view.GetZDO().DataRevision))
				{
					GrantedAt.Remove(item);
					value.OnSynced?.Invoke();
					Reserve(new string[1] { item });
					NetworkContext.Invalidate();
				}
			}
			PruneUnloaded();
		}

		private static void Forget(string id)
		{
			Tracker.Forget(id);
			GrantedAt.Remove(id);
		}

		private static void PruneReservations()
		{
			foreach (string item in (from entry in ReservedUntil
				where entry.Value <= Now
				select entry.Key).ToList())
			{
				ReservedUntil.Remove(item);
			}
		}

		private static void PruneUnloaded()
		{
			if (Registered.Count < 256 || Time.frameCount % 300 != 0)
			{
				return;
			}
			foreach (string item in (from entry in Registered
				where (Object)(object)entry.Value.View == (Object)null || !entry.Value.View.IsValid()
				select entry.Key).ToList())
			{
				Registered.Remove(item);
			}
		}
	}
}
namespace ChestNetwork.Plugin.Net
{
	internal static class HandshakePatches
	{
		[HarmonyPatch(typeof(ZNet), "Awake")]
		private static class ResetOnNewSession
		{
			private static void Postfix()
			{
				NetworkSession.Reset();
			}
		}

		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		private static class RegisterOnConnection
		{
			private static void Postfix(ZNet __instance, ZNetPeer peer)
			{
				peer.m_rpc.Register<ZPackage>("ChestNetwork_Version", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage package)
				{
					OnVersion(__instance, rpc, package);
				});
				if (!__instance.IsServer())
				{
					peer.m_rpc.Register<ZPackage>("ChestNetwork_Config", (Action<ZRpc, ZPackage>)OnConfig);
					peer.m_rpc.Invoke("ChestNetwork_Version", new object[1] { VersionPackage(withConfig: false) });
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private static class CheckOnPeerInfo
		{
			private static bool Prefix(ZNet __instance, ZRpc rpc)
			{
				if (__instance.IsServer())
				{
					if (NetworkSession.IsAccepted(rpc))
					{
						return true;
					}
					ChestNetworkPlugin.Log.LogWarning((object)("Client " + rpc.GetSocket().GetEndPointString() + " has no compatible ChestNetwork: connection refused."));
					rpc.Invoke("Error", new object[1] { 3 });
					return false;
				}
				if (NetworkSession.ServerState == NetworkSession.ServerModState.Unknown)
				{
					ChestNetworkPlugin.Log.LogWarning((object)"Server does not run ChestNetwork: mod disabled for this session.");
					NetworkSession.OnServerMissing();
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		private static class ForgetOnDisconnect
		{
			private static void Prefix(ZNetPeer peer)
			{
				if (peer?.m_rpc != null)
				{
					NetworkSession.ForgetClient(peer.m_rpc);
				}
			}
		}

		private const string VersionRpc = "ChestNetwork_Version";

		private const string ConfigRpc = "ChestNetwork_Config";

		public static void BroadcastConfig()
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			foreach (ZRpc client in NetworkSession.Clients)
			{
				if (client.IsConnected())
				{
					client.Invoke("ChestNetwork_Config", new object[1] { ConfigPackage() });
				}
			}
		}

		private static ZPackage VersionPackage(bool withConfig)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(3);
			val.Write("0.4.0");
			val.Write(withConfig ? ModConfigCodec.Encode(ChestNetworkPlugin.Settings.LocalModConfig) : string.Empty);
			return val;
		}

		private static ZPackage ConfigPackage()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(ModConfigCodec.Encode(ChestNetworkPlugin.Settings.LocalModConfig));
			return val;
		}

		private static void OnVersion(ZNet net, ZRpc rpc, ZPackage package)
		{
			int num = package.ReadInt();
			string text = package.ReadString();
			string encoded = package.ReadString();
			if (net.IsServer())
			{
				if (!Protocol.IsCompatible(num))
				{
					ChestNetworkPlugin.Log.LogWarning((object)($"Client {rpc.GetSocket().GetEndPointString()} runs ChestNetwork {text} (protocol {num}), " + $"server needs protocol {3}: connection refused."));
					return;
				}
				NetworkSession.AcceptClient(rpc);
				rpc.Invoke("ChestNetwork_Version", new object[1] { VersionPackage(withConfig: true) });
			}
			else if (!Protocol.IsCompatible(num))
			{
				ChestNetworkPlugin.Log.LogWarning((object)$"Server runs ChestNetwork {text} (protocol {num}): incompatible, mod disabled.");
				NetworkSession.OnServerMissing();
			}
			else
			{
				ApplyServerConfig(encoded, text);
			}
		}

		private static void OnConfig(ZRpc rpc, ZPackage package)
		{
			ApplyServerConfig(package.ReadString(), null);
		}

		private static void ApplyServerConfig(string encoded, string? serverVersion)
		{
			ModConfig config = default(ModConfig);
			string text = default(string);
			if (!ModConfigCodec.TryDecode(encoded, ref config, ref text))
			{
				ChestNetworkPlugin.Log.LogError((object)("Unreadable server configuration (" + text + "): mod disabled."));
				NetworkSession.OnServerMissing();
			}
			else
			{
				NetworkSession.OnServerPresent(config);
				NetworkContext.Invalidate();
				ChestNetworkPlugin.Log.LogInfo((object)((serverVersion == null) ? "Server configuration updated." : ("Server runs ChestNetwork " + serverVersion + ": using its configuration.")));
			}
		}
	}
	internal static class NetworkSession
	{
		public enum ServerModState
		{
			Unknown,
			Present,
			Missing
		}

		private static readonly HashSet<ZRpc> ValidatedClients = new HashSet<ZRpc>();

		private static ModConfig? s_serverConfig;

		public static ServerModState ServerState { get; private set; } = ServerModState.Unknown;

		public static bool IsClient
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					return !ZNet.instance.IsServer();
				}
				return false;
			}
		}

		public static bool FeaturesEnabled
		{
			get
			{
				if (ChestNetworkPlugin.Settings.Enabled)
				{
					if (IsClient)
					{
						return ServerState == ServerModState.Present;
					}
					return true;
				}
				return false;
			}
		}

		public static ModConfig Config
		{
			get
			{
				if (!IsClient || !(s_serverConfig != (ModConfig)null))
				{
					return ChestNetworkPlugin.Settings.LocalModConfig;
				}
				return s_serverConfig;
			}
		}

		public static IEnumerable<ZRpc> Clients => ValidatedClients;

		public static void Reset()
		{
			ValidatedClients.Clear();
			s_serverConfig = null;
			ServerState = ServerModState.Unknown;
		}

		public static void AcceptClient(ZRpc rpc)
		{
			ValidatedClients.Add(rpc);
		}

		public static bool IsAccepted(ZRpc rpc)
		{
			return ValidatedClients.Contains(rpc);
		}

		public static void ForgetClient(ZRpc rpc)
		{
			ValidatedClients.Remove(rpc);
		}

		public static void OnServerPresent(ModConfig config)
		{
			ServerState = ServerModState.Present;
			s_serverConfig = config;
		}

		public static void OnServerMissing()
		{
			ServerState = ServerModState.Missing;
		}
	}
}
namespace ChestNetwork.Plugin.Machines
{
	public enum FillOrigin
	{
		Player,
		Machine
	}
	internal static class FillCoordinator
	{
		public static bool TryStart(Player player, MachineSlot slot)
		{
			//IL_0043: 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)
			if (!NetworkSession.FeaturesEnabled || !ChestNetworkPlugin.Settings.FillEnabled || double.IsNaN(slot.RemainingCapacity()))
			{
				return false;
			}
			FillPlan val = Plan(player, slot, NetworkContext.At(player, OriginOf(player, slot), committable: false), fromCommittableOnly: false);
			if (val.TotalInserted == 0L)
			{
				FillFeedback.Show(player, val.Status, Array.Empty<Withdrawal>());
				return true;
			}
			List<ZNetView> list = new List<ZNetView> { slot.View };
			list.AddRange(ContainerRegistry.ViewsOf(from w in val.Withdrawals
				where !w.IsFromInventory
				select w.ContainerId));
			if (!NetworkTransaction.Begin(player, list, delegate(Player committing)
			{
				Commit(committing, slot);
			}))
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_fill_preparing", 0, (Sprite)null, false);
			}
			return true;
		}

		private static void Commit(Player player, MachineSlot slot)
		{
			//IL_003c: 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_02f0: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)slot.View == (Object)null || !slot.View.IsValid())
			{
				return;
			}
			if (!OwnershipCoordinator.IsCommittable(slot.View))
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_fill_machine_busy", 0, (Sprite)null, false);
				return;
			}
			NetworkContext networkContext = NetworkContext.At(player, OriginOf(player, slot), committable: true);
			FillPlan val = Plan(player, slot, networkContext, fromCommittableOnly: true);
			if (val.TotalInserted == 0L)
			{
				FillFeedback.Show(player, val.Status, Array.Empty<Withdrawal>());
				return;
			}
			bool allowEquippedItems = NetworkSession.Config.AllowEquippedItems;
			foreach (Withdrawal withdrawal2 in val.Withdrawals)
			{
				InventoryOps.Remove(withdrawal2.IsFromInventory ? ((Humanoid)player).GetInventory() : networkContext.EntriesById[withdrawal2.ContainerId].Container.GetInventory(), withdrawal2.ItemId, withdrawal2.Quality, withdrawal2.Amount, allowEquippedItems || !withdrawal2.IsFromInventory);
			}
			List<KeyValuePair<string, long>> list = new List<KeyValuePair<string, long>>();
			foreach (Withdrawal withdrawal in val.Withdrawals)
			{
				int num = list.FindIndex((KeyValuePair<string, long> unit) => unit.Key == withdrawal.ItemId);
				if (num < 0)
				{
					list.Add(new KeyValuePair<string, long>(withdrawal.ItemId, withdrawal.Amount));
				}
				else
				{
					list[num] = new KeyValuePair<string, long>(withdrawal.ItemId, list[num].Value + withdrawal.Amount);
				}
			}
			long num2 = slot.Insert(list);
			FillSettlement val2 = val.Settle(num2);
			foreach (Withdrawal item in val2.Refunded)
			{
				ContainerRegistry.Entry value;
				Container origin = ((item.ContainerId != null && networkContext.EntriesById.TryGetValue(item.ContainerId, out value)) ? value.Container : null);
				ItemReturn.GiveBack(player, item.ItemId, item.Quality, item.Amount, origin);
			}
			if (val2.Refunded.Count > 0)
			{
				ChestNetworkPlugin.Log.LogWarning((object)$"Machine accepted {num2} of {val.TotalInserted} planned units; the rest was given back.");
			}
			List<string> list2 = (from w in val.Withdrawals
				where !w.IsFromInventory
				select w.ContainerId).Distinct().ToList();
			list2.Add(OwnershipCoordinator.IdOf(slot.View));
			OwnershipCoordinator.Reserve(list2);
			NetworkContext.Invalidate();
			FillFeedback.Show(player, val.Status, val2.Inserted);
		}

		private static FillPlan Plan(Player player, MachineSlot slot, NetworkContext context, bool fromCommittableOnly)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			IEnumerable<ContainerSnapshot> enumerable = context.Selected;
			if (!fromCommittableOnly)
			{
				enumerable = enumerable.Where((ContainerSnapshot c) => !OwnershipCoordinator.IsUnavailable(context.EntriesById[c.Id].Container.m_nview));
			}
			return new MachineFillPlanner(NetworkSession.Config, ChestNetworkPlugin.Settings.FillSettings).Plan(slot.RemainingCapacity(), (IEnumerable<string>)slot.AcceptedItems(ChestNetworkPlugin.Settings.FillOrePriority), (IEnumerable<ItemStack>)GameSnapshots.Items(((Humanoid)player).GetInventory()), enumerable);
		}

		private static Vector3 OriginOf(Player player, MachineSlot slot)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (ChestNetworkPlugin.Settings.FillOrigin != FillOrigin.Machine)
			{
				return ((Component)player).transform.position;
			}
			return slot.Transform.position;
		}
	}
	internal static class FillFeedback
	{
		public const string Preparing = "$chestnetwork_fill_preparing";

		public const string MachineBusy = "$chestnetwork_fill_machine_busy";

		public const string AlreadyFull = "$chestnetwork_fill_already_full";

		public const string NothingToAdd = "$chestnetwork_fill_nothing_to_add";

		public const string HoverAction = "$chestnetwork_fill_hover";

		public static void Show(Player player, FillStatus status, IReadOnlyList<Withdrawal> inserted)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			string text = ((inserted.Count > 0) ? Added(inserted) : (((int)status == 2) ? "$chestnetwork_fill_already_full" : (((int)status == 3) ? "$chestnetwork_fill_nothing_to_add" : null)));
			if (text != null)
			{
				((Character)player).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
		}

		private static string Added(IReadOnlyList<Withdrawal> inserted)
		{
			return string.Join(", ", from w in inserted
				group w by w.ItemId into @group
				select $"+{@group.Sum((Withdrawal w) => w.Amount)} {Localization.instance.Localize(@group.Key)}");
		}
	}
	public enum OrePriority
	{
		QueuedFirst,
		MachineOrder
	}
	internal abstract class MachineSlot
	{
		private sealed class SmelterOre : MachineSlot
		{
			private readonly Smelter _smelter;

			public SmelterOre(Smelter smelter)
				: base(smelter.m_nview, ((Component)smelter).transform)
			{
				_smelter = smelter;
			}

			public override double RemainingCapacity()
			{
				return Math.Max(0, _smelter.m_maxOre - _smelter.GetQueueSize());
			}

			public override IReadOnlyList<string> AcceptedItems(OrePriority priority)
			{
				List<string> list = new List<string>();
				Dictionary<string, string> dictionary = new Dictionary<string, string>();
				foreach (ItemConversion item in _smelter.m_conversion)
				{
					if (!((Object)(object)item.m_from == (Object)null))
					{
						string name = item.m_from.m_itemData.m_shared.m_name;
						list.Add(name);
						dictionary[((Object)((Component)item.m_from).gameObject).name] = name;
					}
				}
				if (priority == OrePriority.MachineOrder)
				{
					return list;
				}
				List<string> ordered = new List<string>();
				ZDO zDO = base.View.GetZDO();
				int queueSize = _smelter.GetQueueSize();
				for (int i = 0; i < queueSize; i++)
				{
					if (dictionary.TryGetValue(zDO.GetString("item" + i, ""), out var value) && !ordered.Contains(value))
					{
						ordered.Add(value);
					}
				}
				ordered.AddRange(list.Where((string shared) => !ordered.Contains(shared)));
				return ordered;
			}

			public override long Insert(IReadOnlyList<KeyValuePair<string, long>> units)
			{
				//IL_00a0: 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_00b9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
				long num = (long)RemainingCapacity();
				long num2 = 0L;
				foreach (KeyValuePair<string, long> unit in units)
				{
					string text = PrefabOf(unit.Key);
					long num3 = ((text == null) ? 0 : Math.Min(unit.Value, num - num2));
					for (long num4 = 0L; num4 < num3; num4++)
					{
						_smelter.QueueOre(text, false);
					}
					num2 += num3;
					if (num3 < unit.Value)
					{
						break;
					}
				}
				if (num2 > 0)
				{
					_smelter.m_oreAddedEffects.Create(base.Transform.position, base.Transform.rotation, (Transform)null, 1f, -1, default(ZDOID));
				}
				return num2;
			}

			private string? PrefabOf(string shared)
			{
				return (from conversion in _smelter.m_conversion
					where (Object)(object)conversion.m_from != (Object)null && conversion.m_from.m_itemData.m_shared.m_name == shared
					select ((Object)((Component)conversion.m_from).gameObject).name).FirstOrDefault();
			}
		}

		private sealed class SmelterFuel : MachineSlot
		{
			private readonly Smelter _smelter;

			public SmelterFuel(Smelter smelter)
				: base(smelter.m_nview, ((Component)smelter).transform)
			{
				_smelter = smelter;
			}

			public override double RemainingCapacity()
			{
				if (!((Object)(object)_smelter.m_fuelItem == (Object)null) && _smelter.m_maxFuel > 0)
				{
					return Math.Max(0.0, Math.Floor((float)_smelter.m_maxFuel - _smelter.GetFuel()));
				}
				return double.NaN;
			}

			public override IReadOnlyList<string> AcceptedItems(OrePriority priority)
			{
				return new string[1] { _smelter.m_fuelItem.m_itemData.m_shared.m_name };
			}

			public override long Insert(IReadOnlyList<KeyValuePair<string, long>> units)
			{
				//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)
				//IL_006e: 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)
				double num = RemainingCapacity();
				long num2 = (double.IsNaN(num) ? 0 : Math.Min(Sum(units), (long)num));
				if (num2 > 0)
				{
					_smelter.SetFuel(_smelter.GetFuel() + (float)num2);
					_smelter.m_fuelAddedEffects.Create(base.Transform.position, base.Transform.rotation, base.Transform, 1f, -1, default(ZDOID));
				}
				return num2;
			}
		}

		private sealed class FireplaceFuel : MachineSlot
		{
			private readonly Fireplace _fireplace;

			private float Fuel => base.View.GetZDO().GetFloat(ZDOVars.s_fuel, 0f);

			public FireplaceFuel(Fireplace fireplace)
				: base(fireplace.m_nview, ((Component)fireplace).transform)
			{
				_fireplace = fireplace;
			}

			public override double RemainingCapacity()
			{
				if (_fireplace.m_canRefill && !_fireplace.m_infiniteFuel && !((Object)(object)_fireplace.m_fuelItem == (Object)null))
				{
					return Math.Max(0.0, Math.Ceiling(_fireplace.m_maxFuel) - Math.Ceiling(Fuel));
				}
				return double.NaN;
			}

			public override IReadOnlyList<string> AcceptedItems(OrePriority priority)
			{
				return new string[1] { _fireplace.m_fuelItem.m_itemData.m_shared.m_name };
			}

			public override long Insert(IReadOnlyList<KeyValuePair<string, long>> units)
			{
				//IL_006c: 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_0085: 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)
				double num = RemainingCapacity();
				long num2 = (double.IsNaN(num) ? 0 : Math.Min(Sum(units), (long)num));
				if (num2 > 0)
				{
					float num3 = Mathf.Clamp(Fuel + (float)num2, 0f, _fireplace.m_maxFuel);
					base.View.GetZDO().Set(ZDOVars.s_fuel, num3);
					_fireplace.m_fuelAddedEffects.Create(base.Transform.position, base.Transform.rotation, (Transform)null, 1f, -1, default(ZDOID));
					_fireplace.UpdateState();
				}
				return num2;
			}
		}

		public ZNetView View { get; }

		public Transform Transform { get; }

		protected MachineSlot(ZNetView view, Transform transform)
		{
			View = view;
			Transform = transform;
		}

		public abstract double RemainingCapacity();

		public abstract IReadOnlyList<string> AcceptedItems(OrePriority priority);

		public abstract long Insert(IReadOnlyList<KeyValuePair<string, long>> units);

		public static MachineSlot? FromSwitch(Switch sw)
		{
			Smelter componentInParent = ((Component)sw).GetComponentInParent<Smelter>();
			if ((Object)(object)componentInParent == (Object)null || (Object)(object)componentInParent.m_nview == (Object)null || !componentInParent.m_nview.IsValid())
			{
				return null;
			}
			if ((Object)(object)sw == (Object)(object)componentInParent.m_addOreSwitch)
			{
				return new SmelterOre(componentInParent);
			}
			if (!((Object)(object)sw == (Object)(object)componentInParent.m_addWoodSwitch))
			{
				return null;
			}
			return new SmelterFuel(componentInParent);
		}

		public static MachineSlot? FromFireplace(Fireplace fireplace)
		{
			if (!((Object)(object)fireplace.m_nview != (Object)null) || !fireplace.m_nview.IsValid())
			{
				return null;
			}
			return new FireplaceFuel(fireplace);
		}

		protected static long Sum(IReadOnlyList<KeyValuePair<string, long>> units)
		{
			return units.Sum<KeyValuePair<string, long>>((KeyValuePair<string, long> unit) => unit.Value);
		}
	}
	internal static class PrefabIndex
	{
		private static readonly Dictionary<string, GameObject> ByShared = new Dictionary<string, GameObject>();

		public static GameObject? Find(string sharedName)
		{
			if (ByShared.TryGetValue(sharedName, out GameObject value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return null;
			}
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val != (Object)null && val.m_itemData.m_shared.m_name == sharedName)
				{
					ByShared[sharedName] = item;
					return item;
				}
			}
			return null;
		}
	}
}
namespace ChestNetwork.Plugin.Interaction
{
	internal static class AltInteraction
	{
		private sealed class SwitchHandler
		{
			public Func<Player, Switch, bool> Act { get; }

			public Func<Player, Switch, string?> HoverAction { get; }

			public SwitchHandler(Func<Player, Switch, bool> act, Func<Player, Switch, string?> hoverAction)
			{
				Act = act;
				HoverAction = hoverAction;
			}
		}

		[HarmonyPatch(typeof(Switch), "Interact")]
		private static class SwitchInteract
		{
			private static bool Prefix(Switch __instance, Humanoid character, bool hold, bool alt, ref bool __result)
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Expected O, but got Unknown
				if (!IsAltUse(character, hold, alt))
				{
					return true;
				}
				foreach (SwitchHandler switchHandler in SwitchHandlers)
				{
					if (switchHandler.Act((Player)character, __instance))
					{
						__result = true;
						return false;
					}
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(Switch), "GetHoverText")]
		private static class SwitchHover
		{
			private static void Postfix(Switch __instance, ref string __result)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || !Available)
				{
					return;
				}
				foreach (SwitchHandler switchHandler in SwitchHandlers)
				{
					string text = switchHandler.HoverAction(localPlayer, __instance);
					if (text != null)
					{
						__result += HoverLine(text);
						break;
					}
				}
			}
		}

		private static readonly List<Func<Player, GameObject, bool>> Handlers = new List<Func<Player, GameObject, bool>>();

		private static readonly List<SwitchHandler> SwitchHandlers = new List<SwitchHandler>();

		public static bool Available
		{
			get
			{
				if (NetworkSession.FeaturesEnabled)
				{
					return ChestNetworkPlugin.Settings.UseAltInteract;
				}
				return false;
			}
		}

		public static bool IsAltUse(Humanoid user, bool hold, bool alt)
		{
			if (alt && !hold && (Object)(object)user == (Object)(object)Player.m_localPlayer)
			{
				return ChestNetworkPlugin.Settings.UseAltInteract;
			}
			return false;
		}

		public static void Register(Func<Player, GameObject, bool> handler)
		{
			Handlers.Add(handler);
		}

		public static void RegisterSwitch(Func<Player, Switch, bool> act, Func<Player, Switch, string?> hoverAction)
		{
			SwitchHandlers.Add(new SwitchHandler(act, hoverAction));
		}

		public static string HoverLine(string action)
		{
			string text = ((ZInput.IsNonClassicFunctionality() && ZInput.IsGamepadActive()) ? "$KEY_AltKeys + $KEY_Use" : "$KEY_AltPlace + $KEY_Use");
			return Localization.instance.Localize("\n[<color=yellow><b>" + text + "</b></color>] " + action);
		}

		public static void Tick()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !NetworkSession.FeaturesEnabled)
			{
				return;
			}
			KeyboardShortcut altShortcut = ChestNetworkPlugin.Settings.AltShortcut;
			if (!((KeyboardShortcut)(ref altShortcut)).IsDown() || !((Character)localPlayer).TakeInput())
			{
				return;
			}
			GameObject hovering = localPlayer.m_hovering;
			if ((Object)(object)hovering == (Object)null)
			{
				return;
			}
			using List<Func<Player, GameObject, bool>>.Enumerator enumerator = Handlers.GetEnumerator();
			while (enumerator.MoveNext() && !enumerator.Current(localPlayer, hovering))
			{
			}
		}
	}
}
namespace ChestNetwork.Plugin.Armor
{
	internal static class ArmorPieces
	{
		public static ArmorSlot? SlotOf(ItemType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			if ((int)type <= 7)
			{
				if ((int)type == 6)
				{
					return (ArmorSlot)0;
				}
				if ((int)type == 7)
				{
					return (ArmorSlot)1;
				}
			}
			else
			{
				if ((int)type == 11)
				{
					return (ArmorSlot)2;
				}
				if ((int)type == 17)
				{
					return (ArmorSlot)3;
				}
				if ((int)type == 18)
				{
					return (ArmorSlot)4;
				}
			}
			return null;
		}

		public static ArmorPiece? Of(ItemData item, string? containerId, int slot)
		{
			//IL_001a: 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_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Expected O, but got Unknown
			if (!((Object)(object)item?.m_dropPrefab == (Object)null))
			{
				ArmorSlot? val = SlotOf(item.m_shared.m_itemType);
				if (val.HasValue)
				{
					ArmorSlot valueOrDefault = val.GetValueOrDefault();
					return new ArmorPiece(containerId, slot, ((Object)item.m_dropPrefab).name, item.m_shared.m_name, item.m_quality, valueOrDefault, item.m_shared.m_setName, (double)item.m_shared.m_armor, item.m_equipped);
				}
			}
			return null;
		}

		public static List<ArmorPiece> In(Inventory inventory, string? containerId)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			List<ArmorPiece> list = new List<ArmorPiece>();
			int width = inventory.GetWidth();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				ArmorPiece val = Of(allItem, containerId, GameSnapshots.SlotOf(allItem.m_gridPos, width));
				if (val != (ArmorPiece)null)
				{
					list.Add(val);
				}
			}
			return list;
		}

		public static ItemData? Find(Inventory inventory, ArmorPiece piece)
		{
			//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_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			Vector2i val = GameSnapshots.GridPosOf(piece.Slot, inventory.GetWidth());
			ItemData itemAt = inventory.GetItemAt(val.x, val.y);
			ArmorPiece val2 = ((itemAt != null) ? Of(itemAt, piece.ContainerId, piece.Slot) : null);
			if (!(val2 != (ArmorPiece)null) || !(val2.PrefabName == piece.PrefabName) || val2.Quality != piece.Quality || val2.IsEquipped)
			{
				return null;
			}
			return itemAt;
		}

		public static Dictionary<ArmorSlot, ItemData> Worn(Player player)
		{
			Dictionary<ArmorSlot, ItemData> dictionary = new Dictionary<ArmorSlot, ItemData>();
			Add(dictionary, (ArmorSlot)0, ((Humanoid)player).m_helmetItem);
			Add(dictionary, (ArmorSlot)1, ((Humanoid)player).m_chestItem);
			Add(dictionary, (ArmorSlot)2, ((Humanoid)player).m_legItem);
			Add(dictionary, (ArmorSlot)3, ((Humanoid)player).m_shoulderItem);
			Add(dictionary, (ArmorSlot)4, ((Humanoid)player).m_utilityItem);
			return dictionary;
		}

		private static void Add(Dictionary<ArmorSlot, ItemData> worn, ArmorSlot slot, ItemData? item)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			if (item != null)
			{
				worn.Add(slot, item);
			}
		}
	}
	internal static class ArmorStandCoordinator
	{
		private sealed class SwapState
		{
			public ArmorSwapPlan Plan { get; }

			public Dictionary<ArmorSlot, ArmorPiece> Stand { get; }

			public Dictionary<ArmorSlot, ArmorPiece> Worn { get; }

			public int Weapons { get; }

			public int Free { get; }

			public SwapState(ArmorSwapPlan plan, Dictionary<ArmorSlot, ArmorPiece> stand, Dictionary<ArmorSlot, ArmorPiece> worn, int weapons, int free)
			{
				Plan = plan;
				Stand = stand;
				Worn = worn;
				Weapons = weapons;
				Free = free;
			}
		}

		private const string InventoryFull = "$inventory_full";

		private static bool s_catalogReported;

		public static bool TryStart(Player player, ArmorStand stand)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkSession.FeaturesEnabled || !ChestNetworkPlugin.Settings.ArmorStandEnabled || (Object)(object)stand == (Object)null || (Object)(object)stand.m_nview == (Object)null || !stand.m_nview.IsValid())
			{
				return false;
			}
			ArmorStandView armorStandView = new ArmorStandView(stand);
			if (!PrivateArea.CheckAccess(armorStandView.Position, 0f, true, false))
			{
				return true;
			}
			ReportUncataloguedArmorOnce();
			if (HoldsArmor(armorStandView))
			{
				return StartSwap(player, armorStandView);
			}
			return StartFill(player, armorStandView);
		}

		public static string? HoverAction(Player player, Switch sw)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			ArmorStand componentInParent = ((Component)sw).GetComponentInParent<ArmorStand>();
			if ((Object)(object)componentInParent == (Object)null || !ChestNetworkPlugin.Settings.ArmorStandEnabled || (Object)(object)componentInParent.m_nview == (Object)null || !componentInParent.m_nview.IsValid() || !componentInParent.m_slots.Any((ArmorStandSlot slot) => (Object)(object)slot.m_switch == (Object)(object)sw) || !PrivateArea.CheckAccess(((Component)componentInParent).transform.position, 0f, false, false))
			{
				return null;
			}
			if (!HoldsArmor(new ArmorStandView(componentInParent)))
			{
				return "$chestnetwork_armor_fill_hover";
			}
			return "$chestnetwork_armor_swap_hover";
		}

		private static bool HoldsArmor(ArmorStandView view)
		{
			return view.ManagedSlots().Values.Any(view.Has);
		}

		private static bool StartFill(Player player, ArmorStandView view)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			NetworkContext context = NetworkContext.At(player, ((Component)player).transform.position, committable: false);
			ArmorFillPlan val = PlanFill(player, view, context, skipUnavailable: true);
			if (val.Choices.Count == 0)
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_armor_nothing", 0, (Sprite)null, false);
				return true;
			}
			List<ZNetView> list = new List<ZNetView> { view.View };
			list.AddRange(ContainerRegistry.ViewsOf(from c in val.Choices
				where c.Value.ContainerId != null
				select c.Value.ContainerId));
			if (!NetworkTransaction.Begin(player, list, delegate(Player committing)
			{
				CommitFill(committing, view);
			}))
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_armor_preparing", 0, (Sprite)null, false);
			}
			return true;
		}

		private static ArmorFillPlan PlanFill(Player player, ArmorStandView view, NetworkContext context, bool skipUnavailable)
		{
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<ArmorSlot, int> managed = view.ManagedSlots();
			List<ArmorSlot> list = (from slot in managed
				where !view.Has(slot.Value)
				select slot.Key).ToList();
			IEnumerable<ContainerSnapshot> source = context.Selected;
			if (skipUnavailable)
			{
				source = source.Where((ContainerSnapshot c) => !OwnershipCoordinator.IsUnavailable(context.EntriesById[c.Id].Container.m_nview));
			}
			List<string> list2 = source.Select((ContainerSnapshot c) => c.Id).ToList();
			List<ArmorPiece> list3 = new List<ArmorPiece>(ArmorPieces.In(((Humanoid)player).GetInventory(), null));
			foreach (string item in list2)
			{
				list3.AddRange(ArmorPieces.In(context.EntriesById[item].Container.GetInventory(), item));
			}
			list3 = list3.Where(delegate(ArmorPiece piece)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				if (managed.TryGetValue(piece.SlotType, out var value))
				{
					ItemData val = Item(player, context, piece);
					if (val != null)
					{
						return view.CanPlace(value, val);
					}
				}
				return false;
			}).ToList();
			return new ArmorFillPlanner(ChestNetworkPlugin.Settings.ArmorSets, ChestNetworkPlugin.Settings.ArmorFillSettings).Plan((IEnumerable<ArmorPiece>)list3, (IReadOnlyList<string>)list2, (IEnumerable<ArmorSlot>)list, ((Humanoid)player).GetInventory().GetWidth());
		}

		private static void CommitFill(Player player, ArmorStandView view)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)view.View == (Object)null || !view.View.IsValid())
			{
				return;
			}
			if (!OwnershipCoordinator.IsCommittable(view.View))
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_armor_busy", 0, (Sprite)null, false);
				return;
			}
			NetworkContext networkContext = NetworkContext.At(player, ((Component)player).transform.position, committable: true);
			ArmorFillPlan val = PlanFill(player, view, networkContext, skipUnavailable: false);
			if (val.Choices.Count == 0)
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_armor_nothing", 0, (Sprite)null, false);
				return;
			}
			Dictionary<ArmorSlot, int> dictionary = view.ManagedSlots();
			List<ArmorPiece> list = new List<ArmorPiece>(ArmorPieces.In(((Humanoid)player).GetInventory(), null));
			List<string> list2 = (from c in val.Choices
				where c.Value.ContainerId != null
				select c.Value.ContainerId).Distinct().ToList();
			foreach (string item in list2)
			{
				list.AddRange(ArmorPieces.In(networkContext.EntriesById[item].Container.GetInventory(), item));
			}
			ValidationResult val2 = new PlanValidator(NetworkSession.Config).ValidateArmorFill(val, (IEnumerable<ArmorPiece>)list, (IEnumerable<ContainerSnapshot>)networkContext.Reread(list2), from slot in dictionary
				where !view.Has(slot.Value)
				select slot.Key, view.Id);
			if (!val2.IsValid)
			{
				ChestNetworkPlugin.Log.LogInfo((object)("Armor stand fill invalidated before commit: " + string.Join("; ", val2.Issues)));
				return;
			}
			HashSet<string> hashSet = new HashSet<string> { view.Id };
			foreach (KeyValuePair<ArmorSlot, ArmorPiece> choice in val.Choices)
			{
				Inventory val3 = ((choice.Value.ContainerId == null) ? ((Humanoid)player).GetInventory() : networkContext.EntriesById[choice.Value.ContainerId].Container.GetInventory());
				ItemData val4 = ArmorPieces.Find(val3, choice.Value);
				if (val4 != null)
				{
					view.Write(dictionary[choice.Key], val4);
					val3.RemoveOneItem(val4);
					if (choice.Value.ContainerId != null)
					{
						hashSet.Add(choice.Value.ContainerId);
					}
				}
			}
			view.PlayEffect();
			OwnershipCoordinator.Reserve(hashSet);
			NetworkContext.Invalidate();
		}

		private static ItemData? Item(Player player, NetworkContext context, ArmorPiece piece)
		{
			return ArmorPieces.Find((piece.ContainerId == null) ? ((Humanoid)player).GetInventory() : context.EntriesById[piece.ContainerId].Container.GetInventory(), piece);
		}

		private static bool StartSwap(Player player, ArmorStandView view)
		{
			if (!PlanSwap(player, view).Plan.IsFeasible)
			{
				((Character)player).Message((MessageType)2, "$inventory_full", 0, (Sprite)null, false);
				return true;
			}
			if (!NetworkTransaction.Begin(player, (IReadOnlyList<ZNetView>)(object)new ZNetView[1] { view.View }, delegate(Player committing)
			{
				CommitSwap(committing, view);
			}))
			{
				((Character)player).Message((MessageType)2, "$chestnetwork_armor_preparing", 0, (Sprite)null, false);
			}
			return true;
		}

		private static SwapState PlanSwap(Player player, ArmorStandView view)
		{
			//IL_0050: 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_00a7: Expected I4, but got Unknown
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<ArmorSlot, int> dictionary = view.ManagedSlots();
			Dictionary<ArmorSlot, ArmorPiece> dictionary2 = new Dictionary<ArmorSlot, ArmorPiece>();
			foreach (KeyValuePair<ArmorSlot, int> item i

plugins/ChestNetwork.Core.dll

Decompiled 13 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using ChestNetwork.Core.Armor;
using ChestNetwork.Core.Deposit;
using ChestNetwork.Core.Internal;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ChestNetwork.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+6e7ba34d720892abcc32bfe576c2fa62aea456e7")]
[assembly: AssemblyProduct("ChestNetwork.Core")]
[assembly: AssemblyTitle("ChestNetwork.Core")]
[assembly: InternalsVisibleTo("ChestNetwork.Core.Tests")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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;
		}
	}
	internal static class IsExternalInit
	{
	}
}
namespace ChestNetwork.Core
{
	public enum ContainerKind
	{
		Chest,
		Cart,
		Ship,
		Other
	}
	public sealed record ContainerSnapshot
	{
		public string Id { get; init; }

		public Vector3 Position { get; init; }

		public ContainerKind Kind { get; init; }

		public bool IsInUse { get; init; }

		public bool IsAccessible { get; init; }

		public int SlotCount { get; init; }

		public IReadOnlyList<ItemStack> Items
		{
			get
			{
				return _items;
			}
			init
			{
				_items = CopyItems(value);
			}
		}

		private readonly IReadOnlyList<ItemStack> _items = Array.Empty<ItemStack>();

		public ContainerSnapshot(string id, Vector3 position, ContainerKind kind, IEnumerable<ItemStack?>? items = null, bool isInUse = false, bool isAccessible = true, int slotCount = 0)
		{
			Id = id;
			Position = position;
			Kind = kind;
			Items = CopyItems(items);
			IsInUse = isInUse;
			IsAccessible = isAccessible;
			SlotCount = slotCount;
		}

		public bool Equals(ContainerSnapshot? other)
		{
			if ((object)other == null)
			{
				return false;
			}
			if (string.Equals(Id, other.Id, StringComparison.Ordinal) && Position.Equals(other.Position) && Kind == other.Kind && IsInUse == other.IsInUse && IsAccessible == other.IsAccessible && SlotCount == other.SlotCount)
			{
				return HaveSameItems(_items, other._items);
			}
			return false;
		}

		public override int GetHashCode()
		{
			int num = 0;
			foreach (ItemStack item in _items)
			{
				num += item.GetHashCode();
			}
			return HashCode.Combine(Id, Position, Kind, IsInUse, IsAccessible, SlotCount, _items.Count, num);
		}

		private static IReadOnlyList<ItemStack> CopyItems(IEnumerable<ItemStack?>? items)
		{
			if (items == null)
			{
				return Array.Empty<ItemStack>();
			}
			return (from item in items
				where (object)item != null
				select (item)).ToArray();
		}

		private static bool HaveSameItems(IReadOnlyList<ItemStack> left, IReadOnlyList<ItemStack> right)
		{
			if (left.Count != right.Count)
			{
				return false;
			}
			Dictionary<ItemStack, int> dictionary = new Dictionary<ItemStack, int>();
			foreach (ItemStack item in left)
			{
				dictionary.TryGetValue(item, out var value);
				dictionary[item] = value + 1;
			}
			foreach (ItemStack item2 in right)
			{
				if (!dictionary.TryGetValue(item2, out var value2) || value2 == 0)
				{
					return false;
				}
				dictionary[item2] = value2 - 1;
			}
			return true;
		}

		[CompilerGenerated]
		private bool PrintMembers(StringBuilder builder)
		{
			RuntimeHelpers.EnsureSufficientExecutionStack();
			builder.Append("Id = ");
			builder.Append((object?)Id);
			builder.Append(", Position = ");
			builder.Append(Position.ToString());
			builder.Append(", Kind = ");
			builder.Append(Kind.ToString());
			builder.Append(", IsInUse = ");
			builder.Append(IsInUse.ToString());
			builder.Append(", IsAccessible = ");
			builder.Append(IsAccessible.ToString());
			builder.Append(", SlotCount = ");
			builder.Append(SlotCount.ToString());
			builder.Append(", Items = ");
			builder.Append(Items);
			return true;
		}

		[CompilerGenerated]
		private ContainerSnapshot(ContainerSnapshot original)
		{
			_items = original._items;
			Id = original.Id;
			Position = original.Position;
			Kind = original.Kind;
			IsInUse = original.IsInUse;
			IsAccessible = original.IsAccessible;
			SlotCount = original.SlotCount;
		}
	}
	public readonly record struct ItemKey(string ItemId, int Quality);
	public sealed record ItemStack(string ItemId, int Quality, int Amount, bool IsEquipped = false, int? Slot = null, string? StackKey = null, int MaxStack = 0);
	public enum ConnectionRule
	{
		OverlappingZones,
		StationInsideZone
	}
	public enum QualityMatching
	{
		Ignore,
		Exact,
		AtLeast
	}
	public sealed record ModConfig
	{
		public static ModConfig Default { get; } = new ModConfig();

		public ConnectionRule ConnectionRule { get; init; }

		public bool InclusiveBoundaries { get; init; } = true;

		public IReadOnlyCollection<string>? AllowedStationTypes { get; init; }

		public IReadOnlyCollection<ContainerKind> ExcludedContainerKinds { get; init; } = (IReadOnlyCollection<ContainerKind>)(object)new ContainerKind[2]
		{
			ContainerKind.Cart,
			ContainerKind.Ship
		};

		public QualityMatching QualityMatching { get; init; }

		public bool AllowEquippedItems { get; init; }

		internal static ModConfig Validate(ModConfig? config)
		{
			ModConfig modConfig = config ?? Default;
			if (!Enum.IsDefined(typeof(ConnectionRule), modConfig.ConnectionRule))
			{
				throw new ArgumentOutOfRangeException("config", modConfig.ConnectionRule, "Unknown connection rule.");
			}
			if (!Enum.IsDefined(typeof(QualityMatching), modConfig.QualityMatching))
			{
				throw new ArgumentOutOfRangeException("config", modConfig.QualityMatching, "Unknown quality matching.");
			}
			return modConfig;
		}

		[CompilerGenerated]
		private ModConfig(ModConfig original)
		{
			ConnectionRule = original.ConnectionRule;
			InclusiveBoundaries = original.InclusiveBoundaries;
			AllowedStationTypes = original.AllowedStationTypes;
			ExcludedContainerKinds = original.ExcludedContainerKinds;
			QualityMatching = original.QualityMatching;
			AllowEquippedItems = original.AllowEquippedItems;
		}

		public ModConfig()
		{
		}
	}
	public sealed record Requirement(string ItemId, int Amount, int? Quality = null);
	public sealed record StationSnapshot(string Id, Vector3 Position, float Range, string StationType);
	public sealed record Withdrawal(string? ContainerId, string ItemId, int Quality, long Amount)
	{
		public bool IsFromInventory => ContainerId == null;
	}
	public sealed class ConsumptionPlan
	{
		public static ConsumptionPlan Empty { get; } = new ConsumptionPlan(Array.Empty<Withdrawal>());

		public IReadOnlyList<Withdrawal> Withdrawals { get; }

		internal ConsumptionPlan(IReadOnlyList<Withdrawal> withdrawals)
		{
			Withdrawals = withdrawals;
		}

		public long TotalFor(string itemId)
		{
			long num = 0L;
			foreach (Withdrawal withdrawal in Withdrawals)
			{
				if (string.Equals(withdrawal.ItemId, itemId, StringComparison.Ordinal))
				{
					num += withdrawal.Amount;
				}
			}
			return num;
		}
	}
	public sealed record Shortage(string ItemId, int? Quality, long Required, long Available)
	{
		public long Missing => Required - Available;
	}
	public enum PlanStatus
	{
		Success,
		InvalidRequirements,
		InsufficientResources
	}
	public sealed class PlanResult
	{
		public PlanStatus Status { get; }

		public bool IsSuccess => Status == PlanStatus.Success;

		public ConsumptionPlan? Plan { get; }

		public IReadOnlyList<Shortage> Shortages { get; }

		public IReadOnlyList<Requirement> InvalidRequirements { get; }

		private PlanResult(PlanStatus status, ConsumptionPlan? plan, IReadOnlyList<Shortage> shortages, IReadOnlyList<Requirement> invalidRequirements)
		{
			Status = status;
			Plan = plan;
			Shortages = shortages;
			InvalidRequirements = invalidRequirements;
		}

		internal static PlanResult Success(ConsumptionPlan plan)
		{
			return new PlanResult(PlanStatus.Success, plan, Array.Empty<Shortage>(), Array.Empty<Requirement>());
		}

		internal static PlanResult Invalid(IReadOnlyList<Requirement> invalid)
		{
			return new PlanResult(PlanStatus.InvalidRequirements, null, Array.Empty<Shortage>(), invalid);
		}

		internal static PlanResult Insufficient(IReadOnlyList<Shortage> shortages)
		{
			return new PlanResult(PlanStatus.InsufficientResources, null, shortages, Array.Empty<Requirement>());
		}
	}
	public sealed class ConsumptionPlanner
	{
		private readonly record struct DemandKey(string ItemId, int? Quality);

		private readonly record struct Demand(string ItemId, int? Quality, long Amount);

		private readonly record struct WithdrawalKey(int SourceIndex, string ItemId, int Quality);

		private sealed class Bucket
		{
			public int Quality { get; }

			public long Remaining { get; set; }

			public Bucket(int quality, long remaining)
			{
				Quality = quality;
				Remaining = remaining;
			}
		}

		private sealed class Source
		{
			private readonly Dictionary<string, List<Bucket>> _bucketsByItem = new Dictionary<string, List<Bucket>>(StringComparer.Ordinal);

			public string? ContainerId { get; }

			public Source(string? containerId, Dictionary<ItemKey, long> stock)
			{
				ContainerId = containerId;
				foreach (KeyValuePair<ItemKey, long> item in stock)
				{
					if (!_bucketsByItem.TryGetValue(item.Key.ItemId, out List<Bucket> value))
					{
						value = new List<Bucket>();
						_bucketsByItem.Add(item.Key.ItemId, value);
					}
					value.Add(new Bucket(item.Key.Quality, item.Value));
				}
				foreach (List<Bucket> value2 in _bucketsByItem.Values)
				{
					value2.Sort((Bucket a, Bucket b) => a.Quality.CompareTo(b.Quality));
				}
			}

			public long Take(Demand demand, long remaining, QualityMatching matching, int sourceIndex, Dictionary<WithdrawalKey, long> taken)
			{
				if (!_bucketsByItem.TryGetValue(demand.ItemId, out List<Bucket> buckets))
				{
					return remaining;
				}
				long num = GreedyAllocation.Allocate(remaining, buckets.Count, (int i) => (!Stock.Matches(matching, buckets[i].Quality, demand.Quality)) ? 0 : buckets[i].Remaining, delegate(int i, long amount)
				{
					Bucket bucket = buckets[i];
					bucket.Remaining -= amount;
					WithdrawalKey key = new WithdrawalKey(sourceIndex, demand.ItemId, bucket.Quality);
					taken.TryGetValue(key, out var value);
					taken[key] = value + amount;
				});
				return remaining - num;
			}
		}

		private readonly ModConfig _config;

		public ConsumptionPlanner(ModConfig? config = null)
		{
			_config = ModConfig.Validate(config);
		}

		public PlanResult Plan(IEnumerable<Requirement?>? requirements, IEnumerable<ItemStack?>? inventory, IEnumerable<ContainerSnapshot?>? containers)
		{
			List<Requirement> list = new List<Requirement>();
			List<Requirement> list2 = new List<Requirement>();
			if (requirements != null)
			{
				foreach (Requirement requirement in requirements)
				{
					if ((object)requirement != null)
					{
						list.Add(requirement);
						if (string.IsNullOrEmpty(requirement.ItemId) || requirement.Amount < 0)
						{
							list2.Add(requirement);
						}
					}
				}
			}
			if (list2.Count > 0)
			{
				return PlanResult.Invalid(list2);
			}
			List<Demand> list3 = GroupDemands(list);
			if (list3.Count == 0)
			{
				return PlanResult.Success(ConsumptionPlan.Empty);
			}
			List<ContainerSnapshot> list4 = Deduplication.ById(containers, (ContainerSnapshot c) => c.Id);
			Source[] array = new Source[list4.Count + 1];
			array[0] = new Source(null, Stock.TallyInventory(inventory, _config));
			Dictionary<WithdrawalKey, long> taken = new Dictionary<WithdrawalKey, long>();
			List<Shortage> list5 = new List<Shortage>();
			foreach (Demand item in list3)
			{
				long num = item.Amount;
				for (int num2 = 0; num2 < array.Length; num2++)
				{
					if (num <= 0)
					{
						break;
					}
					ref Source reference = ref array[num2];
					num = (reference ?? (reference = new Source(list4[num2 - 1].Id, Stock.TallyContainer(list4[num2 - 1])))).Take(item, num, _config.QualityMatching, num2, taken);
				}
				if (num > 0)
				{
					list5.Add(new Shortage(item.ItemId, item.Quality, item.Amount, item.Amount - num));
				}
			}
			if (list5.Count > 0)
			{
				return PlanResult.Insufficient(list5);
			}
			return PlanResult.Success(new ConsumptionPlan(ToWithdrawals(taken, array)));
		}

		private List<Demand> GroupDemands(List<Requirement> requested)
		{
			bool flag = _config.QualityMatching == QualityMatching.Ignore;
			Dictionary<DemandKey, long> dictionary = new Dictionary<DemandKey, long>();
			foreach (Requirement item in requested)
			{
				DemandKey key = new DemandKey(item.ItemId, flag ? ((int?)null) : item.Quality);
				dictionary.TryGetValue(key, out var value);
				dictionary[key] = value + item.Amount;
			}
			List<Demand> list = new List<Demand>(dictionary.Count);
			foreach (KeyValuePair<DemandKey, long> item2 in dictionary)
			{
				list.Add(new Demand(item2.Key.ItemId, item2.Key.Quality, item2.Value));
			}
			list.Sort(CompareDemands);
			return list;
		}

		private static int CompareDemands(Demand a, Demand b)
		{
			int num = string.CompareOrdinal(a.ItemId, b.ItemId);
			if (num != 0)
			{
				return num;
			}
			int num2 = b.Quality.HasValue.CompareTo(a.Quality.HasValue);
			if (num2 != 0)
			{
				return num2;
			}
			return b.Quality.GetValueOrDefault().CompareTo(a.Quality.GetValueOrDefault());
		}

		private static List<Withdrawal> ToWithdrawals(Dictionary<WithdrawalKey, long> taken, Source?[] sources)
		{
			List<WithdrawalKey> list = new List<WithdrawalKey>(taken.Keys);
			list.Sort(delegate(WithdrawalKey a, WithdrawalKey b)
			{
				int num = a.SourceIndex.CompareTo(b.SourceIndex);
				if (num != 0)
				{
					return num;
				}
				int num2 = string.CompareOrdinal(a.ItemId, b.ItemId);
				return (num2 == 0) ? a.Quality.CompareTo(b.Quality) : num2;
			});
			List<Withdrawal> list2 = new List<Withdrawal>(list.Count);
			foreach (WithdrawalKey item in list)
			{
				list2.Add(new Withdrawal(sources[item.SourceIndex].ContainerId, item.ItemId, item.Quality, taken[item]));
			}
			return list2;
		}
	}
	public sealed class ContainerSelector
	{
		private readonly ModConfig _config;

		private readonly HashSet<ContainerKind> _excludedKinds;

		public ContainerSelector(ModConfig? config = null)
		{
			_config = ModConfig.Validate(config);
			_excludedKinds = new HashSet<ContainerKind>((IEnumerable<ContainerKind>)(((object)_config.ExcludedContainerKinds) ?? ((object)Array.Empty<ContainerKind>())));
		}

		public IReadOnlyList<ContainerSnapshot> Select(StationNetwork network, IEnumerable<ContainerSnapshot?>? containers)
		{
			if (network == null)
			{
				throw new ArgumentNullException("network");
			}
			if (network.IsEmpty)
			{
				return Array.Empty<ContainerSnapshot>();
			}
			List<ContainerSnapshot> list = Deduplication.ById(containers, (ContainerSnapshot container) => container.Id);
			List<KeyValuePair<double, ContainerSnapshot>> list2 = new List<KeyValuePair<double, ContainerSnapshot>>(list.Count);
			List<int> nearby = new List<int>();
			foreach (ContainerSnapshot item in list)
			{
				if (IsEligible(item, network, nearby))
				{
					list2.Add(new KeyValuePair<double, ContainerSnapshot>(Geometry.DistanceSquared(network.Origin, item.Position), item));
				}
			}
			list2.Sort(CompareByDistanceThenId);
			ContainerSnapshot[] array = new ContainerSnapshot[list2.Count];
			for (int num = 0; num < array.Length; num++)
			{
				array[num] = list2[num].Value;
			}
			return array;
		}

		private static int CompareByDistanceThenId(KeyValuePair<double, ContainerSnapshot> a, KeyValuePair<double, ContainerSnapshot> b)
		{
			int num = a.Key.CompareTo(b.Key);
			if (num == 0)
			{
				return string.CompareOrdinal(a.Value.Id, b.Value.Id);
			}
			return num;
		}

		private bool IsEligible(ContainerSnapshot container, StationNetwork network, List<int> nearby)
		{
			if (!container.IsInUse && container.IsAccessible && !_excludedKinds.Contains(container.Kind))
			{
				return IsInAnyZone(container, network, nearby);
			}
			return false;
		}

		private bool IsInAnyZone(ContainerSnapshot container, StationNetwork network, List<int> nearby)
		{
			double num = container.Position.X;
			double ay = container.Position.Y;
			double num2 = container.Position.Z;
			bool inclusiveBoundaries = _config.InclusiveBoundaries;
			double[] x = network.X;
			double[] y = network.Y;
			double[] z = network.Z;
			double[] ranges = network.Ranges;
			network.Grid.Query(num, num2, network.MaxRange, nearby);
			foreach (int item in nearby)
			{
				if (Geometry.IsWithin(Geometry.DistanceSquared(num, ay, num2, x[item], y[item], z[item]), ranges[item], inclusiveBoundaries))
				{
					return true;
				}
			}
			return false;
		}
	}
	public enum ValidationIssueKind
	{
		ContainerMissing,
		ContainerInUse,
		ContainerInaccessible,
		ContainerExcluded,
		InsufficientQuantity,
		SlotUnavailable,
		NotATarget,
		InventoryFull
	}
	public sealed record ValidationIssue(ValidationIssueKind Kind, string? ContainerId, string? ItemId = null, int? Quality = null, long Required = 0L, long Available = 0L, int? Slot = null);
	public sealed class ValidationResult
	{
		public bool IsValid => Issues.Count == 0;

		public IReadOnlyList<ValidationIssue> Issues { get; }

		internal ValidationResult(IReadOnlyList<ValidationIssue> issues)
		{
			Issues = issues;
		}
	}
	public sealed class PlanValidator
	{
		private readonly ModConfig _config;

		private readonly HashSet<ContainerKind> _excludedKinds;

		public PlanValidator(ModConfig? config = null)
		{
			_config = ModConfig.Validate(config);
			_excludedKinds = new HashSet<ContainerKind>((IEnumerable<ContainerKind>)(((object)_config.ExcludedContainerKinds) ?? ((object)Array.Empty<ContainerKind>())));
		}

		public ValidationResult Validate(ConsumptionPlan plan, IEnumerable<ItemStack?>? inventory, IEnumerable<ContainerSnapshot?>? containers)
		{
			if (plan == null)
			{
				throw new ArgumentNullException("plan");
			}
			List<ValidationIssue> list = new List<ValidationIssue>();
			Dictionary<string, ContainerSnapshot> containersById = Index(containers);
			Dictionary<ItemKey, long> dictionary = Stock.TallyInventory(inventory, _config);
			Dictionary<string, Dictionary<ItemKey, long>> dictionary2 = new Dictionary<string, Dictionary<ItemKey, long>>(StringComparer.Ordinal);
			foreach (Withdrawal withdrawal in plan.Withdrawals)
			{
				Dictionary<ItemKey, long> value;
				if (withdrawal.ContainerId == null)
				{
					value = dictionary;
				}
				else if (!dictionary2.TryGetValue(withdrawal.ContainerId, out value))
				{
					ContainerSnapshot container;
					ValidationIssueKind? validationIssueKind = CheckContainer(withdrawal.ContainerId, containersById, out container);
					if (!validationIssueKind.HasValue)
					{
						value = Stock.TallyContainer(container);
					}
					else
					{
						list.Add(new ValidationIssue(validationIssueKind.Value, withdrawal.ContainerId, null, null, 0L, 0L));
					}
					dictionary2.Add(withdrawal.ContainerId, value);
				}
				if (value != null)
				{
					value.TryGetValue(new ItemKey(withdrawal.ItemId, withdrawal.Quality), out var value2);
					if (value2 < withdrawal.Amount)
					{
						list.Add(new ValidationIssue(ValidationIssueKind.InsufficientQuantity, withdrawal.ContainerId, withdrawal.ItemId, withdrawal.Quality, withdrawal.Amount, value2));
					}
				}
			}
			return new ValidationResult(list);
		}

		public ValidationResult ValidateDeposit(DepositPlan plan, IEnumerable<ItemStack?>? inventory, IEnumerable<ContainerSnapshot?>? containers)
		{
			if (plan == null)
			{
				throw new ArgumentNullException("plan");
			}
			List<ValidationIssue> list = new List<ValidationIssue>();
			Dictionary<string, ContainerSnapshot> containersById = Index(containers);
			Dictionary<int, ItemStack> dictionary = BySlot(inventory);
			foreach (IGrouping<int, StackDeposit> item in from d in plan.Deposits
				group d by d.SourceSlot)
			{
				StackDeposit stackDeposit = item.First();
				long num = item.Sum((StackDeposit d) => d.Amount);
				ItemStack value;
				long num2 = ((dictionary.TryGetValue(item.Key, out value) && value.ItemId == stackDeposit.ItemId && value.StackKey == stackDeposit.StackKey) ? value.Amount : 0);
				if (num2 < num)
				{
					list.Add(new ValidationIssue(ValidationIssueKind.InsufficientQuantity, null, stackDeposit.ItemId, stackDeposit.Quality, num, num2, item.Key));
				}
			}
			foreach (IGrouping<string, StackDeposit> item2 in from d in plan.Deposits
				group d by d.ContainerId)
			{
				ContainerSnapshot container;
				ValidationIssueKind? validationIssueKind = CheckContainer(item2.Key, containersById, out container);
				if (validationIssueKind.HasValue)
				{
					list.Add(new ValidationIssue(validationIssueKind.Value, item2.Key, null, null, 0L, 0L));
					continue;
				}
				foreach (string itemId in item2.Select((StackDeposit d) => d.ItemId).Distinct())
				{
					if (!container.Items.Any((ItemStack stack) => stack.ItemId == itemId && stack.Amount > 0))
					{
						list.Add(new ValidationIssue(ValidationIssueKind.NotATarget, item2.Key, itemId, null, 0L, 0L));
					}
				}
				foreach (IGrouping<int, StackDeposit> intoSlot in from d in item2
					group d by d.TargetSlot)
				{
					StackDeposit stackDeposit2 = intoSlot.First();
					long num3 = intoSlot.Sum((StackDeposit d) => d.Amount);
					ItemStack itemStack = container.Items.FirstOrDefault((ItemStack stack) => stack.Slot == intoSlot.Key && stack.Amount > 0);
					ItemStack value2;
					int num4 = ((dictionary.TryGetValue(stackDeposit2.SourceSlot, out value2) && value2.MaxStack > 0) ? value2.MaxStack : (itemStack?.MaxStack ?? 0));
					if (num4 > 0)
					{
						long num5 = (((object)itemStack == null) ? num4 : ((itemStack.ItemId == stackDeposit2.ItemId && itemStack.StackKey != null && itemStack.StackKey == stackDeposit2.StackKey) ? (num4 - itemStack.Amount) : 0));
						if (intoSlot.Key < 0 || intoSlot.Key >= container.SlotCount || num5 < num3)
						{
							list.Add(new ValidationIssue(ValidationIssueKind.SlotUnavailable, item2.Key, stackDeposit2.ItemId, stackDeposit2.Quality, num3, Math.Max(0L, num5), intoSlot.Key));
						}
					}
				}
			}
			return new ValidationResult(list);
		}

		public ValidationResult ValidateArmorFill(ArmorFillPlan plan, IEnumerable<ArmorPiece?>? currentPieces, IEnumerable<ContainerSnapshot?>? containers, IEnumerable<ArmorSlot>? freeStandSlots, string standId)
		{
			if (plan == null)
			{
				throw new ArgumentNullException("plan");
			}
			List<ValidationIssue> list = new List<ValidationIssue>();
			Dictionary<string, ContainerSnapshot> containersById = Index(containers);
			List<ArmorPiece> source = (from p in currentPieces ?? Array.Empty<ArmorPiece>()
				where (object)p != null
				select (p)).ToList();
			HashSet<ArmorSlot> hashSet = new HashSet<ArmorSlot>(freeStandSlots ?? Array.Empty<ArmorSlot>());
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
			foreach (KeyValuePair<ArmorSlot, ArmorPiece> choice in plan.Choices)
			{
				ArmorPiece chosen = choice.Value;
				string containerId = chosen.ContainerId;
				if (containerId != null && hashSet3.Add(containerId))
				{
					ContainerSnapshot container;
					ValidationIssueKind? validationIssueKind = CheckContainer(containerId, containersById, out container);
					if (validationIssueKind.HasValue)
					{
						list.Add(new ValidationIssue(validationIssueKind.Value, containerId, null, null, 0L, 0L));
						hashSet2.Add(containerId);
					}
				}
				if ((chosen.ContainerId == null || !hashSet2.Contains(chosen.ContainerId)) && !source.Any((ArmorPiece p) => p.ContainerId == chosen.ContainerId && p.Slot == chosen.Slot && p.PrefabName == chosen.PrefabName && p.Quality == chosen.Quality && !p.IsEquipped))
				{
					list.Add(new ValidationIssue(ValidationIssueKind.InsufficientQuantity, chosen.ContainerId, chosen.ItemId, chosen.Quality, 1L, 0L, chosen.Slot));
				}
				if (!hashSet.Contains(choice.Key))
				{
					list.Add(new ValidationIssue(ValidationIssueKind.SlotUnavailable, standId, chosen.ItemId, chosen.Quality, 1L, 0L, (int)choice.Key));
				}
			}
			return new ValidationResult(list);
		}

		public ValidationResult ValidateArmorSwap(ArmorSwapPlan plan, IReadOnlyDictionary<ArmorSlot, ArmorPiece>? stand, IReadOnlyDictionary<ArmorSlot, ArmorPiece>? worn, int standWeapons, int freeInventorySlots, string standId)
		{
			if (plan == null)
			{
				throw new ArgumentNullException("plan");
			}
			List<ValidationIssue> list = new List<ValidationIssue>();
			Dictionary<ArmorSlot, ArmorPiece> dictionary = plan.ToPlayer.ToDictionary<KeyValuePair<ArmorSlot, ArmorPiece>, ArmorSlot, ArmorPiece>((KeyValuePair<ArmorSlot, ArmorPiece> m) => m.Key, (KeyValuePair<ArmorSlot, ArmorPiece> m) => m.Value);
			ArmorSlot[] array = (ArmorSlot[])Enum.GetValues(typeof(ArmorSlot));
			foreach (ArmorSlot slot in array)
			{
				ArmorPiece value;
				ArmorPiece armorPiece = ((stand != null && stand.TryGetValue(slot, out value)) ? value : null);
				dictionary.TryGetValue(slot, out var value2);
				if (!SamePiece(armorPiece, value2) && ((object)value2 != null || plan.ToStand.Any<KeyValuePair<ArmorSlot, ArmorPiece>>((KeyValuePair<ArmorSlot, ArmorPiece> m) => m.Key == slot)))
				{
					ArmorPiece armorPiece2 = value2 ?? armorPiece;
					list.Add(new ValidationIssue(ValidationIssueKind.SlotUnavailable, standId, armorPiece2.ItemId, armorPiece2.Quality, 1L, 0L, (int)slot));
				}
			}
			foreach (KeyValuePair<ArmorSlot, ArmorPiece> item in plan.ToStand)
			{
				if (!SamePiece((worn != null && worn.TryGetValue(item.Key, out ArmorPiece value3)) ? value3 : null, item.Value))
				{
					list.Add(new ValidationIssue(ValidationIssueKind.InsufficientQuantity, null, item.Value.ItemId, item.Value.Quality, 1L, 0L, (int)item.Key));
				}
			}
			int num2 = Math.Max(0, plan.ToPlayer.Count - plan.ToStand.Count) + Math.Max(0, standWeapons);
			if (freeInventorySlots < num2)
			{
				long required = num2;
				long available = Math.Max(0, freeInventorySlots);
				list.Add(new ValidationIssue(ValidationIssueKind.InventoryFull, null, null, null, required, available));
			}
			return new ValidationResult(list);
		}

		private static bool SamePiece(ArmorPiece? a, ArmorPiece? b)
		{
			if ((object)a != null)
			{
				if ((object)b != null && a.PrefabName == b.PrefabName)
				{
					return a.Quality == b.Quality;
				}
				return false;
			}
			return (object)b == null;
		}

		private static Dictionary<string, ContainerSnapshot> Index(IEnumerable<ContainerSnapshot?>? containers)
		{
			Dictionary<string, ContainerSnapshot> dictionary = new Dictionary<string, ContainerSnapshot>(StringComparer.Ordinal);
			foreach (ContainerSnapshot item in Deduplication.ById(containers, (ContainerSnapshot c) => c.Id))
			{
				dictionary.Add(item.Id, item);
			}
			return dictionary;
		}

		private static Dictionary<int, ItemStack> BySlot(IEnumerable<ItemStack?>? inventory)
		{
			Dictionary<int, ItemStack> dictionary = new Dictionary<int, ItemStack>();
			foreach (ItemStack item in Deduplication.ById(inventory, (ItemStack s) => s.Slot?.ToString(CultureInfo.InvariantCulture)))
			{
				dictionary.Add(item.Slot.Value, item);
			}
			return dictionary;
		}

		private ValidationIssueKind? CheckContainer(string id, Dictionary<string, ContainerSnapshot> containersById, out ContainerSnapshot? container)
		{
			if (!containersById.TryGetValue(id, out container))
			{
				return ValidationIssueKind.ContainerMissing;
			}
			if (container.IsInUse)
			{
				return ValidationIssueKind.ContainerInUse;
			}
			if (!container.IsAccessible)
			{
				return ValidationIssueKind.ContainerInaccessible;
			}
			if (_excludedKinds.Contains(container.Kind))
			{
				return ValidationIssueKind.ContainerExcluded;
			}
			return null;
		}
	}
	public sealed class ResourceCount
	{
		private readonly Dictionary<ItemKey, long> _byKey;

		private readonly Dictionary<string, long> _byItem;

		private readonly QualityMatching _qualityMatching;

		public IReadOnlyDictionary<ItemKey, long> ByItemAndQuality => _byKey;

		internal ResourceCount(Dictionary<ItemKey, long> byKey, QualityMatching qualityMatching)
		{
			_byKey = byKey;
			_qualityMatching = qualityMatching;
			_byItem = new Dictionary<string, long>(StringComparer.Ordinal);
			foreach (KeyValuePair<ItemKey, long> item in byKey)
			{
				_byItem.TryGetValue(item.Key.ItemId, out var value);
				_byItem[item.Key.ItemId] = value + item.Value;
			}
		}

		public long GetTotal(string itemId)
		{
			if (!_byItem.TryGetValue(itemId, out var value))
			{
				return 0L;
			}
			return value;
		}

		public long GetTotal(string itemId, int quality)
		{
			if (!_byKey.TryGetValue(new ItemKey(itemId, quality), out var value))
			{
				return 0L;
			}
			return value;
		}

		public long GetAvailable(Requirement requirement)
		{
			if ((object)requirement == null)
			{
				throw new ArgumentNullException("requirement");
			}
			if (!requirement.Quality.HasValue || _qualityMatching == QualityMatching.Ignore)
			{
				return GetTotal(requirement.ItemId);
			}
			long num = 0L;
			foreach (KeyValuePair<ItemKey, long> item in _byKey)
			{
				if (string.Equals(item.Key.ItemId, requirement.ItemId, StringComparison.Ordinal) && Stock.Matches(_qualityMatching, item.Key.Quality, requirement.Quality))
				{
					num += item.Value;
				}
			}
			return num;
		}
	}
	public sealed class ResourceCounter
	{
		private readonly ModConfig _config;

		public ResourceCounter(ModConfig? config = null)
		{
			_config = ModConfig.Validate(config);
		}

		public ResourceCount Count(IEnumerable<ItemStack?>? inventory, IEnumerable<ContainerSnapshot?>? containers)
		{
			Dictionary<ItemKey, long> dictionary = Stock.TallyInventory(inventory, _config);
			foreach (ContainerSnapshot item in Deduplication.ById(containers, (ContainerSnapshot c) => c.Id))
			{
				foreach (KeyValuePair<ItemKey, long> item2 in Stock.TallyContainer(item))
				{
					dictionary.TryGetValue(item2.Key, out var value);
					dictionary[item2.Key] = value + item2.Value;
				}
			}
			return new ResourceCount(dictionary, _config.QualityMatching);
		}
	}
	public sealed class StationNetwork
	{
		public Vector3 Origin { get; }

		public IReadOnlyList<StationSnapshot> Stations { get; }

		public bool IsEmpty => Stations.Count == 0;

		internal double[] X { get; }

		internal double[] Y { get; }

		internal double[] Z { get; }

		internal double[] Ranges { get; }

		internal double MaxRange { get; }

		internal SpatialGrid Grid { get; }

		internal StationNetwork(Vector3 origin, IReadOnlyList<StationSnapshot> stations)
		{
			Origin = origin;
			Stations = stations;
			int count = stations.Count;
			X = new double[count];
			Y = new double[count];
			Z = new double[count];
			Ranges = new double[count];
			for (int i = 0; i < count; i++)
			{
				StationSnapshot stationSnapshot = stations[i];
				X[i] = stationSnapshot.Position.X;
				Y[i] = stationSnapshot.Position.Y;
				Z[i] = stationSnapshot.Position.Z;
				Ranges[i] = stationSnapshot.Range;
				MaxRange = Math.Max(MaxRange, Ranges[i]);
			}
			Grid = new SpatialGrid(X, Z, MaxRange);
		}
	}
	public sealed class StationNetworkResolver
	{
		private readonly ModConfig _config;

		private readonly HashSet<string>? _allowedTypes;

		public StationNetworkResolver(ModConfig? config = null)
		{
			_config = ModConfig.Validate(config);
			_allowedTypes = ((_config.AllowedStationTypes == null) ? null : new HashSet<string>(_config.AllowedStationTypes, StringComparer.Ordinal));
		}

		public StationNetwork Resolve(Vector3 playerPosition, IEnumerable<StationSnapshot?>? stations)
		{
			List<StationSnapshot> list = Deduplication.ById(stations, (StationSnapshot station) => station.Id);
			list.RemoveAll((StationSnapshot station) => !IsUsable(station));
			int count = list.Count;
			double[] array = new double[count];
			double[] array2 = new double[count];
			double[] array3 = new double[count];
			double[] array4 = new double[count];
			bool[] array5 = new bool[count];
			int[] array6 = new int[count];
			int num = 0;
			int num2 = 0;
			bool inclusiveBoundaries = _config.InclusiveBoundaries;
			double num3 = 0.0;
			for (int num4 = 0; num4 < count; num4++)
			{
				StationSnapshot stationSnapshot = list[num4];
				array[num4] = stationSnapshot.Position.X;
				array2[num4] = stationSnapshot.Position.Y;
				array3[num4] = stationSnapshot.Position.Z;
				array4[num4] = stationSnapshot.Range;
				num3 = Math.Max(num3, array4[num4]);
				if (Geometry.IsWithin(Geometry.DistanceSquared(array[num4], array2[num4], array3[num4], playerPosition.X, playerPosition.Y, playerPosition.Z), array4[num4], inclusiveBoundaries))
				{
					array5[num4] = true;
					array6[num2++] = num4;
				}
			}
			if (num2 > 0)
			{
				SpatialGrid spatialGrid = new SpatialGrid(array, array3, ConnectionRadius(num3, num3));
				List<int> list2 = new List<int>();
				while (num < num2)
				{
					int num5 = array6[num++];
					spatialGrid.Query(array[num5], array3[num5], ConnectionRadius(array4[num5], num3), list2);
					foreach (int item in list2)
					{
						if (!array5[item] && Geometry.IsWithin(Geometry.DistanceSquared(array[num5], array2[num5], array3[num5], array[item], array2[item], array3[item]), ConnectionRadius(array4[num5], array4[item]), inclusiveBoundaries))
						{
							array5[item] = true;
							array6[num2++] = item;
						}
					}
				}
			}
			List<StationSnapshot> list3 = new List<StationSnapshot>(num2);
			for (int num6 = 0; num6 < count; num6++)
			{
				if (array5[num6])
				{
					list3.Add(list[num6]);
				}
			}
			list3.Sort((StationSnapshot a, StationSnapshot b) => string.CompareOrdinal(a.Id, b.Id));
			return new StationNetwork(playerPosition, list3);
		}

		private bool IsUsable(StationSnapshot station)
		{
			if (float.IsFinite(station.Range) && station.Range > 0f)
			{
				if (_allowedTypes != null)
				{
					if (station.StationType != null)
					{
						return _allowedTypes.Contains(station.StationType);
					}
					return false;
				}
				return true;
			}
			return false;
		}

		private double ConnectionRadius(double rangeA, double rangeB)
		{
			if (_config.ConnectionRule != ConnectionRule.StationInsideZone)
			{
				return rangeA + rangeB;
			}
			return Math.Max(rangeA, rangeB);
		}
	}
}
namespace ChestNetwork.Core.Multiplayer
{
	public static class ModConfigCodec
	{
		private const string Header = "cn1";

		private const string AllTypes = "*";

		public static string Encode(ModConfig config)
		{
			if ((object)config == null)
			{
				throw new ArgumentNullException("config");
			}
			string text = ((config.AllowedStationTypes == null) ? "*" : string.Join(",", config.AllowedStationTypes.Where((string type) => !string.IsNullOrEmpty(type)).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string type) => type, StringComparer.Ordinal)
				.Select(Uri.EscapeDataString)));
			string text2 = string.Join(",", (from kind in ((IEnumerable<ContainerKind>)(((object)config.ExcludedContainerKinds) ?? ((object)Array.Empty<ContainerKind>()))).Distinct()
				orderby kind
				select kind).Select(delegate(ContainerKind kind)
			{
				int num = (int)kind;
				return num.ToString(CultureInfo.InvariantCulture);
			}));
			return string.Join("|", "cn1", "rule=" + ((int)config.ConnectionRule).ToString(CultureInfo.InvariantCulture), "inclusive=" + Flag(config.InclusiveBoundaries), "types=" + text, "excluded=" + text2, "quality=" + ((int)config.QualityMatching).ToString(CultureInfo.InvariantCulture), "equipped=" + Flag(config.AllowEquippedItems));
		}

		public static bool TryDecode(string? text, out ModConfig config, out string error)
		{
			config = ModConfig.Default;
			if (string.IsNullOrEmpty(text))
			{
				error = "Empty configuration.";
				return false;
			}
			string[] array = text.Split('|');
			if (array[0] != "cn1")
			{
				error = "Unknown configuration format '" + array[0] + "'.";
				return false;
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			foreach (string item in array.Skip(1))
			{
				int num = item.IndexOf('=');
				if (num <= 0)
				{
					error = "Malformed entry '" + item + "'.";
					return false;
				}
				string text2 = item.Substring(0, num);
				if (dictionary.ContainsKey(text2))
				{
					error = "Duplicated key '" + text2 + "'.";
					return false;
				}
				dictionary.Add(text2, item.Substring(num + 1));
			}
			if (!TryEnum<ConnectionRule>(dictionary, "rule", out var result, out error) || !TryFlag(dictionary, "inclusive", out var flag, out error) || !TryTypes(dictionary, out IReadOnlyCollection<string> types, out error) || !TryKinds(dictionary, out IReadOnlyCollection<ContainerKind> kinds, out error) || !TryEnum<QualityMatching>(dictionary, "quality", out var result2, out error) || !TryFlag(dictionary, "equipped", out var flag2, out error))
			{
				return false;
			}
			config = ModConfig.Default with
			{
				ConnectionRule = result,
				InclusiveBoundaries = flag,
				AllowedStationTypes = types,
				ExcludedContainerKinds = kinds,
				QualityMatching = result2,
				AllowEquippedItems = flag2
			};
			error = string.Empty;
			return true;
		}

		private static string Flag(bool value)
		{
			if (!value)
			{
				return "0";
			}
			return "1";
		}

		private static bool TryGet(Dictionary<string, string> values, string key, out string value, out string error)
		{
			if (!values.TryGetValue(key, out value))
			{
				error = "Missing key '" + key + "'.";
				return false;
			}
			error = string.Empty;
			return true;
		}

		private static bool TryFlag(Dictionary<string, string> values, string key, out bool flag, out string error)
		{
			flag = false;
			if (!TryGet(values, key, out string value, out error))
			{
				return false;
			}
			if (value != "0" && value != "1")
			{
				error = "Invalid flag '" + value + "' for '" + key + "'.";
				return false;
			}
			flag = value == "1";
			return true;
		}

		private static bool TryInt(string value, out int number)
		{
			return int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out number);
		}

		private static bool TryEnum<T>(Dictionary<string, string> values, string key, out T result, out string error) where T : struct, Enum
		{
			result = default(T);
			if (!TryGet(values, key, out string value, out error))
			{
				return false;
			}
			if (!TryInt(value, out var number) || !Enum.IsDefined(typeof(T), number))
			{
				error = "Invalid value '" + value + "' for '" + key + "'.";
				return false;
			}
			result = (T)Enum.ToObject(typeof(T), number);
			return true;
		}

		private static bool TryTypes(Dictionary<string, string> values, out IReadOnlyCollection<string>? types, out string error)
		{
			types = null;
			if (!TryGet(values, "types", out string value, out error))
			{
				return false;
			}
			if (value == "*")
			{
				return true;
			}
			types = (IReadOnlyCollection<string>?)(object)((value.Length == 0) ? Array.Empty<string>() : value.Split(',').Select(Uri.UnescapeDataString).ToArray());
			return true;
		}

		private static bool TryKinds(Dictionary<string, string> values, out IReadOnlyCollection<ContainerKind> kinds, out string error)
		{
			kinds = (IReadOnlyCollection<ContainerKind>)(object)Array.Empty<ContainerKind>();
			if (!TryGet(values, "excluded", out string value, out error))
			{
				return false;
			}
			if (value.Length == 0)
			{
				return true;
			}
			List<ContainerKind> list = new List<ContainerKind>();
			string[] array = value.Split(',');
			foreach (string text in array)
			{
				if (!TryInt(text, out var number) || !Enum.IsDefined(typeof(ContainerKind), number))
				{
					error = "Invalid container kind '" + text + "'.";
					return false;
				}
				list.Add((ContainerKind)number);
			}
			kinds = list;
			return true;
		}
	}
	public enum GrantDecision
	{
		Grant,
		NotOwner,
		InUse,
		NoAccess,
		Reserved
	}
	public static class OwnershipGrantPolicy
	{
		public static GrantDecision Decide(bool isOwner, bool isInUse, bool requesterHasAccess, double reservedUntil, double now)
		{
			if (!isOwner)
			{
				return GrantDecision.NotOwner;
			}
			if (isInUse)
			{
				return GrantDecision.InUse;
			}
			if (!requesterHasAccess)
			{
				return GrantDecision.NoAccess;
			}
			if (!(now < reservedUntil))
			{
				return GrantDecision.Grant;
			}
			return GrantDecision.Reserved;
		}
	}
	public enum OwnershipState
	{
		Idle,
		Requested,
		AwaitingData,
		Unavailable
	}
	public sealed class OwnershipTracker
	{
		private readonly struct Entry
		{
			public OwnershipState State { get; }

			public double Since { get; }

			public uint GrantedRevision { get; }

			public Entry(OwnershipState state, double since, uint grantedRevision)
			{
				State = state;
				Since = since;
				GrantedRevision = grantedRevision;
			}
		}

		private readonly double _requestTimeout;

		private readonly double _retryDelay;

		private readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry>(StringComparer.Ordinal);

		public OwnershipTracker(double requestTimeout, double retryDelay)
		{
			if (!(requestTimeout > 0.0) || double.IsInfinity(requestTimeout))
			{
				throw new ArgumentOutOfRangeException("requestTimeout", requestTimeout, "Must be a positive finite number.");
			}
			if (!(retryDelay >= 0.0) || double.IsInfinity(retryDelay))
			{
				throw new ArgumentOutOfRangeException("retryDelay", retryDelay, "Must be a non-negative finite number.");
			}
			_requestTimeout = requestTimeout;
			_retryDelay = retryDelay;
		}

		public OwnershipState StateOf(string containerId, double now)
		{
			if (!_entries.TryGetValue(containerId, out var value))
			{
				return OwnershipState.Idle;
			}
			switch (value.State)
			{
			case OwnershipState.Requested:
				if (now - value.Since >= _requestTimeout)
				{
					_entries[containerId] = new Entry(OwnershipState.Unavailable, value.Since + _requestTimeout, 0u);
					return StateOf(containerId, now);
				}
				break;
			case OwnershipState.Unavailable:
				if (now - value.Since >= _retryDelay)
				{
					_entries.Remove(containerId);
					return OwnershipState.Idle;
				}
				break;
			}
			return value.State;
		}

		public bool TryBeginRequest(string containerId, double now)
		{
			if (StateOf(containerId, now) != OwnershipState.Idle)
			{
				return false;
			}
			_entries[containerId] = new Entry(OwnershipState.Requested, now, 0u);
			return true;
		}

		public void OnGranted(string containerId, uint dataRevision, double now)
		{
			_entries[containerId] = new Entry(OwnershipState.AwaitingData, now, dataRevision);
		}

		public void OnDenied(string containerId, double now)
		{
			if (!_entries.TryGetValue(containerId, out var value) || value.State != OwnershipState.AwaitingData)
			{
				_entries[containerId] = new Entry(OwnershipState.Unavailable, now, 0u);
			}
		}

		public bool OnDataRevision(string containerId, uint localRevision)
		{
			if (_entries.TryGetValue(containerId, out var value) && value.State == OwnershipState.AwaitingData && localRevision >= value.GrantedRevision)
			{
				_entries.Remove(containerId);
				return true;
			}
			return false;
		}

		public bool IsUsableWhenOwned(string containerId, double now)
		{
			return StateOf(containerId, now) != OwnershipState.AwaitingData;
		}

		public void Forget(string containerId)
		{
			_entries.Remove(containerId);
		}

		public IReadOnlyList<string> AwaitingData()
		{
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Entry> entry in _entries)
			{
				if (entry.Value.State == OwnershipState.AwaitingData)
				{
					list.Add(entry.Key);
				}
			}
			list.Sort(StringComparer.Ordinal);
			return list;
		}
	}
	public static class Protocol
	{
		public const int Version = 3;

		public static bool IsCompatible(int remoteVersion)
		{
			return remoteVersion == 3;
		}
	}
}
namespace ChestNetwork.Core.Internal
{
	internal static class Deduplication
	{
		public static List<T> ById<T>(IEnumerable<T?>? source, Func<T, string?> idOf) where T : class, IEquatable<T>
		{
			List<T> list = new List<T>();
			if (source == null)
			{
				return list;
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			HashSet<string> conflicting = null;
			foreach (T item in source)
			{
				if (item == null)
				{
					continue;
				}
				string text = idOf(item);
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				if (dictionary.TryGetValue(text, out var value))
				{
					if (!list[value].Equals(item))
					{
						if (conflicting == null)
						{
							conflicting = new HashSet<string>(StringComparer.Ordinal);
						}
						conflicting.Add(text);
					}
				}
				else
				{
					dictionary.Add(text, list.Count);
					list.Add(item);
				}
			}
			if (conflicting != null)
			{
				list.RemoveAll((T item) => conflicting.Contains(idOf(item)));
			}
			return list;
		}
	}
	internal static class Geometry
	{
		public static double DistanceSquared(Vector3 a, Vector3 b)
		{
			return DistanceSquared(a.X, a.Y, a.Z, b.X, b.Y, b.Z);
		}

		public static double DistanceSquared(double ax, double ay, double az, double bx, double by, double bz)
		{
			double num = ax - bx;
			double num2 = ay - by;
			double num3 = az - bz;
			return num * num + num2 * num2 + num3 * num3;
		}

		public static bool IsWithin(double distanceSquared, double radius, bool inclusive)
		{
			double num = radius * radius;
			if (!inclusive)
			{
				return distanceSquared < num;
			}
			return distanceSquared <= num;
		}
	}
	internal static class GreedyAllocation
	{
		public static long Allocate(long amount, int count, Func<int, long> capacityOf, Action<int, long> onAllocated)
		{
			long num = 0L;
			for (int i = 0; i < count; i++)
			{
				if (num >= amount)
				{
					break;
				}
				long num2 = Math.Min(Math.Max(0L, capacityOf(i)), amount - num);
				if (num2 > 0)
				{
					num += num2;
					onAllocated(i, num2);
				}
			}
			return num;
		}
	}
	internal sealed class SpatialGrid
	{
		private const double MaxCellIndex = 1000000000.0;

		private readonly double _cellSize;

		private readonly Dictionary<long, List<int>> _cells = new Dictionary<long, List<int>>();

		public SpatialGrid(double[] x, double[] z, double cellSize)
		{
			_cellSize = cellSize;
			for (int i = 0; i < x.Length; i++)
			{
				long key = Key(CellOf(x[i]), CellOf(z[i]));
				if (!_cells.TryGetValue(key, out List<int> value))
				{
					value = new List<int>();
					_cells.Add(key, value);
				}
				value.Add(i);
			}
		}

		public void Query(double x, double z, double radius, List<int> candidates)
		{
			candidates.Clear();
			int num = CellOf(x - radius);
			int num2 = CellOf(x + radius);
			int num3 = CellOf(z - radius);
			int num4 = CellOf(z + radius);
			for (int i = num; i <= num2; i++)
			{
				for (int j = num3; j <= num4; j++)
				{
					if (_cells.TryGetValue(Key(i, j), out List<int> value))
					{
						candidates.AddRange(value);
					}
				}
			}
		}

		private int CellOf(double coordinate)
		{
			double val = Math.Floor(coordinate / _cellSize);
			return (int)Math.Max(-1000000000.0, Math.Min(1000000000.0, val));
		}

		private static long Key(int cx, int cz)
		{
			return ((long)cx << 32) | (uint)cz;
		}
	}
	internal static class Stock
	{
		public static Dictionary<ItemKey, long> Tally(IEnumerable<ItemStack?>? stacks, bool includeEquipped)
		{
			Dictionary<ItemKey, long> dictionary = new Dictionary<ItemKey, long>();
			if (stacks == null)
			{
				return dictionary;
			}
			foreach (ItemStack stack in stacks)
			{
				if ((object)stack != null && !string.IsNullOrEmpty(stack.ItemId) && stack.Amount > 0 && (!stack.IsEquipped || includeEquipped))
				{
					ItemKey key = new ItemKey(stack.ItemId, stack.Quality);
					dictionary.TryGetValue(key, out var value);
					dictionary[key] = value + stack.Amount;
				}
			}
			return dictionary;
		}

		public static Dictionary<ItemKey, long> TallyInventory(IEnumerable<ItemStack?>? inventory, ModConfig config)
		{
			return Tally(inventory, config.AllowEquippedItems);
		}

		public static Dictionary<ItemKey, long> TallyContainer(ContainerSnapshot container)
		{
			return Tally(container.Items, includeEquipped: true);
		}

		public static bool Matches(QualityMatching matching, int stackQuality, int? requiredQuality)
		{
			if (requiredQuality.HasValue)
			{
				if (matching != QualityMatching.Exact)
				{
					return stackQuality >= requiredQuality.Value;
				}
				return stackQuality == requiredQuality.Value;
			}
			return true;
		}
	}
}
namespace ChestNetwork.Core.Filling
{
	public enum FillStatus
	{
		Filled,
		Partial,
		AlreadyFull,
		NothingAvailable,
		NoAction
	}
	public sealed class FillPlan
	{
		public FillStatus Status { get; }

		public long Capacity { get; }

		public IReadOnlyList<Withdrawal> Withdrawals { get; }

		public long TotalInserted { get; }

		internal FillPlan(FillStatus status, long capacity, IReadOnlyList<Withdrawal> withdrawals)
		{
			Status = status;
			Capacity = capacity;
			Withdrawals = withdrawals;
			long num = 0L;
			foreach (Withdrawal withdrawal in withdrawals)
			{
				num += withdrawal.Amount;
			}
			TotalInserted = num;
		}

		public long TotalFor(string itemId)
		{
			long num = 0L;
			foreach (Withdrawal withdrawal in Withdrawals)
			{
				if (string.Equals(withdrawal.ItemId, itemId, StringComparison.Ordinal))
				{
					num += withdrawal.Amount;
				}
			}
			return num;
		}

		public FillSettlement Settle(long accepted)
		{
			long num = Math.Max(0L, Math.Min(accepted, TotalInserted));
			List<Withdrawal> list = new List<Withdrawal>();
			List<Withdrawal> list2 = new List<Withdrawal>();
			foreach (Withdrawal withdrawal in Withdrawals)
			{
				long num2 = Math.Min(num, withdrawal.Amount);
				num -= num2;
				if (num2 > 0)
				{
					list.Add(withdrawal with
					{
						Amount = num2
					});
				}
				if (num2 < withdrawal.Amount)
				{
					list2.Add(withdrawal with
					{
						Amount = withdrawal.Amount - num2
					});
				}
			}
			return new FillSettlement(list, list2);
		}
	}
	public sealed class FillSettlement
	{
		public IReadOnlyList<Withdrawal> Inserted { get; }

		public IReadOnlyList<Withdrawal> Refunded { get; }

		internal FillSettlement(IReadOnlyList<Withdrawal> inserted, IReadOnlyList<Withdrawal> refunded)
		{
			Inserted = inserted;
			Refunded = refunded;
		}
	}
	public enum InventoryUse
	{
		Never,
		First,
		Last
	}
	public enum ReserveScope
	{
		Network,
		PerContainer
	}
	public sealed record FillSettings
	{
		public static FillSettings Default { get; } = new FillSettings();

		public InventoryUse InventoryUse { get; init; }

		public int MinimumReserve
		{
			get
			{
				return _minimumReserve;
			}
			init
			{
				if (value < 0)
				{
					throw new ArgumentOutOfRangeException("MinimumReserve", value, "Must be zero or more.");
				}
				_minimumReserve = value;
			}
		}

		public ReserveScope ReserveScope { get; init; }

		public int? MaxPerAction
		{
			get
			{
				return _maxPerAction;
			}
			init
			{
				if (value.HasValue && !(value > 0))
				{
					throw new ArgumentOutOfRangeException("MaxPerAction", value, "Must be positive, or null for no limit.");
				}
				_maxPerAction = value;
			}
		}

		public IReadOnlyCollection<string>? ForbiddenItems { get; init; }

		private readonly int _minimumReserve;

		private readonly int? _maxPerAction;

		[CompilerGenerated]
		private bool PrintMembers(StringBuilder builder)
		{
			RuntimeHelpers.EnsureSufficientExecutionStack();
			builder.Append("InventoryUse = ");
			builder.Append(InventoryUse.ToString());
			builder.Append(", MinimumReserve = ");
			builder.Append(MinimumReserve.ToString());
			builder.Append(", ReserveScope = ");
			builder.Append(ReserveScope.ToString());
			builder.Append(", MaxPerAction = ");
			builder.Append(MaxPerAction.ToString());
			builder.Append(", ForbiddenItems = ");
			builder.Append(ForbiddenItems);
			return true;
		}

		[CompilerGenerated]
		public override int GetHashCode()
		{
			return ((((EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(_minimumReserve)) * -1521134295 + EqualityComparer<int?>.Default.GetHashCode(_maxPerAction)) * -1521134295 + EqualityComparer<InventoryUse>.Default.GetHashCode(InventoryUse)) * -1521134295 + EqualityComparer<ReserveScope>.Default.GetHashCode(ReserveScope)) * -1521134295 + EqualityComparer<IReadOnlyCollection<string>>.Default.GetHashCode(ForbiddenItems);
		}

		[CompilerGenerated]
		public bool Equals(FillSettings? other)
		{
			if ((object)this != other)
			{
				if ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(_minimumReserve, other._minimumReserve) && EqualityComparer<int?>.Default.Equals(_maxPerAction, other._maxPerAction) && EqualityComparer<InventoryUse>.Default.Equals(InventoryUse, other.InventoryUse) && EqualityComparer<ReserveScope>.Default.Equals(ReserveScope, other.ReserveScope))
				{
					return EqualityComparer<IReadOnlyCollection<string>>.Default.Equals(ForbiddenItems, other.ForbiddenItems);
				}
				return false;
			}
			return true;
		}

		[CompilerGenerated]
		private FillSettings(FillSettings original)
		{
			_minimumReserve = original._minimumReserve;
			_maxPerAction = original._maxPerAction;
			InventoryUse = original.InventoryUse;
			ReserveScope = original.ReserveScope;
			ForbiddenItems = original.ForbiddenItems;
		}

		public FillSettings()
		{
		}
	}
	public sealed class MachineFillPlanner
	{
		private sealed class Source
		{
			private readonly Dictionary<string, SortedDictionary<int, long>> _stock = new Dictionary<string, SortedDictionary<int, long>>(StringComparer.Ordinal);

			public string? ContainerId { get; }

			public Source(string? containerId, Dictionary<ItemKey, long> tally)
			{
				ContainerId = containerId;
				foreach (KeyValuePair<ItemKey, long> item in tally)
				{
					if (!_stock.TryGetValue(item.Key.ItemId, out SortedDictionary<int, long> value))
					{
						value = new SortedDictionary<int, long>();
						_stock.Add(item.Key.ItemId, value);
					}
					value.Add(item.Key.Quality, item.Value);
				}
			}

			public long Total(string item)
			{
				long num = 0L;
				if (_stock.TryGetValue(item, out SortedDictionary<int, long> value))
				{
					foreach (long value2 in value.Values)
					{
						num += value2;
					}
				}
				return num;
			}

			public void Take(string item, long units, List<Withdrawal> withdrawals)
			{
				List<KeyValuePair<int, long>> buckets = new List<KeyValuePair<int, long>>(_stock[item]);
				GreedyAllocation.Allocate(units, buckets.Count, (int i) => buckets[i].Value, delegate(int i, long amount)
				{
					withdrawals.Add(new Withdrawal(ContainerId, item, buckets[i].Key, amount));
				});
			}
		}

		private readonly ModConfig _config;

		private readonly FillSettings _settings;

		private readonly HashSet<string> _forbidden;

		public MachineFillPlanner(ModConfig? config = null, FillSettings? settings = null)
		{
			_config = ModConfig.Validate(config);
			_settings = settings ?? FillSettings.Default;
			if (!Enum.IsDefined(typeof(InventoryUse), _settings.InventoryUse))
			{
				throw new ArgumentOutOfRangeException("settings", _settings.InventoryUse, "Unknown inventory use.");
			}
			if (!Enum.IsDefined(typeof(ReserveScope), _settings.ReserveScope))
			{
				throw new ArgumentOutOfRangeException("settings", _settings.ReserveScope, "Unknown reserve scope.");
			}
			_forbidden = new HashSet<string>((IEnumerable<string>)(((object)_settings.ForbiddenItems) ?? ((object)Array.Empty<string>())), StringComparer.Ordinal);
		}

		public FillPlan Plan(double remainingCapacity, IEnumerable<string?>? acceptedItems, IEnumerable<ItemStack?>? inventory, IEnumerable<ContainerSnapshot?>? containers)
		{
			if (double.IsNaN(remainingCapacity) || double.IsInfinity(remainingCapacity) || remainingCapacity < 0.0)
			{
				return new FillPlan(FillStatus.NoAction, 0L, Array.Empty<Withdrawal>());
			}
			long num = ((remainingCapacity >= 9.223372036854776E+18) ? long.MaxValue : ((long)Math.Floor(remainingCapacity)));
			int? maxPerAction = _settings.MaxPerAction;
			if (maxPerAction.HasValue)
			{
				int valueOrDefault = maxPerAction.GetValueOrDefault();
				num = Math.Min(num, valueOrDefault);
			}
			if (num == 0L)
			{
				return new FillPlan(FillStatus.AlreadyFull, 0L, Array.Empty<Withdrawal>());
			}
			List<Source> sources = BuildSources(inventory, containers);
			List<Withdrawal> withdrawals = new List<Withdrawal>();
			long num2 = num;
			foreach (string item in Accepted(acceptedItems))
			{
				num2 -= TakeItem(item, num2, sources, withdrawals);
			}
			return new FillPlan((num2 != 0L) ? ((num2 != num) ? FillStatus.Partial : FillStatus.NothingAvailable) : FillStatus.Filled, num, withdrawals);
		}

		private List<string> Accepted(IEnumerable<string?>? acceptedItems)
		{
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (string item in acceptedItems ?? Array.Empty<string>())
			{
				if (!string.IsNullOrEmpty(item) && !_forbidden.Contains(item) && hashSet.Add(item))
				{
					list.Add(item);
				}
			}
			return list;
		}

		private List<Source> BuildSources(IEnumerable<ItemStack?>? inventory, IEnumerable<ContainerSnapshot?>? containers)
		{
			List<Source> list = new List<Source>();
			foreach (ContainerSnapshot item2 in Deduplication.ById(containers, (ContainerSnapshot c) => c.Id))
			{
				list.Add(new Source(item2.Id, Stock.TallyContainer(item2)));
			}
			if (_settings.InventoryUse != InventoryUse.Never)
			{
				Source item = new Source(null, Stock.TallyInventory(inventory, _config));
				list.Insert((_settings.InventoryUse != InventoryUse.First) ? list.Count : 0, item);
			}
			return list;
		}

		private long TakeItem(string item, long room, List<Source> sources, List<Withdrawal> withdrawals)
		{
			long reserve = _settings.MinimumReserve;
			bool networkScope = _settings.ReserveScope == ReserveScope.Network;
			long networkBudget = long.MaxValue;
			if (networkScope)
			{
				long num = 0L;
				foreach (Source source in sources)
				{
					if (source.ContainerId != null)
					{
						num += source.Total(item);
					}
				}
				networkBudget = Math.Max(0L, num - reserve);
			}
			return GreedyAllocation.Allocate(room, sources.Count, (int i) => Usable(sources[i]), delegate(int i, long units)
			{
				sources[i].Take(item, units, withdrawals);
				if (sources[i].ContainerId != null)
				{
					networkBudget -= units;
				}
			});
			long Usable(Source source)
			{
				long num2 = source.Total(item);
				if (source.ContainerId == null)
				{
					return num2;
				}
				if (!networkScope)
				{
					return Math.Max(0L, num2 - reserve);
				}
				return Math.Min(num2, networkBudget);
			}
		}
	}
}
namespace ChestNetwork.Core.Deposit
{
	public sealed record DepositSettings
	{
		public static DepositSettings Default { get; } = new DepositSettings();

		public IReadOnlyCollection<string> IncludedCategories { get; init; } = (IReadOnlyCollection<string>)(object)new string[2] { "Material", "Trophy" };

		public IReadOnlyCollection<string>? ForcedItems { get; init; }

		public IReadOnlyCollection<string>? ExcludedItems { get; init; }

		[CompilerGenerated]
		private DepositSettings(DepositSettings original)
		{
			IncludedCategories = original.IncludedCategories;
			ForcedItems = original.ForcedItems;
			ExcludedItems = original.ExcludedItems;
		}

		public DepositSettings()
		{
		}
	}
	public sealed record CategorizedStack(ItemStack Stack, string Category);
	public sealed class DepositFilter
	{
		private readonly HashSet<string> _categories;

		private readonly HashSet<string> _forced;

		private readonly HashSet<string> _excluded;

		public DepositFilter(DepositSettings? settings = null)
		{
			DepositSettings depositSettings = settings ?? DepositSettings.Default;
			_categories = new HashSet<string>((IEnumerable<string>)(((object)depositSettings.IncludedCategories) ?? ((object)Array.Empty<string>())), StringComparer.Ordinal);
			_forced = new HashSet<string>((IEnumerable<string>)(((object)depositSettings.ForcedItems) ?? ((object)Array.Empty<string>())), StringComparer.Ordinal);
			_excluded = new HashSet<string>((IEnumerable<string>)(((object)depositSettings.ExcludedItems) ?? ((object)Array.Empty<string>())), StringComparer.Ordinal);
		}

		public IReadOnlyList<ItemStack> Select(IEnumerable<CategorizedStack?>? inventory, int inventoryWidth)
		{
			List<ItemStack> list = new List<ItemStack>();
			foreach (CategorizedStack item in inventory ?? Array.Empty<CategorizedStack>())
			{
				ItemStack itemStack = item?.Stack;
				if ((object)itemStack != null && IsMovable(itemStack, item.Category, inventoryWidth))
				{
					list.Add(itemStack);
				}
			}
			return list;
		}

		private bool IsMovable(ItemStack stack, string category, int inventoryWidth)
		{
			if (InventoryProtection.IsProtected(stack, inventoryWidth) || string.IsNullOrEmpty(stack.ItemId) || stack.Amount <= 0)
			{
				return false;
			}
			if (_excluded.Contains(stack.ItemId))
			{
				return false;
			}
			if (!_forced.Contains(stack.ItemId))
			{
				if (category != null)
				{
					return _categories.Contains(category);
				}
				return false;
			}
			return true;
		}
	}
	public sealed record StackDeposit(int SourceSlot, string ContainerId, int TargetSlot, string ItemId, int Quality, string? StackKey, long Amount, bool NewStack);
	public sealed record DepositRemainder(string ItemId, long Amount);
	public sealed class DepositPlan
	{
		public IReadOnlyList<StackDeposit> Deposits { get; }

		public IReadOnlyList<DepositRemainder> NoRoom { get; }

		public IReadOnlyList<DepositRemainder> NoTarget { get; }

		public long TotalDeposited { get; }

		public IReadOnlyList<string> ContainersTouched { get; }

		internal DepositPlan(IReadOnlyList<StackDeposit> deposits, IReadOnlyList<DepositRemainder> noRoom, IReadOnlyList<DepositRemainder> noTarget)
		{
			Deposits = deposits;
			NoRoom = noRoom;
			NoTarget = noTarget;
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>();
			long num = 0L;
			foreach (StackDeposit deposit in deposits)
			{
				num += deposit.Amount;
				if (hashSet.Add(deposit.ContainerId))
				{
					list.Add(deposit.ContainerId);
				}
			}
			TotalDeposited = num;
			ContainersTouched = list;
		}
	}
	public sealed class DepositPlanner
	{
		private readonly struct Candidate
		{
			public Grid Grid { get; }

			public int Slot { get; }

			public long Capacity { get; }

			public bool NewStack { get; }

			public Candidate(Grid grid, int slot, long capacity, bool newStack)
			{
				Grid = grid;
				Slot = slot;
				Capacity = capacity;
				NewStack = newStack;
			}
		}

		private sealed class Cell
		{
			public string ItemId { get; }

			public string? StackKey { get; }

			public long Amount { get; set; }

			public Cell(string itemId, string? stackKey, long amount)
			{
				ItemId = itemId;
				StackKey = stackKey;
				Amount = amount;
			}
		}

		private sealed class Grid
		{
			private readonly int _slotCount;

			private readonly Dictionary<string, long> _initiallyHeld;

			public string Id { get; }

			public int Index { get; }

			public SortedDictionary<int, Cell> Cells { get; }

			private Grid(string id, int index, int slotCount, SortedDictionary<int, Cell> cells, Dictionary<string, long> initiallyHeld)
			{
				Id = id;
				Index = index;
				_slotCount = slotCount;
				Cells = cells;
				_initiallyHeld = initiallyHeld;
			}

			public static Grid? TryCreate(ContainerSnapshot container, int index)
			{
				SortedDictionary<int, Cell> sortedDictionary = new SortedDictionary<int, Cell>();
				Dictionary<string, long> dictionary = new Dictionary<string, long>(StringComparer.Ordinal);
				foreach (ItemStack item in container.Items)
				{
					if (string.IsNullOrEmpty(item.ItemId) || item.Amount <= 0)
					{
						continue;
					}
					int? slot = item.Slot;
					if (slot.HasValue)
					{
						int valueOrDefault = slot.GetValueOrDefault();
						if (valueOrDefault >= 0 && valueOrDefault < container.SlotCount && !sortedDictionary.ContainsKey(valueOrDefault))
						{
							sortedDictionary.Add(valueOrDefault, new Cell(item.ItemId, item.StackKey, item.Amount));
							dictionary.TryGetValue(item.ItemId, out var value);
							dictionary[item.ItemId] = value + item.Amount;
							continue;
						}
					}
					return null;
				}
				return new Grid(container.Id, index, container.SlotCount, sortedDictionary, dictionary);
			}

			public long InitiallyHeld(string itemId)
			{
				if (!_initiallyHeld.TryGetValue(itemId, out var value))
				{
					return 0L;
				}
				return value;
			}

			public IEnumerable<int> FreeSlots()
			{
				for (int slot = 0; slot < _slotCount; slot++)
				{
					if (!Cells.ContainsKey(slot))
					{
						yield return slot;
					}
				}
			}

			public void Put(int slot, ItemStack stack, long units)
			{
				if (Cells.TryGetValue(slot, out Cell value))
				{
					value.Amount += units;
				}
				else
				{
					Cells.Add(slot, new Cell(stack.ItemId, stack.StackKey, units));
				}
			}
		}

		public DepositPlan Plan(IEnumerable<ItemStack?>? items, IEnumerable<ContainerSnapshot?>? containers)
		{
			List<Grid> source = (from grid in Deduplication.ById(containers, (ContainerSnapshot c) => c.Id).Select((ContainerSnapshot container, int index) => Grid.TryCreate(container, index))
				where grid != null
				select (grid)).ToList();
			List<ItemStack> list = (from s in Deduplication.ById(items, (ItemStack s) => s.Slot?.ToString(CultureInfo.InvariantCulture))
				where !string.IsNullOrEmpty(s.ItemId) && s.Amount > 0
				select s).OrderBy<ItemStack, string>((ItemStack s) => s.ItemId, StringComparer.Ordinal).ThenBy<ItemStack, string>((ItemStack s) => s.StackKey, StringComparer.Ordinal).ThenBy((ItemStack s) => s.Quality)
				.ThenBy((ItemStack s) => s.Slot)
				.ToList();
			List<StackDeposit> deposits = new List<StackDeposit>();
			SortedDictionary<string, long> sortedDictionary = new SortedDictionary<string, long>(StringComparer.Ordinal);
			SortedDictionary<string, long> sortedDictionary2 = new SortedDictionary<string, long>(StringComparer.Ordinal);
			foreach (ItemStack stack in list)
			{
				List<Grid> list2 = source.Where((Grid grid) => grid.InitiallyHeld(stack.ItemId) > 0).ToList();
				if (list2.Count == 0)
				{
					Add(sortedDictionary2, stack.ItemId, stack.Amount);
					continue;
				}
				long num = ((stack.MaxStack > 0) ? Place(stack, list2, deposits) : 0);
				if (num < stack.Amount)
				{
					Add(sortedDictionary, stack.ItemId, stack.Amount - num);
				}
			}
			return new DepositPlan(deposits, sortedDictionary.Select((KeyValuePair<string, long> entry) => new DepositRemainder(entry.Key, entry.Value)).ToList(), sortedDictionary2.Select((KeyValuePair<string, long> entry) => new DepositRemainder(entry.Key, entry.Value)).ToList());
		}

		private static void Add(SortedDictionary<string, long> totals, string itemId, long amount)
		{
			totals.TryGetValue(itemId, out var value);
			totals[itemId] = value + amount;
		}

		private static long Place(ItemStack stack, List<Grid> targets, List<StackDeposit> deposits)
		{
			int maxStack = stack.MaxStack;
			List<Candidate> candidates = new List<Candidate>();
			if (stack.StackKey != null)
			{
				foreach (Grid target in targets)
				{
					foreach (KeyValuePair<int, Cell> cell in target.Cells)
					{
						if (cell.Value.ItemId == stack.ItemId && cell.Value.StackKey == stack.StackKey && cell.Value.Amount < maxStack)
						{
							candidates.Add(new Candidate(target, cell.Key, maxStack - cell.Value.Amount, newStack: false));
						}
					}
				}
			}
			long num = candidates.Sum((Candidate c) => c.Capacity);
			foreach (Grid item in from g in targets
				orderby g.InitiallyHeld(stack.ItemId) descending, g.Index
				select g)
			{
				foreach (int item2 in item.FreeSlots())
				{
					if (num >= stack.Amount)
					{
						break;
					}
					candidates.Add(new Candidate(item, item2, maxStack, newStack: true));
					num += maxStack;
				}
			}
			return GreedyAllocation.Allocate(stack.Amount, candidates.Count, (int i) => candidates[i].Capacity, delegate(int i, long units)
			{
				Candidate candidate = candidates[i];
				candidate.Grid.Put(candidate.Slot, stack, units);
				deposits.Add(new StackDeposit(stack.Slot.Value, candidate.Grid.Id, candidate.Slot, stack.ItemId, stack.Quality, stack.StackKey, units, candidate.NewStack));
			});
		}
	}
	public static class InventoryProtection
	{
		public static bool IsProtected(ItemStack stack, int inventoryWidth, bool protectHotbar = true)
		{
			int? slot = stack.Slot;
			if (slot.HasValue)
			{
				int valueOrDefault = slot.GetValueOrDefault();
				if (!stack.IsEquipped)
				{
					if (protectHotbar)
					{
						return valueOrDefault < inventoryWidth;
					}
					return false;
				}
			}
			return true;
		}
	}
}
namespace ChestNetwork.Core.Armor
{
	public sealed record ArmorFillSettings
	{
		public static ArmorFillSettings Default { get; } = new ArmorFillSettings();

		public double SetWeight
		{
			get
			{
				return _setWeight;
			}
			init
			{
				_setWeight = Checked(value, "SetWeight");
			}
		}

		public double TierWeight
		{
			get
			{
				return _tierWeight;
			}
			init
			{
				_tierWeight = Checked(value, "TierWeight");
			}
		}

		public double QualityWeight
		{
			get
			{
				return _qualityWeight;
			}
			init
			{
				_qualityWeight = Checked(value, "QualityWeight");
			}
		}

		public bool ProtectHotbar { get; init; } = true;

		private readonly double _setWeight = 100.0;

		private readonly double _tierWeight = 10.0;

		private readonly double _qualityWeight = 1.0;

		private static double Checked(double value, string name)
		{
			if (!(value >= 0.0) || double.IsInfinity(value))
			{
				throw new ArgumentOutOfRangeException(name, value, "Must be a finite number, zero or more.");
			}
			return value;
		}

		[CompilerGenerated]
		private bool PrintMembers(StringBuilder builder)
		{
			RuntimeHelpers.EnsureSufficientExecutionStack();
			builder.Append("SetWeight = ");
			builder.Append(SetWeight.ToString());
			builder.Append(", TierWeight = ");
			builder.Append(TierWeight.ToString());
			builder.Append(", QualityWeight = ");
			builder.Append(QualityWeight.ToString());
			builder.Append(", ProtectHotbar = ");
			builder.Append(ProtectHotbar.ToString());
			return true;
		}

		[CompilerGenerated]
		public override int GetHashCode()
		{
			return (((EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<double>.Default.GetHashCode(_setWeight)) * -1521134295 + EqualityComparer<double>.Default.GetHashCode(_tierWeight)) * -1521134295 + EqualityComparer<double>.Default.GetHashCode(_qualityWeight)) * -1521134295 + EqualityComparer<bool>.Default.GetHashCode(ProtectHotbar);
		}

		[CompilerGenerated]
		public bool Equals(ArmorFillSettings? other)
		{
			if ((object)this != other)
			{
				if ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<double>.Default.Equals(_setWeight, other._setWeight) && EqualityComparer<double>.Default.Equals(_tierWeight, other._tierWeight) && EqualityComparer<double>.Default.Equals(_qualityWeight, other._qualityWeight))
				{
					return EqualityComparer<bool>.Default.Equals(ProtectHotbar, other.ProtectHotbar);
				}
				return false;
			}
			return true;
		}

		[CompilerGenerated]
		private ArmorFillSettings(ArmorFillSettings original)
		{
			_setWeight = original._setWeight;
			_tierWeight = original._tierWeight;
			_qualityWeight = original._qualityWeight;
			ProtectHotbar = original.ProtectHotbar;
		}

		public ArmorFillSettings()
		{
		}
	}
	public sealed class ArmorFillPlan
	{
		public string? AnchorSet { get; }

		public int AnchorTier { get; }

		public int ReferenceQuality { get; }

		public IReadOnlyList<KeyValuePair<ArmorSlot, ArmorPiece>> Choices { get; }

		internal ArmorFillPlan(string? anchorSet, int anchorTier, int referenceQuality, IReadOnlyList<KeyValuePair<ArmorSlot, ArmorPiece>> choices)
		{
			AnchorSet = anchorSet;
			AnchorTier = anchorTier;
			ReferenceQuality = referenceQuality;
			Choices = choices;
		}
	}
	public sealed class ArmorFillPlanner
	{
		private sealed class Candidate
		{
			public ArmorPiece Piece { get; }

			public ArmorIdentity Identity { get; }

			public int SourceRank { get; }

			public Candidate(ArmorPiece piece, ArmorIdentity identity, int sourceRank)
			{
				Piece = piece;
				Identity = identity;
				SourceRank = sourceRank;
			}
		}

		private readonly ArmorSetCatalog _catalog;

		private readonly ArmorFillSettings _settings;

		public ArmorFillPlanner(ArmorSetCatalog? catalog = null, ArmorFillSettings? settings = null)
		{
			_catalog = catalog ?? ArmorSetCatalog.Vanilla;
			_settings = settings ?? ArmorFillSettings.Default;
		}

		public ArmorFillPlan Plan(IEnumerable<ArmorPiece?>? candidates, IReadOnlyList<string>? containerOrder, IEnumerable<ArmorSlot>? freeSlots, int inventoryWidth)
		{
			Dictionary<string, int> rankOf = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (string item in containerOrder ?? Array.Empty<string>())
			{
				if (item != null && !rankOf.ContainsKey(item))
				{
					rankOf.Add(item, rankOf.Count + 1);
				}
			}
			SortedSet<ArmorSlot> slots = new SortedSet<ArmorSlot>((freeSlots ?? Array.Empty<ArmorSlot>()).Where((ArmorSlot s) => Enum.IsDefined(typeof(ArmorSlot), s)));
			List<Candidate> list = (from piece in Deduplication.ById(candidates, IdentityOf)
				where slots.Contains(piece.SlotType) && IsSource(piece, rankOf, inventoryWidth)
				select new Candidate(piece, _catalog.Resolve(piece), (piece.ContainerId != null) ? rankOf[piece.ContainerId] : 0)).ToList();
			if (list.Count == 0)
			{
				return new ArmorFillPlan(null, 0, 0, Array.Empty<KeyValuePair<ArmorSlot, ArmorPiece>>());
			}
			var anchor = (from s in list.GroupBy<Candidate, string>((Candidate c) => c.Identity.SetKey, StringComparer.Ordinal).Select(delegate(IGrouping<string, Candidate> set)
				{
					int[] array = (from c in set
						group c by c.Piece.SlotType into source
						select source.Max((Candidate c) => c.Piece.Quality)).ToArray();
					return new
					{
						Set = set.Key,
						Coverage = array.Length,
						Tier = set.Max((Candidate c) => c.Identity.Tier),
						MeanQuality = array.Average(),
						BestPerSlot = array
					};
				})
				orderby s.Coverage descending, s.Tier descending, s.MeanQuality descending
				select s).ThenBy(s => s.Set, StringComparer.Ordinal).First();
			int reference = LowerMedian(anchor.BestPerSlot);
			List<KeyValuePair<ArmorSlot, ArmorPiece>> list2 = new List<KeyValuePair<ArmorSlot, ArmorPiece>>();
			foreach (ArmorSlot slot in slots)
			{
				Candidate candidate = (from c in list
					where c.Piece.SlotType == slot
					orderby Distance(c, anchor.Set, anchor.Tier, reference), c.SourceRank, c.Piece.Slot
					select c).FirstOrDefault();
				if (candidate != null)
				{
					list2.Add(new KeyValuePair<ArmorSlot, ArmorPiece>(slot, candidate.Piece));
				}
			}
			return new ArmorFillPlan(anchor.Set, anchor.Tier, reference, list2);
		}

		private static string IdentityOf(ArmorPiece piece)
		{
			return (piece.ContainerId ?? string.Empty) + "#" + piece.Slot.ToString(CultureInfo.InvariantCulture);
		}

		private bool IsSource(ArmorPiece piece, Dictionary<string, int> rankOf, int inventoryWidth)
		{
			if (string.IsNullOrEmpty(piece.PrefabName) || piece.IsEquipped)
			{
				return false;
			}
			if (piece.ContainerId == null)
			{
				return !InventoryProtection.IsProtected(new ItemStack(piece.ItemId, piece.Quality, 1, piece.IsEquipped, piece.Slot), inventoryWidth, _settings.ProtectHotbar);
			}
			return rankOf.ContainsKey(piece.ContainerId);
		}

		private double Distance(Candidate candidate, string anchorSet, int anchorTier, int reference)
		{
			return _settings.SetWeight * (double)((!(candidate.Identity.SetKey == anchorSet)) ? 1 : 0) + _settings.TierWeight * (double)Math.Abs(candidate.Identity.Tier - anchorTier) + _settings.QualityWeight * (double)Math.Abs(candidate.Piece.Quality - reference);
		}

		private static int LowerMedian(int[] values)
		{
			int[] array = values.OrderBy((int v) => v).ToArray();
			return array[(array.Length - 1) / 2];
		}
	}
	public enum ArmorSlot
	{
		Helmet,
		Chest,
		Legs,
		Cape,
		Belt
	}
	public sealed record ArmorPiece(string? ContainerId, int Slot, string PrefabName, string ItemId, int Quality, ArmorSlot SlotType, string? SetName = null, double Armor = 0.0, bool IsEquipped = false);
	public readonly record struct ArmorIdentity(string SetKey, int Tier, bool IsKnown);
	public sealed record ArmorSetEntry(string Name, int Tier, IReadOnlyList<string> Prefabs);
	public sealed class ArmorSetCatalog
	{
		public const string VanillaTable = "rags:1:ArmorRagsChest,ArmorRagsLegs;leather:1:ArmorLeatherChest,ArmorLeatherLegs,HelmetLeather,CapeDeerHide;troll:2:ArmorTrollLeatherChest,ArmorTrollLeatherLegs,HelmetTrollLeather,CapeTrollHide;bronze:2:ArmorBronzeChest,ArmorBronzeLegs,HelmetBronze;root:3:ArmorRootChest,ArmorRootLegs,HelmetRoot;iron:3:ArmorIronChest,ArmorIronLegs,HelmetIron;wolf:4:ArmorWolfChest,ArmorWolfLegs,HelmetDrake,CapeWolf;fenris:4:ArmorFenringChest,ArmorFenringLegs,HelmetFenring;padded:5:ArmorPaddedCuirass,ArmorPaddedGreaves,HelmetPadded;carapace:6:ArmorCarapaceChest,ArmorCarapaceLegs,HelmetCarapace,CapeFeather;mage:6:ArmorMageChest,ArmorMageLegs,HelmetMage;flametal:7:ArmorFlametalChest,ArmorFlametalLegs;askvin:7:ArmorAshlandsMediumChest,ArmorAshlandsMediumlegs,HelmetAshlandsMediumHood,CapeAsksvin;deepnorthheavy:8:ArmorDeepNorthHeavyChest,ArmorDeepNorthHeavylegs,HelmetDNHeavy;deepnorthmedium:8:ArmorDeepNorthMediumChest,ArmorDeepNorthMediumlegs,HelmetDNMedium;deepnorthmage:8:ArmorDeepNorthMageChest,ArmorDeepNorthMagelegs,HelmetDNMage";

		public const double ArmorPerTier = 6.0;

		public const int MaxGuessedTier = 9;

		private readonly Dictionary<string, ArmorSetEntry> _byPrefab = new Dictionary<string, ArmorSetEntry>(StringComparer.Ordinal);

		private readonly Dictionary<string, int> _tierBySet = new Dictionary<string, int>(StringComparer.Ordinal);

		public static ArmorSetCatalog Vanilla { get; } = Parse("rags:1:ArmorRagsChest,ArmorRagsLegs;leather:1:ArmorLeatherChest,ArmorLeatherLegs,HelmetLeather,CapeDeerHide;troll:2:ArmorTrollLeatherChest,ArmorTrollLeatherLegs,HelmetTrollLeather,CapeTrollHide;bronze:2:ArmorBronzeChest,ArmorBronzeLegs,HelmetBronze;root:3:ArmorRootChest,ArmorRootLegs,HelmetRoot;iron:3:ArmorIronChest,ArmorIronLegs,HelmetIron;wolf:4:ArmorWolfChest,ArmorWolfLegs,HelmetDrake,CapeWolf;fenris:4:ArmorFenringChest,ArmorFenringLegs,HelmetFenring;padded:5:ArmorPaddedCuirass,ArmorPaddedGreaves,HelmetPadded;carapace:6:ArmorCarapaceChest,ArmorCarapaceLegs,HelmetCarapace,CapeFeather;mage:6:ArmorMageChest,ArmorMageLegs,HelmetMage;flametal:7:ArmorFlametalChest,ArmorFlametalLegs;askvin:7:ArmorAshlandsMediumChest,ArmorAshlandsMediumlegs,HelmetAshlandsMediumHood,CapeAsksvin;deepnorthheavy:8:ArmorDeepNorthHeavyChest,ArmorDeepNorthHeavylegs,HelmetDNHeavy;deepnorthmedium:8:ArmorDeepNorthMediumChest,ArmorDeepNorthMediumlegs,HelmetDNMedium;deepnorthmage:8:ArmorDeepNorthMageChest,ArmorDeepNorthMagelegs,HelmetDNMage");

		public IReadOnlyList<ArmorSetEntry> Sets { get; }

		public ArmorSetCatalog(IEnumerable<ArmorSetEntry> sets)
		{
			Sets = (sets ?? Array.Empty<ArmorSetEntry>()).Where((ArmorSetEntry set) => (object)set != null).ToArray();
			foreach (ArmorSetEntry set in Sets)
			{
				_tierBySet[set.Name] = set.Tier;
				foreach (string prefab in set.Prefabs)
				{
					if (!_byPrefab.ContainsKey(prefab))
					{
						_byPrefab.Add(prefab, set);
					}
				}
			}
		}

		public static int GuessTier(double armor)
		{
			if (double.IsNaN(armor) || armor <= 0.0)
			{
				return 1;
			}
			return (int)Math.Min(9.0, 1.0 + Math.Floor(armor / 6.0));
		}

		public static ArmorSetCatalog Parse(string text)
		{
			if (!TryParse(text, out ArmorSetCatalog catalog, out string error))
			{
				throw new FormatException(error);
			}
			return catalog;
		}

		public static bool TryParse(string? text, out ArmorSetCatalog catalog, out string error)
		{
			catalog = new ArmorSetCatalog(Array.Empty<ArmorSetEntry>());
			List<ArmorSetEntry> list = new List<ArmorSetEntry>();
			string[] array = (text ?? string.Empty).Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0)
				{
					string[] array2 = text2.Split(':');
					if (array2.Length != 3 || array2[0].Trim().Length == 0 || !int.TryParse(array2[1].Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 1)
					{
						error = "Malformed set entry '" + text2 + "' (expected set:tier:prefab,prefab with tier >= 1).";
						return false;
					}
					string[] array3 = (from p in array2[2].Split(',')
						select p.Trim() into p
						where p.Length > 0
						select p).ToArray();
					if (array3.Length == 0)
					{
						error = "Set '" + array2[0].Trim() + "' has no prefab.";
						return false;
					}
					list.Add(new ArmorSetEntry(array2[0].Trim(), result, array3));
				}
			}
			catalog = new ArmorSetCatalog(list);
			error = string.Empty;
			return true;
		}

		public bool Knows(string prefabName)
		{
			return _byPrefab.ContainsKey(prefabName);
		}

		public ArmorIdentity Resolve(ArmorPiece piece)
		{
			_byPrefab.TryGetValue(piece.PrefabName, out ArmorSetEntry value);
			string text = ((!string.IsNullOrEmpty(piece.SetName)) ? piece.SetName : value?.Name);
			if (text == null)
			{
				return new ArmorIdentity("?" + piece.PrefabName, GuessTier(piece.Armor), IsKnown: false);
			}
			int value2;
			int tier = (_tierBySet.TryGetValue(text, out value2) ? value2 : (value?.Tier ?? GuessTier(piece.Armor)));
			return new ArmorIdentity(text, tier, IsKnown: true);
		}
	}
	public sealed class ArmorSwapPlan
	{
		public IReadOnlyList<KeyValuePair<ArmorSlot, ArmorPiece>> ToPlayer { get; }

		public IReadOnlyList<KeyValuePair<ArmorSlot, ArmorPiece>> ToStand { get; }

		public int WeaponsToInventory { get; }

		public int RequiredFreeSlots { get; }

		public bool IsFeasible { get; }

		internal ArmorSwapPlan(IReadOnlyList<KeyValuePair<ArmorSlot, ArmorPiece>> toPlayer, IReadOnlyList<KeyValuePair<ArmorSlot, ArmorPiece>> toStand, int weaponsToInventory, int requiredFreeSlots, int freeSlots)
		{
			ToPlayer = toPlayer;
			ToStand = toStand;
			WeaponsToInventory = weaponsToInventory;
			RequiredFreeSlots = requiredFreeSlots;
			IsFeasible = freeSlots >= requiredFreeSlots;
		}
	}
	public sealed class ArmorSwapPlanner
	{
		public ArmorSwapPlan Plan(IReadOnlyDictionary<ArmorSlot, ArmorPiece>? stand, IReadOnlyDictionary<ArmorSlot, ArmorPiece>? worn, int standWeapons, int freeInventorySlots, IEnumerable<ArmorSlot>? standSlots)
		{
			SortedSet<ArmorSlot> supported = new SortedSet<ArmorSlot>((standSlots ?? Array.Empty<ArmorSlot>()).Where((ArmorSlot s) => Enum.IsDefined(typeof(ArmorSlot), s)));
			List<KeyValuePair<ArmorSlot, ArmorPiece>> list = Pick(stand, supported);
			List<KeyValuePair<ArmorSlot, ArmorPiece>> list2 = Pick(worn, supported);
			int num = Math.Max(0, standWeapons);
			int requiredFreeSlots = Math.Max(0, list.Count - list2.Count) + num;
			return new ArmorSwapPlan(list, list2, num, requiredFreeSlots, freeInventorySlots);
		}

		private static List<KeyValuePair<ArmorSlot, ArmorPiece>> Pick(IReadOnlyDictionary<ArmorSlot, ArmorPiece>? pieces, SortedSet<ArmorSlot> supported)
		{
			ArmorPiece value;
			return (from slot in supported
				where pieces != null && pieces.TryGetValue(slot, out value) && (object)value != null
				select new KeyValuePair<ArmorSlot, ArmorPiece>(slot, pieces[slot])).ToList();
		}
	}
}