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 VikingCraftingAndQuickDeposit v0.0.4
BepInEx/plugins/VikingCraftingAndQuickDeposit/VikingCraftingAndQuickDeposit.dll
Decompiled 15 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Splatform; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace VikingCraftingQuickDeposit; public static class StorageDefaults { public const float DepositRadiusMetres = 250f; public const double DepositCooldownSeconds = 3.0; public const float MaxSerializableDurability = 21474830f; } public enum DepositAdmission { Accepted, Cooldown, Pending } public sealed class DepositGate { private sealed class Entry { public string Operation; public double NextAllowed; } private readonly Dictionary<string, Entry> entries = new Dictionary<string, Entry>(StringComparer.Ordinal); public DepositAdmission TryBegin(string player, string operation, double monotonicSeconds) { if (string.IsNullOrEmpty(player) || string.IsNullOrEmpty(operation) || double.IsNaN(monotonicSeconds) || double.IsInfinity(monotonicSeconds) || monotonicSeconds < 0.0) { throw new ArgumentException("Invalid deposit admission"); } if (entries.TryGetValue(player, out var value)) { if (value.Operation != null) { return DepositAdmission.Pending; } if (monotonicSeconds < value.NextAllowed) { return DepositAdmission.Cooldown; } } else { value = new Entry(); entries.Add(player, value); } value.Operation = operation; value.NextAllowed = monotonicSeconds + 3.0; return DepositAdmission.Accepted; } public bool Complete(string player, string operation) { if (player == null || string.IsNullOrEmpty(operation) || !entries.TryGetValue(player, out var value) || value.Operation != operation) { return false; } value.Operation = null; return true; } public void PruneCompleted(double monotonicSeconds) { if (double.IsNaN(monotonicSeconds) || double.IsInfinity(monotonicSeconds) || monotonicSeconds < 0.0) { throw new ArgumentException(); } foreach (string item in (from p in entries where p.Value.Operation == null && p.Value.NextAllowed <= monotonicSeconds select p.Key).ToList()) { entries.Remove(item); } } } public enum MagicState { Normal, Enchanted, Unknown } public sealed class ItemRecord { private readonly byte[] payload; public readonly string Prefab; public readonly string Identity; public readonly int MaxStack; public readonly int Quality; public readonly bool Equipment; public readonly bool HasCustomData; public readonly bool QuestItem; public readonly float? ExactDurability; public readonly MagicState Magic; public ItemRecord(string prefab, string identity, int maxStack, byte[] nativePayload, bool equipment, bool hasCustomData, MagicState magic, bool questItem = false, float? exactDurability = null, int quality = 1) { if (string.IsNullOrEmpty(prefab) || string.IsNullOrEmpty(identity) || maxStack < 1 || nativePayload == null || nativePayload.Length == 0) { throw new ArgumentException("Invalid item snapshot"); } if (quality < 1 || quality > 65535) { throw new ArgumentException("Invalid quality"); } Prefab = prefab; Identity = identity; MaxStack = maxStack; Quality = quality; payload = (byte[])nativePayload.Clone(); if (exactDurability.HasValue && (float.IsNaN(exactDurability.Value) || float.IsInfinity(exactDurability.Value) || exactDurability.Value < 0f || exactDurability.Value > 21474830f)) { throw new ArgumentException("Invalid durability"); } ExactDurability = exactDurability; Equipment = equipment; HasCustomData = hasCustomData; Magic = magic; QuestItem = questItem; } public byte[] Payload() { return (byte[])payload.Clone(); } public bool StacksWith(ItemRecord other) { return other != null && MaxStack > 1 && MaxStack == other.MaxStack && Prefab == other.Prefab && Identity == other.Identity; } } public sealed class StackRecord { public readonly ItemRecord Item; public readonly int Slot; public readonly int Count; public readonly int Reserve; public readonly bool Equipped; public readonly bool Locked; public readonly bool Hotbar; public int Available => Math.Max(0, Count - Reserve); public StackRecord(ItemRecord item, int slot, int count, int reserve = 0, bool equipped = false, bool locked = false, bool hotbar = false) { if (item == null || slot < 0 || count < 1 || count > item.MaxStack || reserve < 0) { throw new ArgumentException("Invalid stack"); } Item = item; Slot = slot; Count = count; Reserve = reserve; Equipped = equipped; Locked = locked; Hotbar = hotbar; } } public sealed class InventoryRecord { public readonly string Id; public readonly long Revision; public readonly int Capacity; public readonly bool QuickDepositEnabled; public readonly bool CraftEnabled; public readonly bool BuildEnabled; public readonly int Priority; public readonly ReadOnlyCollection<int> ReservedSlots; public readonly ReadOnlyCollection<StackRecord> Stacks; public InventoryRecord(string id, long revision, int capacity, IEnumerable<StackRecord> stacks, bool quickDepositEnabled = true, bool craftEnabled = true, bool buildEnabled = true, int priority = 0, IEnumerable<int> reservedSlots = null) { if (string.IsNullOrEmpty(id) || revision < 0 || capacity < 1 || stacks == null) { throw new ArgumentException("Invalid inventory"); } List<StackRecord> list = stacks.ToList(); if (list.Any((StackRecord x) => x == null || x.Slot >= capacity) || list.Select((StackRecord x) => x.Slot).Distinct().Count() != list.Count) { throw new ArgumentException("Invalid inventory slots"); } List<int> list2 = (from s in (reservedSlots ?? Enumerable.Empty<int>()).Distinct() orderby s select s).ToList(); if (priority < -10 || priority > 10 || list2.Any((int s) => s < 0 || s >= capacity)) { throw new ArgumentException("Invalid inventory rules"); } Id = id; Revision = revision; Capacity = capacity; Stacks = list.AsReadOnly(); QuickDepositEnabled = quickDepositEnabled; CraftEnabled = craftEnabled; BuildEnabled = buildEnabled; Priority = priority; ReservedSlots = list2.AsReadOnly(); } } public sealed class DepositPolicy { public bool ProtectHotbar = true; public bool AllowEquipment = false; public bool AllowEnchanted = false; public bool MatchExistingPrefab = true; public readonly HashSet<string> Blacklist = new HashSet<string>(StringComparer.Ordinal); public readonly Dictionary<string, int> KeepCounts = new Dictionary<string, int>(StringComparer.Ordinal); public string Denial(StackRecord stack) { if (stack.Item.QuestItem) { return "quest item"; } if (stack.Equipped) { return "equipped"; } if (stack.Locked) { return "locked"; } if (ProtectHotbar && stack.Hotbar) { return "hotbar"; } if (Blacklist.Contains(stack.Item.Prefab)) { return "blacklisted"; } if (stack.Item.Magic == MagicState.Unknown) { return "unrecognized enchantment state"; } if (stack.Item.Magic == MagicState.Enchanted && !AllowEnchanted) { return "enchanted item protected"; } if (stack.Item.Equipment && !AllowEquipment) { return "equipment protected"; } if (stack.Available == 0) { return "carried reserve"; } return null; } } public sealed class TransferLine { public readonly string Source; public readonly string Destination; public readonly long SourceRevision; public readonly long DestinationRevision; public readonly int SourceSlot; public readonly int DestinationSlot; public readonly int Count; public readonly ItemRecord Item; public TransferLine(InventoryRecord source, InventoryRecord destination, StackRecord item, int targetSlot, int count) { Source = source.Id; Destination = destination.Id; SourceRevision = source.Revision; DestinationRevision = destination.Revision; SourceSlot = item.Slot; DestinationSlot = targetSlot; Count = count; Item = item.Item; } } public sealed class DepositResult { public readonly ReadOnlyCollection<TransferLine> Lines; public readonly ReadOnlyCollection<string> Skips; internal DepositResult(List<TransferLine> lines, List<string> skips) { Lines = lines.AsReadOnly(); Skips = skips.AsReadOnly(); } } public static class DepositPlanner { private sealed class Working { public InventoryRecord Inventory; public Dictionary<int, ItemRecord> Items = new Dictionary<int, ItemRecord>(); public Dictionary<int, int> Counts = new Dictionary<int, int>(); } public static DepositResult Plan(InventoryRecord source, IEnumerable<InventoryRecord> destinations, DepositPolicy policy) { if (source == null || destinations == null || policy == null) { throw new ArgumentNullException(); } List<InventoryRecord> list = destinations.ToList(); if (list.Any((InventoryRecord b) => b == null || b.Id == source.Id) || list.Select((InventoryRecord b) => b.Id).Distinct().Count() != list.Count) { throw new ArgumentException("Duplicate/source inventory in destinations"); } list = list.OrderByDescending((InventoryRecord b) => b.Priority).ToList(); List<Working> list2 = list.Select((InventoryRecord b) => new Working { Inventory = b, Items = b.Stacks.ToDictionary((StackRecord s) => s.Slot, (StackRecord s) => s.Item), Counts = b.Stacks.ToDictionary((StackRecord s) => s.Slot, (StackRecord s) => s.Count) }).ToList(); List<TransferLine> list3 = new List<TransferLine>(); List<string> list4 = new List<string>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); foreach (KeyValuePair<string, int> target in policy.KeepCounts) { KeyValuePair<string, int> keyValuePair = target; if (keyValuePair.Value < 0) { throw new ArgumentException("Negative carried target"); } long num = source.Stacks.Where(delegate(StackRecord s) { string prefab = s.Item.Prefab; KeyValuePair<string, int> keyValuePair2 = target; return prefab == keyValuePair2.Key; }).Sum((Func<StackRecord, long>)((StackRecord s) => (policy.Denial(s) != null) ? s.Count : Math.Min(s.Count, s.Reserve))); keyValuePair = target; string key = keyValuePair.Key; long val = 0L; keyValuePair = target; dictionary[key] = (int)Math.Max(val, keyValuePair.Value - num); } foreach (StackRecord stack in source.Stacks.OrderBy((StackRecord s) => s.Slot)) { string text = policy.Denial(stack); if (text != null) { list4.Add(stack.Slot + ": " + text); continue; } int num2 = stack.Available; if (dictionary.TryGetValue(stack.Item.Prefab, out var value)) { int num3 = Math.Min(num2, value); num2 -= num3; dictionary[stack.Item.Prefab] -= num3; } foreach (Working item in list2) { if (!item.Inventory.QuickDepositEnabled || (policy.MatchExistingPrefab && !item.Inventory.Stacks.Any((StackRecord s) => s.Item.Prefab == stack.Item.Prefab))) { continue; } for (int num4 = 0; num4 < item.Inventory.Capacity; num4++) { if (num2 <= 0) { break; } if (item.Items.TryGetValue(num4, out var value2) && stack.Item.StacksWith(value2)) { int num5 = Math.Min(num2, value2.MaxStack - item.Counts[num4]); if (num5 != 0) { list3.Add(new TransferLine(source, item.Inventory, stack, num4, num5)); item.Counts[num4] += num5; num2 -= num5; } } } for (int num4 = 0; num4 < item.Inventory.Capacity; num4++) { if (num2 <= 0) { break; } if (!item.Items.ContainsKey(num4) && !item.Inventory.ReservedSlots.Contains(num4)) { int num5 = Math.Min(num2, stack.Item.MaxStack); list3.Add(new TransferLine(source, item.Inventory, stack, num4, num5)); item.Items.Add(num4, stack.Item); item.Counts.Add(num4, num5); num2 -= num5; } } if (num2 != 0) { continue; } break; } if (num2 > 0) { list4.Add(stack.Slot + ": " + num2 + " not placed (no matching capacity)"); } } return new DepositResult(list3, list4); } } public sealed class Ingredient { public readonly string Prefab; public readonly int Count; public readonly int Quality; public Ingredient(string prefab, int count, int quality = -1) { if (string.IsNullOrEmpty(prefab) || count <= 0 || quality == 0 || quality < -1 || quality > 65535) { throw new ArgumentException(); } Prefab = prefab; Count = count; Quality = quality; } } public sealed class ConsumptionLine { public readonly InventoryRecord Inventory; public readonly StackRecord Stack; public readonly int Count; public ConsumptionLine(InventoryRecord inventory, StackRecord stack, int count) { Inventory = inventory; Stack = stack; Count = count; } } public sealed class CraftResult { public readonly bool Satisfied; public readonly ReadOnlyCollection<ConsumptionLine> Lines; public readonly ReadOnlyCollection<Ingredient> Missing; internal CraftResult(List<ConsumptionLine> lines, List<Ingredient> missing) { Satisfied = missing.Count == 0; Lines = (Satisfied ? lines : new List<ConsumptionLine>()).AsReadOnly(); Missing = missing.AsReadOnly(); } } public static class CraftPlanner { public static bool CanConsume(StackRecord s) { return !s.Equipped && !s.Locked && !s.Item.QuestItem && !s.Item.Equipment && s.Item.Magic == MagicState.Normal && !s.Item.HasCustomData; } public static CraftResult Plan(IEnumerable<InventoryRecord> inventories, IEnumerable<Ingredient> requirements, bool building = false) { if (inventories == null || requirements == null) { throw new ArgumentNullException(); } List<InventoryRecord> list = inventories.ToList(); List<Ingredient> source = requirements.ToList(); if (list.Any((InventoryRecord s) => s == null) || list.Select((InventoryRecord s) => s.Id).Distinct().Count() != list.Count || source.Any((Ingredient s) => s == null)) { throw new ArgumentException(); } list = list.Where((InventoryRecord s) => building ? s.BuildEnabled : s.CraftEnabled).ToList(); List<ConsumptionLine> list2 = new List<ConsumptionLine>(); List<Ingredient> list3 = new List<Ingredient>(); foreach (var item in from r in source group r by new { r.Prefab, r.Quality } into g orderby (g.Key.Quality < 0) ? 1 : 0 select g) { int num = item.Sum((Ingredient r) => r.Count); foreach (InventoryRecord inv in list) { foreach (StackRecord stack in inv.Stacks.OrderBy((StackRecord s) => s.Slot)) { if (num != 0 && !(stack.Item.Prefab != item.Key.Prefab) && (item.Key.Quality <= 0 || stack.Item.Quality == item.Key.Quality) && CanConsume(stack)) { int num2 = list2.Where((ConsumptionLine l) => l.Inventory.Id == inv.Id && l.Stack.Slot == stack.Slot).Sum((ConsumptionLine l) => l.Count); int num3 = Math.Min(num, stack.Available - num2); if (num3 > 0) { list2.Add(new ConsumptionLine(inv, stack, num3)); num -= num3; } } } } if (num > 0) { list3.Add(new Ingredient(item.Key.Prefab, num, item.Key.Quality)); } } return new CraftResult(list2, list3); } } public sealed class SupplyRule { public readonly string Prefab; public readonly int Target; public readonly int MaxStack; public readonly bool Blacklisted; public SupplyRule(string prefab, int target, int maxStack, bool blacklisted) { if (string.IsNullOrEmpty(prefab) || maxStack < 1 || target < 0 || target > maxStack) { throw new ArgumentException("Invalid supply rule"); } Prefab = prefab; Target = target; MaxStack = maxStack; Blacklisted = blacklisted; } } public sealed class SupplyPreferences { private readonly Dictionary<string, SupplyRule> rules = new Dictionary<string, SupplyRule>(StringComparer.Ordinal); public IEnumerable<SupplyRule> Rules => rules.Values.OrderBy<SupplyRule, string>((SupplyRule r) => r.Prefab, StringComparer.Ordinal); public SupplyRule Get(string prefab, int currentMax) { SupplyRule value; return rules.TryGetValue(prefab, out value) ? new SupplyRule(prefab, Math.Min(value.Target, currentMax), currentMax, value.Blacklisted) : new SupplyRule(prefab, 0, currentMax, blacklisted: false); } public void Set(SupplyRule rule) { if (rule == null) { throw new ArgumentNullException(); } if (rule.Target == 0 && !rule.Blacklisted) { rules.Remove(rule.Prefab); } else { rules[rule.Prefab] = rule; } } public void Apply(DepositPolicy policy) { foreach (SupplyRule rule in Rules) { if (rule.Blacklisted) { policy.Blacklist.Add(rule.Prefab); } if (rule.Target > 0) { policy.KeepCounts[rule.Prefab] = rule.Target; } } } public string Encode() { return string.Join(";", Rules.Select(delegate(SupplyRule r) { string[] array = new string[7] { Convert.ToBase64String(Encoding.UTF8.GetBytes(r.Prefab)), ",", null, null, null, null, null }; int target = r.Target; array[2] = target.ToString(CultureInfo.InvariantCulture); array[3] = ","; target = r.MaxStack; array[4] = target.ToString(CultureInfo.InvariantCulture); array[5] = ","; array[6] = (r.Blacklisted ? "1" : "0"); return string.Concat(array); }).ToArray()); } public static SupplyPreferences Decode(string value) { SupplyPreferences supplyPreferences = new SupplyPreferences(); if (string.IsNullOrEmpty(value)) { return supplyPreferences; } string[] array = value.Split(new char[1] { ';' }); foreach (string text in array) { string[] array2 = text.Split(new char[1] { ',' }); if (array2.Length != 4 || (array2[3] != "0" && array2[3] != "1")) { throw new FormatException("Invalid saved supply preferences"); } string text2 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(Convert.FromBase64String(array2[0])); if (supplyPreferences.rules.ContainsKey(text2)) { throw new FormatException("Duplicate supply rule"); } supplyPreferences.Set(new SupplyRule(text2, int.Parse(array2[1], CultureInfo.InvariantCulture), int.Parse(array2[2], CultureInfo.InvariantCulture), array2[3] == "1")); } return supplyPreferences; } } public sealed class RestockResult { public readonly ReadOnlyCollection<TransferLine> Lines; public readonly ReadOnlyCollection<Ingredient> Missing; internal RestockResult(List<TransferLine> lines, List<Ingredient> missing) { Lines = lines.AsReadOnly(); Missing = missing.AsReadOnly(); } } public static class RestockPlanner { public static RestockResult Plan(InventoryRecord player, IEnumerable<InventoryRecord> chests, IEnumerable<SupplyRule> rules) { if (player == null || chests == null || rules == null) { throw new ArgumentNullException(); } List<InventoryRecord> list = chests.ToList(); List<SupplyRule> list2 = rules.ToList(); if (list.Any((InventoryRecord s) => s == null || s.Id == player.Id) || list.Select((InventoryRecord s) => s.Id).Distinct().Count() != list.Count || list2.Any((SupplyRule t) => t == null) || list2.Select((SupplyRule t) => t.Prefab).Distinct().Count() != list2.Count) { throw new ArgumentException("Duplicate/invalid restock input"); } list = list.OrderByDescending((InventoryRecord s) => s.Priority).ToList(); List<StackRecord> list3 = player.Stacks.ToList(); List<TransferLine> list4 = new List<TransferLine>(); List<Ingredient> list5 = new List<Ingredient>(); foreach (SupplyRule target in list2) { Func<StackRecord, bool> predicate = (StackRecord s) => s.Item.Prefab == target.Prefab; long num = list3.Where(predicate).Sum((Func<StackRecord, long>)((StackRecord s) => s.Count)); List<int> list6 = (from s in list3.Concat(list.SelectMany((InventoryRecord s) => s.Stacks)) where s.Item.Prefab == target.Prefab select s.Item.MaxStack).ToList(); int num2 = ((list6.Count == 0) ? target.Target : Math.Min(target.Target, list6.Min())); int num3 = (int)Math.Max(0L, num2 - num); foreach (InventoryRecord item in list) { foreach (StackRecord item2 in item.Stacks.OrderBy((StackRecord s) => s.Slot)) { if (num3 == 0 || item2.Item.Prefab != target.Prefab || !CraftPlanner.CanConsume(item2) || item2.Available == 0) { continue; } int val = Math.Min(Math.Min(num3, item2.Available), item2.Item.MaxStack); int val2 = (int)Math.Max(0L, Math.Min(target.Target, item2.Item.MaxStack) - num); val = Math.Min(val, val2); if (val == 0) { continue; } InventoryRecord source = new InventoryRecord(item.Id, item.Revision, item.Capacity, new StackRecord[1] { new StackRecord(item2.Item, item2.Slot, val) }); InventoryRecord inventoryRecord = new InventoryRecord(player.Id, player.Revision, player.Capacity, list3, quickDepositEnabled: true, craftEnabled: true, buildEnabled: true, 0, player.ReservedSlots); DepositResult depositResult = DepositPlanner.Plan(source, new InventoryRecord[1] { inventoryRecord }, new DepositPolicy { MatchExistingPrefab = false }); foreach (TransferLine move in depositResult.Lines) { Func<StackRecord, bool> predicate2 = (StackRecord s) => s.Slot == move.DestinationSlot; StackRecord stackRecord = list3.FirstOrDefault(predicate2); if (stackRecord != null) { list3.Remove(stackRecord); } list3.Add(new StackRecord((stackRecord == null) ? move.Item : stackRecord.Item, move.DestinationSlot, move.Count + (stackRecord?.Count ?? 0), stackRecord?.Reserve ?? 0, stackRecord?.Equipped ?? false, stackRecord?.Locked ?? false, stackRecord?.Hotbar ?? false)); list4.Add(move); num3 -= move.Count; num += move.Count; } } } if (num3 > 0) { list5.Add(new Ingredient(target.Prefab, num3)); } } return new RestockResult(list4, list5); } } public sealed class TransferBatch { public readonly ReadOnlyCollection<InventoryRecord> Before; public readonly ReadOnlyCollection<InventoryRecord> After; public readonly ReadOnlyCollection<TransferLine> Lines; public readonly int ItemCount; public string Purpose; private TransferBatch(List<InventoryRecord> before, List<InventoryRecord> after, List<TransferLine> lines) { Before = before.AsReadOnly(); After = after.AsReadOnly(); Lines = lines.AsReadOnly(); ItemCount = lines.Sum((TransferLine l) => l.Count); } private static bool Same(ItemRecord a, ItemRecord b) { return a.Prefab == b.Prefab && a.Identity == b.Identity && a.MaxStack == b.MaxStack; } public static TransferBatch Prepare(IEnumerable<InventoryRecord> inventories, IEnumerable<TransferLine> transfers) { if (inventories == null || transfers == null) { throw new ArgumentNullException(); } List<InventoryRecord> list = inventories.ToList(); List<TransferLine> list2 = transfers.ToList(); if (list.Any((InventoryRecord i) => i == null) || list.Select((InventoryRecord i) => i.Id).Distinct().Count() != list.Count || list2.Any((TransferLine l) => l == null)) { throw new ArgumentException("Invalid batch"); } Dictionary<string, InventoryRecord> dictionary = list.ToDictionary<InventoryRecord, string>((InventoryRecord i) => i.Id, StringComparer.Ordinal); Dictionary<string, Dictionary<int, int>> remaining = list.ToDictionary<InventoryRecord, string, Dictionary<int, int>>((InventoryRecord i) => i.Id, (InventoryRecord i) => i.Stacks.ToDictionary((StackRecord s) => s.Slot, (StackRecord s) => s.Count), StringComparer.Ordinal); foreach (TransferLine line in list2) { if ((line.Source == line.Destination && line.SourceSlot == line.DestinationSlot) || line.Count <= 0 || line.Item == null || !dictionary.TryGetValue(line.Source, out var value) || !dictionary.TryGetValue(line.Destination, out var value2)) { throw new ArgumentException("Invalid transfer endpoints"); } if (value.Revision != line.SourceRevision || value2.Revision != line.DestinationRevision) { throw new InvalidOperationException("Inventory changed; rebuild the plan"); } if (line.DestinationSlot < 0 || line.DestinationSlot >= value2.Capacity) { throw new ArgumentException("Invalid destination slot"); } StackRecord stackRecord = value.Stacks.FirstOrDefault((StackRecord s) => s.Slot == line.SourceSlot); if (stackRecord == null || !Same(stackRecord.Item, line.Item) || stackRecord.Equipped || stackRecord.Locked || stackRecord.Item.QuestItem) { throw new InvalidOperationException("Source item changed or protected"); } int num = remaining[value.Id][stackRecord.Slot]; if (line.Count > num - stackRecord.Reserve) { throw new InvalidOperationException("Insufficient unreserved source items"); } remaining[value.Id][stackRecord.Slot] = num - line.Count; } Dictionary<string, Dictionary<int, StackRecord>> working = list.ToDictionary<InventoryRecord, string, Dictionary<int, StackRecord>>((InventoryRecord i) => i.Id, (InventoryRecord i) => i.Stacks.Where((StackRecord s) => remaining[i.Id][s.Slot] > 0).ToDictionary((StackRecord s) => s.Slot, (StackRecord s) => new StackRecord(s.Item, s.Slot, remaining[i.Id][s.Slot], s.Reserve, s.Equipped, s.Locked, s.Hotbar)), StringComparer.Ordinal); foreach (TransferLine item in list2) { InventoryRecord value2 = dictionary[item.Destination]; Dictionary<int, StackRecord> dictionary2 = working[value2.Id]; if (dictionary2.TryGetValue(item.DestinationSlot, out var value3)) { if (!item.Item.StacksWith(value3.Item) || value3.Equipped || checked(value3.Count + item.Count) > value3.Item.MaxStack) { throw new InvalidOperationException("Destination stack changed or full"); } dictionary2[item.DestinationSlot] = new StackRecord(value3.Item, value3.Slot, value3.Count + item.Count, value3.Reserve, value3.Equipped, value3.Locked, value3.Hotbar); } else { if (value2.ReservedSlots.Contains(item.DestinationSlot) || item.Count > item.Item.MaxStack) { throw new InvalidOperationException("Destination slot protected or full"); } dictionary2.Add(item.DestinationSlot, new StackRecord(item.Item, item.DestinationSlot, item.Count)); } } List<InventoryRecord> after = list.Select((InventoryRecord i) => new InventoryRecord(i.Id, i.Revision, i.Capacity, working[i.Id].Values.OrderBy((StackRecord s) => s.Slot), i.QuickDepositEnabled, i.CraftEnabled, i.BuildEnabled, i.Priority, i.ReservedSlots)).ToList(); return new TransferBatch(list, after, list2); } } public sealed class TransferJournal { public sealed class PendingRecord { public string Operation; public DateTime Utc; public int Items; public List<InventoryRecord> Before; public List<InventoryRecord> After; } private readonly string directory; private readonly string pending; private string operation; public bool HasPending => File.Exists(pending); public string PendingPath => pending; public bool TryResolve(IEnumerable<InventoryRecord> current, out bool committed) { if (!MatchPending(current, out committed)) { return false; } Finish(committed); return true; } public bool MatchPending(IEnumerable<InventoryRecord> current, out bool committed) { committed = false; PendingRecord pendingRecord = InspectPending(); List<InventoryRecord> actual = current.ToList(); if (actual.Any((InventoryRecord i) => i == null) || actual.Count != pendingRecord.Before.Count || actual.Select((InventoryRecord i) => i.Id).Distinct().Count() != actual.Count) { return false; } Func<List<InventoryRecord>, bool> func = (List<InventoryRecord> expected) => expected.All((InventoryRecord e) => NativeTransfer.SameContents(e, actual.FirstOrDefault((InventoryRecord a) => a.Id == e.Id), e.Id.StartsWith("player:", StringComparison.Ordinal))); bool flag = func(pendingRecord.Before); bool flag2 = func(pendingRecord.After); if (flag == flag2) { return false; } operation = pendingRecord.Operation; committed = flag2; return true; } public TransferJournal(string directory) { this.directory = Path.GetFullPath(directory); pending = Path.Combine(this.directory, "pending.bin"); } public PendingRecord InspectPending() { return ReadRecord(pending); } public static PendingRecord ReadRecord(string path) { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); using BinaryReader binaryReader = new BinaryReader(fileStream, Encoding.UTF8); if (fileStream.Length > 33554432 || binaryReader.ReadString() != "VCQD1") { throw new InvalidDataException("Invalid recovery record"); } PendingRecord pendingRecord = new PendingRecord(); pendingRecord.Operation = binaryReader.ReadString(); pendingRecord.Utc = new DateTime(binaryReader.ReadInt64(), DateTimeKind.Utc); pendingRecord.Items = binaryReader.ReadInt32(); PendingRecord pendingRecord2 = pendingRecord; if (!Guid.TryParseExact(pendingRecord2.Operation, "N", out var _) || pendingRecord2.Items < 0) { throw new InvalidDataException("Invalid recovery header"); } pendingRecord2.Before = ReadInventories(binaryReader); pendingRecord2.After = ReadInventories(binaryReader); if (fileStream.Position != fileStream.Length || !(from i in pendingRecord2.Before select i.Id into s orderby s select s).SequenceEqual(from i in pendingRecord2.After select i.Id into s orderby s select s)) { throw new InvalidDataException("Invalid recovery endpoints"); } return pendingRecord2; } private static int Bounded(BinaryReader reader, int min, int max) { int num = reader.ReadInt32(); if (num < min || num > max) { throw new InvalidDataException("Recovery field outside bounds"); } return num; } private static List<InventoryRecord> ReadInventories(BinaryReader reader) { int num = Bounded(reader, 1, 512); List<InventoryRecord> list = new List<InventoryRecord>(); for (int i = 0; i < num; i++) { string id = reader.ReadString(); long revision = reader.ReadInt64(); int num2 = Bounded(reader, 1, 65536); int num3 = Bounded(reader, 0, num2); List<StackRecord> list2 = new List<StackRecord>(); for (int j = 0; j < num3; j++) { int slot = Bounded(reader, 0, num2 - 1); int count = Bounded(reader, 1, 65535); string prefab = reader.ReadString(); string identity = reader.ReadString(); int maxStack = Bounded(reader, 1, 65535); float? exactDurability = (reader.ReadBoolean() ? new float?(reader.ReadSingle()) : ((float?)null)); int num4 = Bounded(reader, 1, 2097152); byte[] array = reader.ReadBytes(num4); if (array.Length != num4) { throw new EndOfStreamException(); } list2.Add(new StackRecord(new ItemRecord(prefab, identity, maxStack, array, equipment: false, hasCustomData: true, MagicState.Unknown, questItem: false, exactDurability), slot, count)); } list.Add(new InventoryRecord(id, revision, num2, list2)); } if (list.Select((InventoryRecord inventoryRecord) => inventoryRecord.Id).Distinct().Count() != list.Count) { throw new InvalidDataException("Duplicate recovery endpoints"); } return list; } public void Prepare(TransferBatch batch) { if (batch == null) { throw new ArgumentNullException(); } Directory.CreateDirectory(directory); if (HasPending) { throw new InvalidOperationException("An earlier storage operation requires reconciliation"); } operation = Guid.NewGuid().ToString("N"); string text = Path.Combine(directory, operation + ".tmp"); try { using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough)) { using BinaryWriter binaryWriter = new BinaryWriter(fileStream, Encoding.UTF8); binaryWriter.Write("VCQD1"); binaryWriter.Write(operation); binaryWriter.Write(DateTime.UtcNow.Ticks); binaryWriter.Write(batch.ItemCount); WriteInventories(binaryWriter, batch.Before); WriteInventories(binaryWriter, batch.After); binaryWriter.Flush(); fileStream.Flush(flushToDisk: true); } File.Move(text, pending); } finally { if (File.Exists(text)) { File.Delete(text); } } } private static void WriteInventories(BinaryWriter writer, IEnumerable<InventoryRecord> inventories) { List<InventoryRecord> list = inventories.ToList(); writer.Write(list.Count); foreach (InventoryRecord item in list) { writer.Write(item.Id); writer.Write(item.Revision); writer.Write(item.Capacity); writer.Write(item.Stacks.Count); foreach (StackRecord stack in item.Stacks) { writer.Write(stack.Slot); writer.Write(stack.Count); writer.Write(stack.Item.Prefab); writer.Write(stack.Item.Identity); writer.Write(stack.Item.MaxStack); float? exactDurability = stack.Item.ExactDurability; writer.Write(exactDurability.HasValue); exactDurability = stack.Item.ExactDurability; if (exactDurability.HasValue) { exactDurability = stack.Item.ExactDurability; writer.Write(exactDurability.Value); } byte[] array = stack.Item.Payload(); writer.Write(array.Length); writer.Write(array); } } } public void Finish(bool committed) { if (!HasPending || string.IsNullOrEmpty(operation)) { throw new InvalidOperationException("Missing prepared storage record"); } string path = Path.Combine(directory, DateTime.UtcNow.ToString("yyyyMMddTHHmmssfffffff") + "-" + operation + ".json"); using (FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read, 4096, FileOptions.WriteThrough)) { byte[] bytes = Encoding.UTF8.GetBytes("{\"schema\":2,\"operation\":\"" + operation + "\",\"utc\":\"" + DateTime.UtcNow.ToString("o") + "\",\"state\":\"" + (committed ? "locally_applied" : "cancelled") + "\",\"remotePersistenceConfirmed\":false}\n"); fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } File.Move(pending, Path.ChangeExtension(path, "local.bin")); operation = null; try { foreach (FileInfo item in new DirectoryInfo(directory).GetFiles("*.json").OrderByDescending<FileInfo, string>((FileInfo f) => f.Name, StringComparer.Ordinal).Skip(128)) { item.Delete(); } } catch (IOException) { } catch (UnauthorizedAccessException) { } try { long num = 0L; int num2 = 0; foreach (FileInfo item2 in new DirectoryInfo(directory).GetFiles("*.local.bin").OrderByDescending<FileInfo, string>((FileInfo f) => f.Name, StringComparer.Ordinal)) { if (num2 >= 128 || num + item2.Length > 33554432) { item2.Delete(); continue; } num2++; num += item2.Length; } } catch (IOException) { } catch (UnauthorizedAccessException) { } } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("JacobsValheim.VikingCraftingAndQuickDeposit", "Viking Crafting & Quick Deposit", "0.0.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { private sealed class Observation { public ZRpc Rpc; public ZDOID Chest; public string Token; public string Hash; public float Deadline; } private sealed class AdmissionResult { public bool Granted; } private sealed class MaterialCount { public int Quality; public int World; public long Count; } private sealed class MaterialCache { public Player Player; public float Expires; public Vector3 Position; public readonly Dictionary<string, List<MaterialCount>> Items = new Dictionary<string, List<MaterialCount>>(StringComparer.Ordinal); } public sealed class CountScope { public int Depth; public bool Building; } private static class DisplayCounts { private static void Begin(out int __state) { __state = displayCountDepth; displayCountDepth++; } private static void BeginRefresh(out int __state) { Begin(out __state); if ((Object)(object)Instance != (Object)null) { Instance.InvalidateMaterials(); } } private static void End(int __state) { displayCountDepth = __state; } } public sealed class EpicActionScope { public int Physical; public CountScope Count; } private sealed class Flight { public Vector3 From; public Transform To; public string Prefab; public int Variant; public float Start; public float Duration; } [CompilerGenerated] private sealed class <>c__DisplayClass92 { public Plugin <>4__this; public Player player; public Func<Container, bool> extraAccess; public bool <GatherMaterialsCore>b__8c() { return <>4__this.StorageReady && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead(); } public InventoryRecord <GatherMaterialsCore>b__8d() { return <>4__this.CraftBag(player); } } [CompilerGenerated] private sealed class <>c__DisplayClass95 { private sealed class <>c__DisplayClass9d { public <>c__DisplayClass95 CS$<>8__locals96; public <>c__DisplayClass92 CS$<>8__locals93; public ItemData i; public bool <GatherMaterialsCore>b__8b(Ingredient r) { return r.Prefab == ((Object)i.m_dropPrefab).name && (r.Quality <= 0 || r.Quality == i.m_quality); } } public <>c__DisplayClass92 CS$<>8__locals93; public StorageUse use; public float range; public List<Ingredient> missing; public bool <GatherMaterialsCore>b__8a(ItemData i) { <>c__DisplayClass92 <>c__DisplayClass = CS$<>8__locals93; return (Object)(object)i.m_dropPrefab != (Object)null && missing.Any((Ingredient r) => r.Prefab == ((Object)i.m_dropPrefab).name && (r.Quality <= 0 || r.Quality == i.m_quality)) && i.m_worldLevel >= Game.m_worldLevel && CS$<>8__locals93.<>4__this.OrdinaryMaterial(i, player: false); } } [CompilerGenerated] private sealed class <>c__DisplayClass98 { public <>c__DisplayClass95 CS$<>8__locals96; public <>c__DisplayClass92 CS$<>8__locals93; public Container chest; public InventoryRecord <GatherMaterialsCore>b__8f() { return CS$<>8__locals93.<>4__this.CraftChest(chest, CS$<>8__locals93.player, CS$<>8__locals96.range, CS$<>8__locals96.use); } } [CompilerGenerated] private sealed class <>c__DisplayClass9b { public <>c__DisplayClass98 CS$<>8__locals99; public <>c__DisplayClass95 CS$<>8__locals96; public <>c__DisplayClass92 CS$<>8__locals93; public ChestOwnership.Acquisition acquired; public bool <GatherMaterialsCore>b__8e() { return ChestOwnership.Holds(CS$<>8__locals99.chest, acquired.Operation) && ContainerStorage.Eligible(CS$<>8__locals99.chest, CS$<>8__locals93.player, CS$<>8__locals96.range, CS$<>8__locals96.use) && (CS$<>8__locals93.extraAccess == null || CS$<>8__locals93.extraAccess(CS$<>8__locals99.chest)); } } [CompilerGenerated] private sealed class <GatherMaterialsCore>d__9f : IEnumerator<object>, IEnumerator, IDisposable { private object <>2__current; private int <>1__state; public Plugin <>4__this; public Player player; public List<Ingredient> requirements; public bool building; public Action complete; public string purpose; public Func<Container, bool> extraAccess; public TransferJournal <journal>5__a0; public string <error>5__a1; public bool <satisfied>5__a2; public bool <failed>5__a3; public int <moved>5__a4; public NativeTransferResult <result>5__a5; public Func<bool> CS$<>9__CachedAnonymousMethodDelegate9a; public <>c__DisplayClass9b CS$<>8__locals9c; public Func<InventoryRecord> CS$<>9__CachedAnonymousMethodDelegate97; public <>c__DisplayClass98 CS$<>8__locals99; public Func<ItemData, bool> CS$<>9__CachedAnonymousMethodDelegate94; public <>c__DisplayClass95 CS$<>8__locals96; public Func<bool> CS$<>9__CachedAnonymousMethodDelegate90; public Func<InventoryRecord> CS$<>9__CachedAnonymousMethodDelegate91; public <>c__DisplayClass92 CS$<>8__locals93; public List<Container>.Enumerator <>7__wrapa7; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } private bool MoveNext() { try { int num = <>1__state; if (num != 0) { if (num != 4) { if (num != 7) { goto IL_07f3; } <>1__state = 5; if (<result>5__a5.Committed) { <moved>5__a4 += <result>5__a5.Items; CS$<>8__locals96.missing = CraftPlanner.Plan(new InventoryRecord[1] { <>4__this.CraftBag(CS$<>8__locals93.player) }, requirements, building).Missing.ToList(); } else if (<result>5__a5.NeedsRecovery) { <failed>5__a3 = true; <>m__Finallya9(); goto IL_0719; } <>m__Finallya9(); } else { <>1__state = 3; if (CS$<>8__locals9c.acquired.Granted) { <>1__state = 5; <result>5__a5 = null; try { InventoryRecord inventoryRecord = <>4__this.CraftChest(CS$<>8__locals99.chest, CS$<>8__locals93.player, CS$<>8__locals96.range, CS$<>8__locals96.use); if (inventoryRecord == null) { <>m__Finallya9(); goto IL_0704; } InventoryRecord inventoryRecord2 = <>4__this.CraftBag(CS$<>8__locals93.player); TransferBatch transferBatch = MaterialPull.Plan(inventoryRecord2, inventoryRecord, requirements, building); if (transferBatch.Lines.Count == 0) { <>m__Finallya9(); goto IL_0704; } NativeInventoryEndpoint[] array = new NativeInventoryEndpoint[2]; string id = inventoryRecord2.Id; Inventory inventory = ((Humanoid)CS$<>8__locals93.player).GetInventory(); if (CS$<>9__CachedAnonymousMethodDelegate90 == null) { CS$<>9__CachedAnonymousMethodDelegate90 = () => CS$<>8__locals93.<>4__this.StorageReady && (Object)(object)CS$<>8__locals93.player != (Object)null && (Object)(object)CS$<>8__locals93.player == (Object)(object)Player.m_localPlayer && !((Character)CS$<>8__locals93.player).IsDead(); } Func<bool> authorized = CS$<>9__CachedAnonymousMethodDelegate90; if (CS$<>9__CachedAnonymousMethodDelegate91 == null) { CS$<>9__CachedAnonymousMethodDelegate91 = () => CS$<>8__locals93.<>4__this.CraftBag(CS$<>8__locals93.player); } array[0] = new NativeInventoryEndpoint(id, inventory, authorized, CS$<>9__CachedAnonymousMethodDelegate91, allowPickupMark: true); string id2 = inventoryRecord.Id; Inventory inventory2 = CS$<>8__locals99.chest.GetInventory(); if (CS$<>9__CachedAnonymousMethodDelegate9a == null) { CS$<>9__CachedAnonymousMethodDelegate9a = () => ChestOwnership.Holds(CS$<>8__locals9c.CS$<>8__locals99.chest, CS$<>8__locals9c.acquired.Operation) && ContainerStorage.Eligible(CS$<>8__locals9c.CS$<>8__locals99.chest, CS$<>8__locals9c.CS$<>8__locals93.player, CS$<>8__locals9c.CS$<>8__locals96.range, CS$<>8__locals9c.CS$<>8__locals96.use) && (CS$<>8__locals9c.CS$<>8__locals93.extraAccess == null || CS$<>8__locals9c.CS$<>8__locals93.extraAccess(CS$<>8__locals9c.CS$<>8__locals99.chest)); } Func<bool> authorized2 = CS$<>9__CachedAnonymousMethodDelegate9a; if (CS$<>9__CachedAnonymousMethodDelegate97 == null) { CS$<>9__CachedAnonymousMethodDelegate97 = () => CS$<>8__locals99.CS$<>8__locals93.<>4__this.CraftChest(CS$<>8__locals99.chest, CS$<>8__locals99.CS$<>8__locals93.player, CS$<>8__locals99.CS$<>8__locals96.range, CS$<>8__locals99.CS$<>8__locals96.use); } array[1] = new NativeInventoryEndpoint(id2, inventory2, authorized2, CS$<>9__CachedAnonymousMethodDelegate97); NativeInventoryEndpoint[] native = array; transferBatch.Purpose = purpose; <result>5__a5 = NativeTransfer.Execute(transferBatch, native, <journal>5__a0.Prepare, DeferCompletion(<journal>5__a0)); } catch (Exception ex) { ((BaseUnityPlugin)<>4__this).Logger.LogError((object)("Material pull: " + ex)); <failed>5__a3 = true; <>m__Finallya9(); goto IL_0719; } <>2__current = <>4__this.ConfirmTransfer(CS$<>8__locals93.player, CS$<>8__locals99.chest, <journal>5__a0, <result>5__a5); <>1__state = 7; return true; } } } else { <>1__state = -1; CS$<>9__CachedAnonymousMethodDelegate90 = null; CS$<>9__CachedAnonymousMethodDelegate91 = null; CS$<>8__locals93 = new <>c__DisplayClass92(); CS$<>8__locals93.player = player; CS$<>8__locals93.extraAccess = extraAccess; CS$<>8__locals93.<>4__this = <>4__this; <journal>5__a0 = null; <error>5__a1 = null; try { <journal>5__a0 = <>4__this.Journal(CS$<>8__locals93.player); if (<journal>5__a0.HasPending) { <error>5__a1 = "An earlier transfer needs recovery."; } } catch (Exception ex) { <error>5__a1 = ex.Message; } if (<error>5__a1 != null) { <>4__this.actionRunning = false; <>4__this.Notify(<error>5__a1); goto IL_07f3; } <satisfied>5__a2 = false; <failed>5__a3 = false; <moved>5__a4 = 0; <>1__state = 2; CS$<>9__CachedAnonymousMethodDelegate94 = null; CS$<>8__locals96 = new <>c__DisplayClass95(); CS$<>8__locals96.CS$<>8__locals93 = CS$<>8__locals93; CS$<>8__locals96.use = (building ? StorageUse.Build : StorageUse.Craft); CS$<>8__locals96.range = <>4__this.PullRadius(building); CS$<>8__locals96.missing = CraftPlanner.Plan(new InventoryRecord[1] { <>4__this.CraftBag(CS$<>8__locals93.player) }, requirements, building).Missing.ToList(); <>7__wrapa7 = <>4__this.MaterialCandidates(CS$<>8__locals93.player, CS$<>8__locals96.range, CS$<>8__locals96.use, requirements).GetEnumerator(); <>1__state = 3; } goto IL_0704; IL_07f3: return false; IL_0704: while (<>7__wrapa7.MoveNext()) { CS$<>9__CachedAnonymousMethodDelegate97 = null; CS$<>8__locals99 = new <>c__DisplayClass98(); CS$<>8__locals99.CS$<>8__locals96 = CS$<>8__locals96; CS$<>8__locals99.CS$<>8__locals93 = CS$<>8__locals93; CS$<>8__locals99.chest = <>7__wrapa7.Current; CS$<>9__CachedAnonymousMethodDelegate9a = null; CS$<>8__locals9c = new <>c__DisplayClass9b(); CS$<>8__locals9c.CS$<>8__locals99 = CS$<>8__locals99; CS$<>8__locals9c.CS$<>8__locals96 = CS$<>8__locals96; CS$<>8__locals9c.CS$<>8__locals93 = CS$<>8__locals93; if ((Object)(object)CS$<>8__locals93.player == (Object)null || (Object)(object)CS$<>8__locals93.player != (Object)(object)Player.m_localPlayer || ((Character)CS$<>8__locals93.player).IsDead()) { break; } if (CS$<>8__locals93.extraAccess != null && !CS$<>8__locals93.extraAccess(CS$<>8__locals99.chest)) { continue; } if (CS$<>8__locals96.missing.Count == 0) { break; } List<ItemData> allItems = CS$<>8__locals99.chest.GetInventory().GetAllItems(); if (CS$<>9__CachedAnonymousMethodDelegate94 == null) { CS$<>9__CachedAnonymousMethodDelegate94 = delegate(ItemData i) { <>c__DisplayClass95.<>c__DisplayClass9d CS$<>8__locals5 = new <>c__DisplayClass95.<>c__DisplayClass9d { CS$<>8__locals96 = CS$<>8__locals96, CS$<>8__locals93 = CS$<>8__locals96.CS$<>8__locals93, i = i }; return (Object)(object)CS$<>8__locals5.i.m_dropPrefab != (Object)null && CS$<>8__locals96.missing.Any((Ingredient r) => r.Prefab == ((Object)CS$<>8__locals5.i.m_dropPrefab).name && (r.Quality <= 0 || r.Quality == CS$<>8__locals5.i.m_quality)) && CS$<>8__locals5.i.m_worldLevel >= Game.m_worldLevel && CS$<>8__locals96.CS$<>8__locals93.<>4__this.OrdinaryMaterial(CS$<>8__locals5.i, player: false); }; } if (!allItems.Any(CS$<>9__CachedAnonymousMethodDelegate94)) { continue; } CS$<>8__locals9c.acquired = new ChestOwnership.Acquisition(); <>2__current = <>4__this.ownership.Acquire(CS$<>8__locals99.chest, CS$<>8__locals93.player, CS$<>8__locals9c.acquired); <>1__state = 4; return true; } goto IL_0719; IL_0719: <>m__Finallya8(); <satisfied>5__a2 = !<failed>5__a3 && (Object)(object)CS$<>8__locals93.player != (Object)null && (Object)(object)CS$<>8__locals93.player == (Object)(object)Player.m_localPlayer && CraftPlanner.Plan(new InventoryRecord[1] { <>4__this.CraftBag(CS$<>8__locals93.player) }, requirements, building).Satisfied; <>m__Finallya6(); if (<satisfied>5__a2) { complete(); } else { <>4__this.Notify(<failed>5__a3 ? "Material transfer stopped; check the plugin log." : ("Could not gather every ingredient. Check free inventory space and storage access. " + <moved>5__a4 + " items gathered.")); } goto IL_07f3; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } void IDisposable.Dispose() { switch (<>1__state) { case 6: break; case 2: case 3: case 4: case 5: case 7: try { switch (<>1__state) { case 6: break; case 3: case 4: case 5: case 7: try { switch (<>1__state) { case 6: break; case 5: case 7: try { break; } finally { <>m__Finallya9(); } } break; } finally { <>m__Finallya8(); } } break; } finally { <>m__Finallya6(); } } } [DebuggerHidden] public <GatherMaterialsCore>d__9f(int <>1__state) { this.<>1__state = <>1__state; } private void <>m__Finallya6() { <>1__state = -1; <>4__this.actionRunning = false; <>4__this.nextScan = 0f; <>4__this.InvalidateMaterials(); } private void <>m__Finallya8() { <>1__state = 2; ((IDisposable)<>7__wrapa7/*cast due to .constrained prefix*/).Dispose(); } private void <>m__Finallya9() { <>1__state = 3; ChestOwnership.Release(CS$<>8__locals99.chest, CS$<>8__locals9c.acquired.Operation); } } [CompilerGenerated] private sealed class <>c__DisplayClass146 { public Player player; public Plugin <>4__this; public bool <RunStorageAction>b__140() { return <>4__this.StorageReady && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead(); } public InventoryRecord <RunStorageAction>b__141() { return ContainerStorage.CapturePlayer(player, <>4__this.bridge, <>4__this.favorites); } } [CompilerGenerated] private sealed class <>c__DisplayClass148 { public StorageUse use; } [CompilerGenerated] private sealed class <>c__DisplayClass14b { public <>c__DisplayClass148 CS$<>8__locals149; public <>c__DisplayClass146 CS$<>8__locals147; public Container chest; public InventoryRecord <RunStorageAction>b__143() { return ContainerStorage.Capture(chest, CS$<>8__locals147.player, CS$<>8__locals147.<>4__this.Radius(), CS$<>8__locals149.use, CS$<>8__locals147.<>4__this.bridge).Inventory; } } [CompilerGenerated] private sealed class <>c__DisplayClass14e { public <>c__DisplayClass14b CS$<>8__locals14c; public <>c__DisplayClass148 CS$<>8__locals149; public <>c__DisplayClass146 CS$<>8__locals147; public ChestOwnership.Acquisition acquired; public bool <RunStorageAction>b__142() { return ChestOwnership.Holds(CS$<>8__locals14c.chest, acquired.Operation) && ContainerStorage.Eligible(CS$<>8__locals14c.chest, CS$<>8__locals147.player, CS$<>8__locals147.<>4__this.Radius(), CS$<>8__locals149.use); } } [CompilerGenerated] private sealed class <RunStorageAction>d__150 : IEnumerator<object>, IEnumerator, IDisposable { private object <>2__current; private int <>1__state; public Plugin <>4__this; public StorageAction action; public int <moved>5__151; public int <chests>5__152; public int <skipped>5__153; public bool <recovery>5__154; public TransferJournal <journal>5__155; public string <failure>5__156; public AdmissionResult <admission>5__157; public bool <candidate>5__158; public NativeTransferResult <result>5__159; public TransferBatch <batch>5__15a; public Func<bool> CS$<>9__CachedAnonymousMethodDelegate14d; public <>c__DisplayClass14e CS$<>8__locals14f; public Func<InventoryRecord> CS$<>9__CachedAnonymousMethodDelegate14a; public <>c__DisplayClass14b CS$<>8__locals14c; public <>c__DisplayClass148 CS$<>8__locals149; public Func<bool> CS$<>9__CachedAnonymousMethodDelegate144; public Func<InventoryRecord> CS$<>9__CachedAnonymousMethodDelegate145; public <>c__DisplayClass146 CS$<>8__locals147; public List<Container>.Enumerator <>7__wrap15c; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } private bool MoveNext() { try { switch (<>1__state) { case 0: <>1__state = -1; CS$<>9__CachedAnonymousMethodDelegate144 = null; CS$<>9__CachedAnonymousMethodDelegate145 = null; CS$<>8__locals147 = new <>c__DisplayClass146(); CS$<>8__locals147.<>4__this = <>4__this; CS$<>8__locals147.player = Player.m_localPlayer; <moved>5__151 = 0; <chests>5__152 = 0; <skipped>5__153 = 0; <recovery>5__154 = false; <journal>5__155 = null; <failure>5__156 = null; try { <journal>5__155 = <>4__this.Journal(CS$<>8__locals147.player); } catch (Exception ex) { <failure>5__156 = ex.Message; } if (<failure>5__156 != null) { <>4__this.Notify(<failure>5__156); ((BaseUnityPlugin)<>4__this).Logger.LogError((object)(<failure>5__156 + ((<journal>5__155 == null) ? "" : (" " + <journal>5__155.PendingPath)))); <>4__this.actionRunning = false; break; } if (<journal>5__155.HasPending) { <>2__current = <>4__this.CheckRecovery(CS$<>8__locals147.player, <journal>5__155); <>1__state = 2; return true; } <admission>5__157 = new AdmissionResult(); <>2__current = <>4__this.RequestAdmission(CS$<>8__locals147.player, <admission>5__157); <>1__state = 3; return true; case 2: <>1__state = -1; break; case 3: <>1__state = -1; if (!<admission>5__157.Granted) { <>4__this.actionRunning = false; <>4__this.Notify("Server cooldown or storage connection unavailable. No items moved."); break; } <>1__state = 4; CS$<>8__locals149 = new <>c__DisplayClass148(); CS$<>8__locals149.use = ((action == StorageAction.QuickDeposit) ? StorageUse.QuickDeposit : StorageUse.Restock); <>7__wrap15c = <>4__this.AvailableStorage(CS$<>8__locals147.player, <>4__this.Radius(), CS$<>8__locals149.use).GetEnumerator(); <>1__state = 6; goto IL_0917; case 8: <>1__state = 6; if (!CS$<>8__locals14f.acquired.Granted) { <skipped>5__153++; ((BaseUnityPlugin)<>4__this).Logger.LogDebug((object)("Storage skipped: " + CS$<>8__locals14f.acquired.Error)); } else { <>1__state = 9; <result>5__159 = null; <batch>5__15a = null; try { StorageLocation storageLocation2 = ContainerStorage.Capture(CS$<>8__locals14c.chest, CS$<>8__locals147.player, <>4__this.Radius(), CS$<>8__locals149.use, <>4__this.bridge); if (storageLocation2 == null || !ChestOwnership.Holds(CS$<>8__locals14c.chest, CS$<>8__locals14f.acquired.Operation)) { <skipped>5__153++; <>m__Finally15e(); goto IL_0917; } InventoryRecord inventoryRecord = ContainerStorage.CapturePlayer(CS$<>8__locals147.player, <>4__this.bridge, <>4__this.favorites); IEnumerable<TransferLine> enumerable = <>4__this.PlanStorage(action, inventoryRecord, storageLocation2.Inventory); if (!enumerable.Any()) { <>m__Finally15e(); goto IL_0917; } <batch>5__15a = TransferBatch.Prepare(new InventoryRecord[2] { inventoryRecord, storageLocation2.Inventory }, enumerable); NativeInventoryEndpoint[] array = new NativeInventoryEndpoint[2]; string id = inventoryRecord.Id; Inventory inventory = ((Humanoid)CS$<>8__locals147.player).GetInventory(); if (CS$<>9__CachedAnonymousMethodDelegate144 == null) { CS$<>9__CachedAnonymousMethodDelegate144 = () => CS$<>8__locals147.<>4__this.StorageReady && (Object)(object)CS$<>8__locals147.player != (Object)null && (Object)(object)CS$<>8__locals147.player == (Object)(object)Player.m_localPlayer && !((Character)CS$<>8__locals147.player).IsDead(); } Func<bool> authorized = CS$<>9__CachedAnonymousMethodDelegate144; if (CS$<>9__CachedAnonymousMethodDelegate145 == null) { CS$<>9__CachedAnonymousMethodDelegate145 = () => ContainerStorage.CapturePlayer(CS$<>8__locals147.player, CS$<>8__locals147.<>4__this.bridge, CS$<>8__locals147.<>4__this.favorites); } array[0] = new NativeInventoryEndpoint(id, inventory, authorized, CS$<>9__CachedAnonymousMethodDelegate145, allowPickupMark: true); string id2 = storageLocation2.Inventory.Id; Inventory inventory2 = CS$<>8__locals14c.chest.GetInventory(); if (CS$<>9__CachedAnonymousMethodDelegate14d == null) { CS$<>9__CachedAnonymousMethodDelegate14d = () => ChestOwnership.Holds(CS$<>8__locals14f.CS$<>8__locals14c.chest, CS$<>8__locals14f.acquired.Operation) && ContainerStorage.Eligible(CS$<>8__locals14f.CS$<>8__locals14c.chest, CS$<>8__locals14f.CS$<>8__locals147.player, CS$<>8__locals14f.CS$<>8__locals147.<>4__this.Radius(), CS$<>8__locals14f.CS$<>8__locals149.use); } Func<bool> authorized2 = CS$<>9__CachedAnonymousMethodDelegate14d; if (CS$<>9__CachedAnonymousMethodDelegate14a == null) { CS$<>9__CachedAnonymousMethodDelegate14a = () => ContainerStorage.Capture(CS$<>8__locals14c.chest, CS$<>8__locals14c.CS$<>8__locals147.player, CS$<>8__locals14c.CS$<>8__locals147.<>4__this.Radius(), CS$<>8__locals14c.CS$<>8__locals149.use, CS$<>8__locals14c.CS$<>8__locals147.<>4__this.bridge).Inventory; } array[1] = new NativeInventoryEndpoint(id2, inventory2, authorized2, CS$<>9__CachedAnonymousMethodDelegate14a); NativeInventoryEndpoint[] native = array; <result>5__159 = NativeTransfer.Execute(<batch>5__15a, native, <journal>5__155.Prepare, DeferCompletion(<journal>5__155)); } catch (Exception ex) { <skipped>5__153++; ((BaseUnityPlugin)<>4__this).Logger.LogError((object)("Storage action interrupted: " + ex)); if (<journal>5__155.HasPending) { <recovery>5__154 = true; <>m__Finally15e(); goto IL_092c; } } if (<result>5__159 != null) { <>2__current = <>4__this.ConfirmTransfer(CS$<>8__locals147.player, CS$<>8__locals14c.chest, <journal>5__155, <result>5__159); <>1__state = 11; return true; } <>m__Finally15e(); } goto IL_0917; case 11: <>1__state = 9; if (<result>5__159.Committed) { <moved>5__151 += <result>5__159.Items; <chests>5__152++; if (action == StorageAction.QuickDeposit) { <>4__this.DepositFlight(<batch>5__15a, CS$<>8__locals147.player, CS$<>8__locals14c.chest); } } else { <skipped>5__153++; ((BaseUnityPlugin)<>4__this).Logger.LogWarning((object)("Storage transfer stopped: " + <result>5__159.Error)); if (<result>5__159.NeedsRecovery) { <recovery>5__154 = true; ((BaseUnityPlugin)<>4__this).Logger.LogError((object)("Prepared recovery data: " + <journal>5__155.PendingPath)); <>m__Finally15e(); goto IL_092c; } } <>m__Finally15e(); <>2__current = null; <>1__state = 12; return true; case 12: { <>1__state = 6; goto IL_0917; } IL_092c: <>m__Finally15d(); <>m__Finally15b(); break; IL_0917: while (<>7__wrap15c.MoveNext()) { CS$<>9__CachedAnonymousMethodDelegate14a = null; CS$<>8__locals14c = new <>c__DisplayClass14b(); CS$<>8__locals14c.CS$<>8__locals149 = CS$<>8__locals149; CS$<>8__locals14c.CS$<>8__locals147 = CS$<>8__locals147; CS$<>8__locals14c.chest = <>7__wrap15c.Current; CS$<>9__CachedAnonymousMethodDelegate14d = null; CS$<>8__locals14f = new <>c__DisplayClass14e(); CS$<>8__locals14f.CS$<>8__locals14c = CS$<>8__locals14c; CS$<>8__locals14f.CS$<>8__locals149 = CS$<>8__locals149; CS$<>8__locals14f.CS$<>8__locals147 = CS$<>8__locals147; if ((Object)(object)CS$<>8__locals147.player == (Object)null || (Object)(object)CS$<>8__locals147.player != (Object)(object)Player.m_localPlayer || ((Character)CS$<>8__locals147.player).IsDead()) { break; } <candidate>5__158 = true; try { StorageLocation storageLocation = ContainerStorage.Capture(CS$<>8__locals14c.chest, CS$<>8__locals147.player, <>4__this.Radius(), CS$<>8__locals149.use, <>4__this.bridge); <candidate>5__158 = storageLocation != null && <>4__this.PlanStorage(action, ContainerStorage.CapturePlayer(CS$<>8__locals147.player, <>4__this.bridge, <>4__this.favorites), storageLocation.Inventory).Any(); } catch { <skipped>5__153++; <candidate>5__158 = false; } if (!<candidate>5__158) { continue; } CS$<>8__locals14f.acquired = new ChestOwnership.Acquisition(); <>2__current = <>4__this.ownership.Acquire(CS$<>8__locals14c.chest, CS$<>8__locals147.player, CS$<>8__locals14f.acquired); <>1__state = 8; return true; } goto IL_092c; } return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } void IDisposable.Dispose() { switch (<>1__state) { case 5: case 7: case 10: break; case 4: case 6: case 8: case 9: case 11: case 12: try { switch (<>1__state) { case 7: case 10: break; case 6: case 8: case 9: case 11: case 12: try { switch (<>1__state) { case 10: break; case 9: case 11: try { break; } finally { <>m__Finally15e(); } } break; } finally { <>m__Finally15d(); } } break; } finally { <>m__Finally15b(); } } } [DebuggerHidden] public <RunStorageAction>d__150(int <>1__state) { this.<>1__state = <>1__state; } private void <>m__Finally15b() { <>1__state = -1; <>4__this.actionRunning = false; <>4__this.nextScan = 0f; <>4__this.InvalidateMaterials(); if (<recovery>5__154) { <>4__this.Notify("Storage outcome needs recovery. Further transfers are blocked; see the plugin log."); return; } List<string> list = new List<string>(); if (<skipped>5__153 > 0) { list.Add(<skipped>5__153 + " busy or unavailable chest(s)"); } if (action == StorageAction.Restock && (Object)(object)CS$<>8__locals147.player != (Object)null && (Object)(object)CS$<>8__locals147.player == (Object)(object)Player.m_localPlayer) { foreach (Ingredient item in RestockPlanner.Plan(ContainerStorage.CapturePlayer(CS$<>8__locals147.player, <>4__this.bridge, <>4__this.favorites), new InventoryRecord[0], <>4__this.supplies.Rules).Missing) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(item.Prefab); string text = (((Object)(object)itemPrefab == (Object)null) ? item.Prefab : itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_name); list.Add(item.Count + " " + ((Localization.instance == null) ? text : Localization.instance.Localize(text))); } } <>4__this.ShowStorageResult(action, (<moved>5__151 == 0) ? ResultState.NoChange : ((list.Count > 0) ? ResultState.Partial : ResultState.Complete), <moved>5__151, <chests>5__152, list); } private void <>m__Finally15d() { <>1__state = 4; ((IDisposable)<>7__wrap15c/*cast due to .constrained prefix*/).Dispose(); } private void <>m__Finally15e() { <>1__state = 6; ChestOwnership.Release(CS$<>8__locals14c.chest, CS$<>8__locals14f.acquired.Operation); } } private const string ConfirmRpc = "VCQD_ObserveV1"; private const string ConfirmReplyRpc = "VCQD_ObservedV1"; private const string HelloRpc = "VCQD_HelloV1"; private const string SettingsRpc = "VCQD_SettingsV1"; private const string AdmitRpc = "VCQD_AdmitV1"; private const string AdmitReplyRpc = "VCQD_AdmitReplyV1"; private const string AutoFuelKey = "VCQD_AutoFuel_v1"; private const string AutoFuelRpc = "VCQD_AutoFuelToggle_v1"; private const float SearchRowHeight = 112f; private readonly Dictionary<ZRpc, Observation> observations = new Dictionary<ZRpc, Observation>(); private string confirmationToken; private bool confirmationReceived; private bool confirmationGranted; private bool serverConfirmation; private ConfigEntry<bool> sharedChests; private bool serverSharedChests; private bool? sharedConflict; private Container sharedView; private Container sharedSettingLease; private static Container renderingShared; private float sharedRefresh; private static readonly MethodInfo ClearDrag = AccessTools.Method(typeof(InventoryGui), "SetupDragItem", (Type[])null, (Type[])null); private readonly Dictionary<ZRpc, float> storageConnections = new Dictionary<ZRpc, float>(); private ZRpc storageServer; private float[] serverRanges; private float nextNetworkScan; private readonly Dictionary<long, float> nextServerAction = new Dictionary<long, float>(); private string admissionOperation; private bool admissionReceived; private bool admissionGranted; private ConfigEntry<float> craftRadius; private ConfigEntry<float> buildRadius; private static int countDepth; private static int physicalOnly; private static int displayCountDepth; private static bool countBuilding; private static int buildConsumptionFrame = -1; private readonly MaterialCache craftCounts = new MaterialCache(); private readonly MaterialCache buildCounts = new MaterialCache(); private static readonly FieldInfo CraftRecipe = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe"); private static readonly FieldInfo UpgradeItem = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem"); private static readonly FieldInfo MultiCraft = AccessTools.Field(typeof(InventoryGui), "m_multiCrafting"); private static readonly MethodInfo DoCraft = AccessTools.Method(typeof(InventoryGui), "DoCrafting", (Type[])null, (Type[])null); private Type epicTableUi; private static Type epicListType; private static readonly Dictionary<Type, FieldInfo> epicCostFields = new Dictionary<Type, FieldInfo>(); private bool epicCraftingReady; private ConfigEntry<bool> refillEnabled; private static int refillNative; private static readonly MethodInfo SmelterFuel = AccessTools.Method(typeof(Smelter), "OnAddFuel", (Type[])null, (Type[])null); private static readonly MethodInfo SmelterOre = AccessTools.Method(typeof(Smelter), "OnAddOre", (Type[])null, (Type[])null); private static readonly MethodInfo SmelterFuelCount = AccessTools.Method(typeof(Smelter), "GetFuel", (Type[])null, (Type[])null); private static readonly MethodInfo SmelterQueue = AccessTools.Method(typeof(Smelter), "GetQueueSize", (Type[])null, (Type[])null); private static readonly MethodInfo CookingFuel = AccessTools.Method(typeof(CookingStation), "OnAddFuelSwitch", (Type[])null, (Type[])null); private static readonly MethodInfo CookingInteract = AccessTools.Method(typeof(CookingStation), "OnInteract", (Type[])null, (Type[])null); private static readonly MethodInfo CookingDone = AccessTools.Method(typeof(CookingStation), "HaveDoneItem", (Type[])null, (Type[])null); private static readonly MethodInfo CookingSlot = AccessTools.Method(typeof(CookingStation), "GetFreeSlot", (Type[])null, (Type[])null); private static readonly MethodInfo CookingFire = AccessTools.Method(typeof(CookingStation), "IsFireLit", (Type[])null, (Type[])null); private static readonly MethodInfo CookingFuelCount = AccessTools.Method(typeof(CookingStation), "GetFuel", (Type[])null, (Type[])null); private static readonly MethodInfo ShieldFuel = AccessTools.Method(typeof(ShieldGenerator), "OnAddFuel", (Type[])null, (Type[])null); private static readonly MethodInfo ShieldFuelCount = AccessTools.Method(typeof(ShieldGenerator), "GetFuel", (Type[])null, (Type[])null); private static CookingStation cookingTarget; private static ItemData cookingChoice; private static readonly MethodInfo GridPosition = AccessTools.Method(typeof(InventoryGrid), "GetButtonPos", (Type[])null, (Type[])null); private static readonly MethodInfo GridElement = AccessTools.Method(typeof(InventoryGrid), "GetElement", (Type[])null, (Type[])null); private static readonly FieldInfo DragItem = AccessTools.Field(typeof(InventoryGui), "m_dragItem"); private static readonly FieldInfo DragInventory = AccessTools.Field(typeof(InventoryGui), "m_dragInventory"); private static readonly FieldInfo DragAmount = AccessTools.Field(typeof(InventoryGui), "m_dragAmount"); private static readonly MethodInfo SetDrag = AccessTools.Method(typeof(InventoryGui), "SetupDragItem", (Type[])null, (Type[])null); private ConfigEntry<bool> trashEnabled; private GameObject trashObject; private InventoryGui trashGui; private Button trashButton; private TMP_Text trashLabel; private UITooltip trashTip; private readonly Dictionary<string, Sprite> itemIcons = new Dictionary<string, Sprite>(); private readonly List<Flight> flights = new List<Flight>(); private ConfigEntry<bool> depositEffects; private ConfigEntry<float> depositFlightSeconds; private readonly HashSet<Fireplace> fires = new HashSet<Fireplace>(); private FuelOwnership fuelOwnership; private ConfigEntry<bool> automaticRefill; private float nextFuelScan; private float nextFireCleanup; private int fuelCursor; private readonly ItemLocationIndex materialIndex = new ItemLocationIndex(); private ConfigEntry<KeyboardShortcut> depositKey; private ConfigEntry<KeyboardShortcut> restockKey; private ConfigEntry<float> noticeSeconds; private string notice; private float noticeUntil; private float nextActionTime; private bool actionRunning; private ConfigEntry<bool> depositEquipment; private ConfigEntry<bool> depositEnchanted; private ConfigEntry<bool> protectHotbar; private static readonly FieldInfo CurrentChest = AccessTools.Field(typeof(InventoryGui), "m_currentContainer"); private GameObject chestControls; private InventoryGui chestGui; private Container displayedChest; private Button depositButton; private Button craftButton; private Button buildButton; private Button priorityDown; private Button priorityUp; private Button sortButton; private TMP_Text depositLabel; private TMP_Text craftLabel; private TMP_Text buildLabel; private TMP_Text priorityLabel; private GameObject locatorObject; private LineRenderer locatorOutline; private Material locatorMaterial; private Minimap locatorMap; private PinData locatorPin; private Renderer[] locatorRenderers; private readonly Vector3[] locatorCorners = (Vector3[])(object)new Vector3[8]; private static readonly int[] BoxPath = new int[16] { 0, 1, 2, 3, 0, 4, 5, 1, 5, 6, 2, 6, 7, 3, 7, 4 }; internal static Plugin Instance; internal readonly ModalInputState Modal = new ModalInputState(); private readonly ChestScanner scanner = new ChestScanner(); private readonly EpicLootBridge bridge = new EpicLootBridge(); private readonly StorytellerBridge storyteller = new StorytellerBridge(); private ConfigEntry<float> radius; private ConfigEntry<bool> discovery; private ConfigEntry<KeyboardShortcut> searchKey; private Harmony patches; private float nextScan; private float nextWarning; private Player lastPlayer; private Coroutine scan; private Coroutine discoveryScan; private bool ready; private bool searchRequested; private ChestOwnership ownership; private float nextStorageCleanup; private Rect window; private Vector2 scroll; private string query = ""; private string rarity = ""; private bool focusSearch; private int resultOrder; private int shownRevision = -1; private List<SearchRow> results = new List<SearchRow>(); private Vector3 searchOrigin; private CursorLockMode cursorMode; private bool cursorVisible; private Texture2D background; private GUIStyle panel; private GUIStyle label; private GUIStyle muted; private GUIStyle button; private GUIStyle title; private string locateId; private float locateUntil; private float nextAccessCheck; private SupplyPreferences supplies = new SupplyPreferences(); private ConfigEntry<string> supplySetting; private FavoriteSlots favorites = new FavoriteSlots(); private ConfigEntry<string> favoriteSetting; private bool favoriteTab; private bool suppliesTab; private bool supplyReady; private Vector2 supplyScroll; private string supplyQuery = ""; private string supplyError = ""; private readonly Dictionary<string, string> targetText = new Dictionary<string, string>(); private bool SharedAvailable { get { if (!sharedConflict.HasValue) { sharedConflict = AppDomain.CurrentDomain.GetAssemblies().Any((Assembly a) => a.GetName().Name == "MultiUserChest"); if (sharedConflict.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Built-in shared chest access is disabled while another shared chest handler is installed. Remove MultiUserChest before enabling built-in access."); } } return !sharedConflict.Value; } } private bool SharedEnabled => StorageReady && SharedAvailable && (ZNet.instance.IsServer() ? sharedChests.Value : serverSharedChests); private bool StorageReady => (Object)(object)ZNet.instance != (Object)null && (ZNet.instance.IsServer() || (serverConfirmation && storageServer != null && storageServer.IsConnected() && ZNet.instance.GetServerPeer() != null && ZNet.instance.GetServerPeer().m_rpc == storageServer)); internal static bool BlockInput => (Object)(object)Instance != (Object)null && Instance.Modal.Blocks(Time.frameCount); private static string ItemHash(byte[] bytes) { if (bytes == null) { return null; } using SHA256 sHA = SHA256.Create(); return Convert.ToBase64String(sHA.ComputeHash(bytes)); } private static bool ObservationMatches(long owner, long sender, byte[] items, string hash) { return owner == sender && items != null && hash != null && ItemHash(items) == hash; } private void ReceiveObservation(ZRpc rpc, ZPackage package) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || package.Size() > 256) { return; } try { ZNetPeer val = ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == rpc && p.IsReady())); if (val == null) { return; } string text = package.ReadString(); if (text.Length != 32 || !Guid.TryParseExact(text, "N", out var _)) { return; } ZDOID val2 = package.ReadZDOID(); string text2 = package.ReadString(); if (text2.Length == 44) { ZDO zDO = ZDOMan.instance.GetZDO(val2); if (zDO == null) { rpc.Invoke("VCQD_ObservedV1", new object[2] { text, false }); return; } if (observations.ContainsKey(rpc) || observations.Count >= 64) { rpc.Invoke("VCQD_ObservedV1", new object[2] { text, false }); return; } observations.Add(rpc, new Observation { Rpc = rpc, Chest = val2, Token = text, Hash = text2, Deadline = Time.realtimeSinceStartup + 4f }); } } catch (Exception ex) { Warn("Storage observation rejected: " + ex.Message); } } private void TickObservations() { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) if (observations.Count == 0) { return; } KeyValuePair<ZRpc, Observation>[] array = observations.ToArray(); foreach (KeyValuePair<ZRpc, Observation> pair in array) { KeyValuePair<ZRpc, Observation> keyValuePair = pair; Observation value = keyValuePair.Value; ZNetPeer val = (((Object)(object)ZNet.instance == (Object)null) ? null : ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)delegate(ZNetPeer p) { ZRpc rpc = p.m_rpc; KeyValuePair<ZRpc, Observation> keyValuePair2 = pair; return rpc == keyValuePair2.Key && p.IsReady(); })); if (val == null) { Dictionary<ZRpc, Observation> dictionary = observations; keyValuePair = pair; dictionary.Remove(keyValuePair.Key); continue; } ZDO zDO = ZDOMan.instance.GetZDO(value.Chest); bool flag = zDO != null && zDO.GetOwner() == val.m_uid && ObservationMatches(zDO.GetOwner(), val.m_uid, zDO.GetByteArray(ZDOVars.s_items, (byte[])null), value.Hash); if (flag || Time.realtimeSinceStartup >= value.Deadline) { Dictionary<ZRpc, Observation> dictionary2 = observations; keyValuePair = pair; dictionary2.Remove(keyValuePair.Key); keyValuePair = pair; keyValuePair.Key.Invoke("VCQD_ObservedV1", new object[2] { value.Token, flag }); } } } private void ReceiveObservationReply(ZRpc rpc, string token, bool accepted) { if (rpc == storageServer && !(token != confirmationToken)) { confirmationReceived = true; confirmationGranted = accepted; } } private IEnumerator ConfirmTransfer(Player player, Container chest, TransferJournal journal, NativeTransferResult result, bool applied = true) { if (!result.Committed) { yield break; } result.Committed = false; result.NeedsRecovery = true; ZNetPeer peer = (((Object)(object)ZNet.instance == (Object)null) ? null : ZNet.instance.GetServerPeer()); ZRpc connection = peer?.m_rpc; string error = null; string token = null; string hash = null; ZDOID id = ZDOID.None; try { ZNetView val = ContainerStorage.View(chest); if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner() || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || ((Character)player).IsDead()) { throw new InvalidOperationException("Storage session changed before confirmation"); } id = val.GetZDO().m_uid; hash = ItemHash(val.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null)); token = journal.InspectPending().Operation; if (hash == null) { throw new InvalidOperationException("Container save unavailable"); } if (!ZNet.instance.IsServer() && (!StorageReady || !serverConfirmation || connection == null)) { throw new InvalidOperationException("Server transfer confirmation unavailable"); } } catch (Exception ex) { error = ex.Message; } if (error != null) { result.Error = error; yield break; } confirmationToken = token; confirmationReceived = (confirmationGranted = ZNet.instance.IsServer()); try { if (!ZNet.instance.IsServer()) { try { ZDOMan.instance.ForceSendZDO(peer.m_uid, id); ZPackage val2 = new ZPackage(); val2.Write(token); val2.Write(id); val2.Write(hash); connection.Invoke("VCQD_ObserveV1", new object[1] { val2 }); } catch (Exception ex) { error = ex.Message; } if (error != null) { result.Error = "Confirmation interrupted: " + error; yield break; } float deadline = Time.realtimeSinceStartup + 5f; while (!confirmationReceived && Time.realtimeSinceStartup < deadline && StorageReady && storageServer == connection && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead()) { yield return null; } } bool session = (Object)(object)ZNet.instance != (Object)null && (ZNet.instance.IsServer() || (StorageReady && storageServer == connection)); if (confirmationReceived && confirmationGranted && session && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead()) { try { journal.Finish(applied); result.Committed = true; result.NeedsRecovery = false; yield break; } catch (Exception ex) { result.Error = ex.Message; yield break; } } result.Error = "Server did not confirm the chest update. Pending evidence retained; no automatic retry or refund."; } finally { confirmationToken = null; } } private static Action<bool> DeferCompletion(TransferJournal journal) { return delegate(bool applied) { if (!applied) { journal.Finish(committed: false); } }; } private IEnumerator CheckRecovery(Player player, TransferJournal journal) { List<KeyValuePair<Container, string>> acquired = new List<KeyValuePair<Container, string>>(); string outcome = "Recovery needs manual review; no items changed."; try { TransferJournal.PendingRecord record = journal.InspectPending(); string playerId = "player:" + player.GetPlayerID(); if (!record.Before.Any((InventoryRecord i) => i.Id == playerId)) { Notify("Recovery belongs to another character."); yield break; } List<InventoryRecord> snapshots = new List<InventoryRecord>(); bool available = true; foreach (InventoryRecord endpoint in record.Before) { if (!(endpoint.Id == playerId)) { Container chest = ((IEnumerable<Container>)ChestScanner.Containers).FirstOrDefault((Func<Container, bool>)((Container c) => (Object)(object)c != (Object)null && ContainerStorage.Identity(c) == endpoint.Id && ChestScanner.Accessible(c, player, MaximumRadius()))); if ((Object)(object)chest == (Object)null) { available = false; break; } ChestOwnership.Acquisition grant = new ChestOwnership.Acquisition(); yield return ownership.Acquire(chest, player, grant); if (!grant.Granted) { available = false; break; } acquired.Add(new KeyValuePair<Container, string>(chest, grant.Operation)); } } if (available && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && acquired.All((KeyValuePair<Container, string> p) => ChestOwnership.Holds(p.Key, p.Value) && ChestScanner.Accessible(p.Key, player, MaximumRadius()))) { snapshots.Add(ContainerStorage.CapturePlayer(player, bridge, favorites)); snapshots.AddRange(acquired.Select((KeyValuePair<Container, string> p) => NativeTransfer.Capture(ContainerStorage.Identity(p.Key), p.Key.GetInventory(), bridge, 0L))); if (acquired.Count == 1 && journal.MatchPending(snapshots, out var committed)) { NativeTransferResult result = new NativeTransferResult { Committed = true }; yield return ConfirmTransfer(player, acquired[0].Key, journal, result, committed); if (result.Committed) { outcome = (committed ? "Previous transfer verified complete. Press again for a new action." : "Previous transfer verified unchanged. Press again to try again."); } } } else { outcome = "Return within reach of the original chest to check the pending transfer."; } } finally { foreach (KeyValuePair<Container, string> item in acquired) { ChestOwnership.Release(item.Key, item.Value); } actionRunning = false; Notify(outcome); } } private bool ViewingShared(Container c) { return (Object)(object)c != (Object)null && (Object)(object)c == (Object)(object)sharedView && (Object)(object)InventoryGui.instance != (Object)null && (Object)/*isinst with value type is only supported in some contexts*/ == (Object)(object)c; } internal bool CanEditShared(Container c) { return SharedEnabled && ViewingShared(c) && SharedAccess(c, Player.m_localPlayer) && !actionRunning; } internal bool SettingShared(Container c) { return (Object)(object)c != (Object)null && (Object)(object)sharedSettingLease == (Object)(object)c; } private void QueueSharedSetting(Container c, Func<Container, bool> change) { if (CanEditShared(c)) { actionRunning = true; ((MonoBehaviour)this).StartCoroutine(RunShared(c, null, "Chest setting updated.", change)); } } private void SetupSharedChests() { sharedChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Shared chests", "Enabled", true, "Allow simultaneous chest viewing. Every item change takes an exclusive short storage lease and validates the selected stacks. Server controls this setting."); } private void InstallSharedHooks() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_004f: Expected O, but got Unknown patches.Patch((MethodBase)AccessTools.Method(typeof(InventoryGui), "UpdateContainer", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "BeginSharedRender", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "EndSharedRender", (Type[])null), (HarmonyMethod)null); Hook(typeof(Container), "IsOwner", null, "SharedRenderOwner"); Hook(typeof(Container), "SetInUse", "SharedSetInUse", null); Hook(typeof(InventoryGui), "OnSelectedItem", "SharedSelect", null); Hook(typeof(InventoryGui), "OnTakeAll", "SharedTakeAll", null); Hook(typeof(InventoryGui), "OnStackAll", "SharedStackAll", null); Hook(typeof(Container), "StackAll", "SharedHoldStack", null); Hook(typeof(InventoryGui), "OnDropOutside", "SharedDropOutside", null); Hook(typeof(InventoryGui), "OnRightClickItem", "SharedRightClick", null); } private bool OpenShared(Container chest, Humanoid user, bool hold, ref bool result) { if (!SharedEnabled || (Object)(object)user != (Object)(object)Player.m_localPlayer || (Object)(object)chest == (Object)null || (Object)(object)((Component)chest).GetComponent<TombStone>() != (Object)null || (Object)(object)((Component)chest).GetComponentInParent<Player>() != (Object)null) { return false; } result = false; if (hold) { return true; } if (!SharedAccess(chest, Player.m_localPlayer) || chest.IsInUse()) { Notify("Chest busy or inaccessible."); return true; } if ((Object)(object)InventoryGui.instance == (Object)null) { return true; } try { sharedView = chest; sharedRefresh = 0f; ChestOwnership.Refresh(chest); InventoryGui.instance.Show(chest, 1); result = true; } catch (Exception ex) { sharedView = null; Notify("Unable to open shared storage."); Warn("Shared open failed: " + ex.Message); } return true; } private bool SharedAccess(Container chest, Player player) { return (Object)(object)player != (Object)null && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && ChestScanner.Accessible(chest, player, Math.Max(0f, player.m_maxInteractDistance) + Math.Max(0f, ((Object)(object)chest == (Object)null) ? 0f : chest.m_hoverOffset)) && (!((Object)(object)chest.m_wagon != (Object)null) || !chest.m_wagon.InUse()); } private static void BeginSharedRender(InventoryGui __instance) { Plugin instance = Instance; renderingShared = null; if ((Object)(object)instance == (Object)null || !instance.ViewingShared(instance.sharedView)) { return; } if (!instance.SharedEnabled || !instance.SharedAccess(instance.sharedView, Player.m_localPlayer)) { AccessTools.Method(typeof(InventoryGui), "CloseContainer", (Type[])null, (Type[])null).Invoke(__instance, null); instance.sharedView = null; return; } renderingShared = instance.sharedView; if (!(Time.realtimeSinceStartup >= instance.sharedRefresh) || instance.actionRunning) { return; } instance.sharedRefresh = Time.realtimeSinceStartup + 0.25f; try { ChestOwnership.Refresh(instance.sharedView); } catch (Exception ex) { instance.Warn("Shared storage refresh failed: " + ex.Message); AccessTools.Method(typeof(InventoryGui), "CloseContainer", (Type[])null, (Type[])null).Invoke(__instance, null); instance.sharedView = null; renderingShared = null; } } private static Exception EndSharedRender(Exception __exception) { renderingShared = null; return __exception; } private static void SharedRenderOwner(Container __instance, ref bool __result) { if ((Object)(object)__instance == (Object)(object)renderingShared) { __result = true; } } private static bool SharedSetInUse(Container __instance) { return (Object)(object)Instance == (Object)null || !Instance.ViewingShared(__instance); } private static bool SharedSelect(InventoryGui __instance, InventoryGrid grid, ItemData item, Vector2i pos, Modifier mod) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Invalid comparison between Unknown and I4 //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Invalid comparison between Unknown and I4 //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Instance; if ((Object)(object)instance == (Object)null || !instance.ViewingShared(instance.sharedView)) { return true; } Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory(); Inventory inventory2 = instance.sharedView.GetInventory(); object? value = DragItem.GetValue(__instance); ItemData val = (ItemData)((value is ItemData) ? value : null); object? value2 = DragInventory.GetValue(__instance); Inventory val2 = (Inventory)((value2 is Inventory) ? value2 : null); if (instance.actionRunning) { return false; } if (val != null && (val2 == inventory2 || grid.GetInventory() == inventory2)) { instance.QueueSharedMove(val2, grid.GetInventory(), val, pos, (int)DragAmount.GetValue(__instance), drop: false); return false; } if (item == null) { return true; } if ((int)mod == 2 && (grid.GetInventory() == inventory2 || grid.GetInventory() == inventory)) { instance.QueueSharedMove(grid.GetInventory(), (grid.GetInventory() == inventory2) ? inventory : inventory2, item, new Vector2i(-1, -1), item.m_stack, drop: false); return false; } if ((int)mod == 3 && grid.GetInventory() == inventory2) { instance.QueueSharedMove(inventory2, inventory, item, new Vector2i(-1, -1), item.m_stack, drop: true); return false; } return true; } private static bool SharedDropOutside(InventoryGui __instance) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Instance; if ((Object)(object)instance == (Object)null || !instance.ViewingShared(instance.sharedView)) { return true; } object? value = DragItem.GetValue(__instance); ItemData val = (ItemData)((value is ItemData) ? value : null); object? value2 = DragInventory.GetValue(__instance); Inventory val2 = (Inventory)((value2 is Inventory) ? value2 : null); if (val2 != instance.sharedView.GetInventory() || val == null) { return true; } instance.QueueSharedMove(val2, ((Humanoid)Player.m_localPlayer).GetInventory(), val, new Vector2i(-1, -1), (int)DragAmount.GetValue(__instance), drop: true); return false; } private static bool SharedRightClick(InventoryGrid grid, ItemData item) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Instance; if ((Object)(object)instance == (Object)null || !instance.ViewingShared(instance.sharedView) || grid.GetInventory() != instance.sharedView.GetInventory()) { return true; } if (item != null) { instance.QueueSharedMove(grid.GetInventory(), ((Humanoid)Player.m_localPlayer).GetInventory(), item, new Vector2i(-1, -1), item.m_stack, drop: false, use: true); } return false; } private static bool SharedTakeAll() { Plugin instance = Instance; if ((Object)(object)instance == (Object)null || !instance.ViewingShared(instance.sharedView)) { return true; } instance.QueueShared((InventoryRecord bag, InventoryRecord box) => SharedChestPlan.All(bag, box, take: true, existingOnly: false)); return false; } private static bool SharedStackAll() { Plugin instance = Instance; if ((Object)(object)instance == (Object)null || !instance.ViewingShared(instance.sharedView)) { return true; } instance.QueueShared((InventoryRecord bag, InventoryRecord box) => SharedChestPlan.All(bag, box, take: false, existingOnly: true)); return false; } private static bool SharedHoldStack(Container __instance) { return (Object)(object)Instance == (Object)null || !Instance.ViewingShared(__instance) || SharedStackAll(); } private void QueueSharedMove(Inventory source, Inventory target, ItemData item, Vector2i pos, int amount, bool drop, bool use = false) { if (actionRunning || item == null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } try { Inventory inventory = sharedView.GetInventory(); Inventory bag = ((Humanoid)Player.m_localPlayer).GetInventory(); if ((source != inventory && source != bag) || (target != inventory && target != bag)) { return; } int from = item.m_gridPos.y * source.GetWidth() + item.m_gridPos.x; int to = ((pos.x < 0) ? (-1) : (pos.y * target.GetWidth() + pos.x)); ItemData val = ((to < 0) ? null : target.GetItemAt(pos.x, pos.y)); string identity = NativeSnapshot.Capture(item, bridge).Identity; string targetId = ((val == null) ? null : NativeSnapshot.Capture(val, bridge).Identity); int count = item.m_stack; int targetCount = val?.m_stack ?? 0; bool fromBox = source == inventory; bool toBox = target == inventory; if (fromBox == toBox && from == to) { return; } QueueShared((InventoryRecord b, InventoryRecord c) => SharedChestPlan.Move(b, c, fromBox, toBox, from, to, amount, identity, count, targetId, targetCount), null, (drop || use) ? ((Action<TransferBatch>)delegate(TransferBatch batch) { Player player = Player.m_localPlayer; if ((Object)(object)player == (Object)null || ((Humanoid)player).GetInventory() != bag) { return; } foreach (TransferLine item2 in batch.Lines.Where((TransferLine l) => l.Destination == "player:" + player.GetPlayerID())) { ItemData itemAt = bag.GetItemAt(item2.DestinationSlot % bag.GetWidth(), item2.DestinationSlot / bag.GetWidth()); if (itemAt != null) { if (!drop) { ((Humanoid)player).UseItem(bag, itemAt, true); break; } ((Humanoid)player).DropItem(bag, itemAt, item2.Count); } } }) : null); } catch (Exception ex) { Notify("Unable to prepare that item transfer."); Warn("Shared transfer preparation failed: " + ex.Message); } } private void QueueShared(Func<InventoryRecord, InventoryRecord, TransferBatch> plan, string message = null, Action<TransferBatch> after = null) { if (actionRunning) { Notify("Storage request pending…"); } else if (SharedEnabled && ViewingShared(sharedView)) { actionRunning = true; ((MonoBehaviour)this).StartCoroutine(RunShared(sharedView, plan, message, null, after)); } } private IEnumerator RunShared(Container chest, Func<InventoryRecord, InventoryRecord, TransferBatch> plan, string message, Func<Container, bool> setting = null, Action<TransferBatch> after = null) { Player player = Player.m_localPlayer; TransferJournal journal = null; string error = null; try { journal = Journal(player); } catch (Exception ex) { error = ex.Message; } if (error != null) { actionRunning = false; Notify(error); yield break; } if (journal.HasPending) { yield return CheckRecovery(player, journal); yield break; } ChestOwnership.Acquisition lease = new ChestOwnership.Acquisition(); try { yield return ownership.Acquire(chest, player, lease); if (!lease.Granted) { Notify(lease.Error); yield break; } NativeTransferResult result = null; TransferBatch batch = null; try { if (!ViewingShared(chest) || !SharedAccess(chest, player) || (Object)(object)player != (Object)(object)Player.m_localPlayer) { yield break; } if (setting != null) { sharedSettingLease = chest; try { Notify(setting(chest) ? message : "Chest setting could not be changed."); yield break; } finally { sharedSettingLease = null; } } Func<InventoryRecord> func = () => NativeTransfer.Capture("player:" + player.GetPlayerID(), ((Humanoid)player).GetInventory(), bridge, 0L); Func<InventoryRecord> func2 = () => NativeTransfer.Capture(ContainerStorage.Identity(chest), chest.GetInventory(), bridge, 0L); batch = plan(func(), func2()); if (batch.ItemCount == 0) { Notify("Nothing to move."); yield break; } result = NativeTransfer.Execute(batch, new NativeInventoryEndpoint[2] { new NativeInventoryEndpoint(batch.Before[0].Id, ((Humanoid)player).GetInventory(), () => StorageReady && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && !((Character)player).IsTeleporting(), func, allowPickupMark: true), new NativeInventoryEndpoint(batch.Before[1].Id, chest.GetInventory(), () => ChestOwnership.Holds(chest, lease.Operation) && SharedAccess(chest, player), func2) }, journal.Prepare, DeferCompletion(journal)); if ((Object)(object)InventoryGui.instance != (Object)null) { ClearDrag.Invoke(InventoryGui.instance, new object[3] { null, null, 1 }); } } catch (Exception ex) { if ((Object)(object)InventoryGui.instance != (Object)null) { ClearDrag.Invoke(InventoryGui.instance, new object[3] { null, null, 1 }); } Notify(ex.Message); } if (result == null) { yield break; } yield return ConfirmTransfer(player, chest, journal, result); try { if (result.Committed) { InvalidateMaterials(); scanner.Invalidate(chest); Notify(message ?? ("Moved " + result.Items + " items.")); after?.Invoke(batch); } else { Notify(result.NeedsRecovery ? "Transfer outcome needs recovery. Do not retry; reopen storage after checking inventories." : result.Error); } } catch (Exception ex) { if ((Object)(object)InventoryGui.instance != (Object)null) { ClearDrag.Invoke(InventoryGui.instance, new object[3] { null, null, 1 }); } Notify(ex.Message); } } finally { ChestOwnership.Release(chest, lease.Operation); actionRunning = false; } } private float ConfiguredRadius(int kind, float local) { return ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && StorageReady && serverRanges != null) ? serverRanges[kind] : local; } private void TickStorageNetwork() { TickObservations(); if ((Object)(object)ZNet.instance == (Object)null) { serverConfirmation = false; storageServer = null; serverRanges = null; storageConnections.Clear(); } else { if (Time.realtimeSinceStartup < nextNetworkScan) { return; } nextNetworkScan = Time.realtimeSinceStartup + 0.5f; List<ZNetPeer> peers = ZNet.instance.GetPeers(); bool flag = ZNet.instance.IsServer(); ZRpc[] array = storageConnections.Keys.Where((ZRpc r) => !peers.Any((ZNetPeer p) => p.m_rpc == r)).ToArray(); foreach (ZRpc key in array) { storageConnections.Remove(key); } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (!flag && (serverPeer == null || serverPeer.m_rpc != storageServer)) { storageServer = null; serverRanges = null; } foreach (ZNetPeer item in peers) { if (item.m_rpc == null) { continue; } if (!storageConnections.ContainsKey(item.m_rpc)) { storageConnections.Add(item.m_rpc, 0f); if (flag) { item.m_rpc.Register<int>("VCQD_HelloV1", (Action<ZRpc, int>)ReceiveStorageHello); item.m_rpc.Register<string>("