Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ChestNetwork v0.4.0
plugins/ChestNetwork.Plugin.dll
Decompiled 13 hours ago
The result has been truncated due to the large size, download it to view full contents!
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 agousing 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(); } } }