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 BottomlessChest v0.1.0
BottomlessChest.Logic.dll
Decompiled 20 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("BottomlessChest.Logic")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BottomlessChest.Logic")] [assembly: AssemblyTitle("BottomlessChest.Logic")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } } namespace BottomlessChest.Logic { public readonly struct GridPos : IEquatable<GridPos> { public int X { get; } public int Y { get; } public GridPos(int x, int y) { X = x; Y = y; } public bool Equals(GridPos other) { if (X == other.X) { return Y == other.Y; } return false; } public override bool Equals(object obj) { if (obj is GridPos other) { return Equals(other); } return false; } public override int GetHashCode() { return (X * 397) ^ Y; } public override string ToString() { return $"({X}, {Y})"; } } public static class GridPacker { public static GridPos PositionOf(int index, int width) { RequirePositiveWidth(width); return new GridPos(index % width, index / width); } public static int RowsNeeded(int count, int width) { RequirePositiveWidth(width); if (count > 0) { return (count - 1) / width + 1; } return 0; } public static int RowsForChest(int count, int width, int minRows) { RequirePositiveWidth(width); int num = RowsNeeded(count, width) + 1; int num2 = ((minRows < 1) ? 1 : minRows); if (num <= num2) { return num2; } return num; } public static bool LayoutFitsWindow(IReadOnlyList<GridPos> positions, int width, int rows) { RequirePositiveWidth(width); if (rows < 1 || positions == null) { return false; } if (positions.Count > width * rows) { return false; } HashSet<int> hashSet = new HashSet<int>(); foreach (GridPos position in positions) { if (position.X < 0 || position.X >= width || position.Y < 0 || position.Y >= rows) { return false; } if (!hashSet.Add(position.Y * width + position.X)) { return false; } } return true; } private static void RequirePositiveWidth(int width) { if (width <= 0) { throw new ArgumentOutOfRangeException("width", width, "Grid width must be at least 1."); } } } public interface IStorableItem { string ItemId { get; } string DisplayName { get; } string SearchKey { get; } ItemKind Kind { get; } int Stack { get; } int MaxStackSize { get; } int Quality { get; } int Variant { get; } string CustomData { get; } } public enum ItemKind { Unknown, Material, Food, Weapon, Armor, Ammo, Tool, Trophy, Misc } public sealed class ItemOrdering : IComparer<IStorableItem> { public static ItemOrdering ByName { get; } = new ItemOrdering(); public int Compare(IStorableItem a, IStorableItem b) { if (a == b) { return 0; } if (a == null) { return -1; } if (b == null) { return 1; } int num = string.CompareOrdinal(a.SearchKey ?? string.Empty, b.SearchKey ?? string.Empty); if (num != 0) { return num; } int num2 = b.Quality.CompareTo(a.Quality); if (num2 != 0) { return num2; } return b.Stack.CompareTo(a.Stack); } } public sealed class ItemQuery { private static readonly Dictionary<string, ItemKind> KindTokens = new Dictionary<string, ItemKind>(StringComparer.Ordinal) { { "@material", ItemKind.Material }, { "@food", ItemKind.Food }, { "@weapon", ItemKind.Weapon }, { "@armor", ItemKind.Armor }, { "@ammo", ItemKind.Ammo }, { "@tool", ItemKind.Tool }, { "@trophy", ItemKind.Trophy }, { "@misc", ItemKind.Misc } }; private readonly List<string> _textTerms; private readonly List<ItemKind> _kindTerms; public bool IsEmpty { get { if (_textTerms.Count == 0) { return _kindTerms.Count == 0; } return false; } } private ItemQuery(List<string> textTerms, List<ItemKind> kindTerms) { _textTerms = textTerms; _kindTerms = kindTerms; } public static ItemQuery Parse(string query) { List<string> list = new List<string>(); List<ItemKind> list2 = new List<ItemKind>(); if (!string.IsNullOrWhiteSpace(query)) { string[] array = query.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = TextKey.Of(array[i]); if (text.Length != 0) { if (KindTokens.TryGetValue(text, out var value)) { list2.Add(value); } else { list.Add(text); } } } } return new ItemQuery(list, list2); } public bool Matches(IStorableItem item) { if (IsEmpty) { return true; } if (_kindTerms.Count > 0 && !_kindTerms.Contains(item.Kind)) { return false; } if (_textTerms.Count == 0) { return true; } string text = item.SearchKey ?? string.Empty; foreach (string textTerm in _textTerms) { if (text.IndexOf(textTerm, StringComparison.Ordinal) < 0) { return false; } } return true; } } public sealed class StackPolicy { public bool Unlimited { get; } public int Multiplier { get; } public StackPolicy(bool unlimited, int multiplier) { Unlimited = unlimited; Multiplier = multiplier; } } public static class StackRules { public static bool CanMerge(IStorableItem a, IStorableItem b) { if (a == null || b == null) { return false; } if (a.MaxStackSize <= 1 || b.MaxStackSize <= 1) { return false; } if (string.Equals(a.ItemId, b.ItemId, StringComparison.Ordinal) && a.Quality == b.Quality && a.Variant == b.Variant) { return string.Equals(a.CustomData ?? string.Empty, b.CustomData ?? string.Empty, StringComparison.Ordinal); } return false; } public static int EffectiveStackLimit(IStorableItem item, StackPolicy policy) { if (policy.Unlimited) { return int.MaxValue; } long num = (long)item.MaxStackSize * (long)policy.Multiplier; if (num < item.MaxStackSize) { return item.MaxStackSize; } if (num <= int.MaxValue) { return (int)num; } return int.MaxValue; } public static IReadOnlyList<int> SplitForExit(int stack, int maxStackSize) { if (maxStackSize <= 0) { throw new ArgumentOutOfRangeException("maxStackSize", maxStackSize, "Stack ceiling must be at least 1."); } List<int> list = new List<int>(); int num = stack; while (num > 0) { int num2 = Math.Min(num, maxStackSize); list.Add(num2); num -= num2; } return list; } } public static class TextKey { public static string Of(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } string text = value.Normalize(NormalizationForm.FormD); StringBuilder stringBuilder = new StringBuilder(text.Length); string text2 = text; foreach (char c in text2) { if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) { stringBuilder.Append(c); } } return stringBuilder.ToString().Normalize(NormalizationForm.FormC).ToLowerInvariant(); } } }
BottomlessChest.dll
Decompiled 20 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.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BottomlessChest.Commands; using BottomlessChest.Core; using BottomlessChest.Filter; using BottomlessChest.Logic; using BottomlessChest.Net; using BottomlessChest.Piece; using BottomlessChest.Settings; using BottomlessChest.Storage; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("BottomlessChest")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("BottomlessChest")] [assembly: AssemblyTitle("BottomlessChest")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] namespace BottomlessChest { [BepInPlugin("com.myrridin.bottomlesschest", "BottomlessChest", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.myrridin.bottomlesschest"; public const string PluginName = "BottomlessChest"; public const string PluginVersion = "0.1.0"; private Harmony _harmony; internal static ManualLogSource Log { get; private set; } internal static bool Degraded { get; private set; } private void Awake() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { ModConfig.Bind(((BaseUnityPlugin)this).Config); } catch (Exception arg) { Log.LogError((object)$"Could not read configuration, falling back to defaults: {arg}"); } try { BottomlessChestPiece.Register(); } catch (Exception arg2) { Log.LogError((object)("CRITICAL: the chest prefab could not be registered. " + $"Existing chests will be removed by the game. {arg2}")); } try { _harmony = new Harmony("com.myrridin.bottomlesschest"); _harmony.PatchAll(typeof(Plugin).Assembly); } catch (Exception arg3) { Degraded = true; Log.LogError((object)("Patching failed - running in degraded mode. Chests will behave " + $"as ordinary containers; their stored contents are untouched. {arg3}")); try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception arg4) { Log.LogError((object)$"Could not roll back partial patches: {arg4}"); } } try { ChestRpc.Register(); } catch (Exception arg5) { Log.LogError((object)$"Could not register networking; multiplayer chests will not sync: {arg5}"); } try { CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BottomlessCommand()); } catch (Exception arg6) { Log.LogError((object)$"Could not register console commands: {arg6}"); } try { WorldSaveHooks.Install(SidecarStore.Instance); } catch (Exception arg7) { Log.LogError((object)$"Could not hook world saving: {arg7}"); } Log.LogInfo((object)("BottomlessChest 0.1.0 loaded" + (Degraded ? " (DEGRADED)" : string.Empty) + ".")); } private void OnDestroy() { ItemAdapter.ClearCache(); ChestSessions.PersistAll(); ChestSessions.Clear(); WorldSaveHooks.Uninstall(); SidecarStore.Instance.Flush(); SidecarStore.Instance.Unload(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace BottomlessChest.Storage { internal static class FastInventoryReader { private const int SupportedVersion = 106; internal static bool TryLoad(Inventory inventory, byte[] contents, out int expected) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) expected = 0; if (contents == null || contents.Length < 8 || (Object)(object)ObjectDB.instance == (Object)null) { return false; } List<ItemData> list; try { ZPackage val = new ZPackage(contents); if (val.ReadInt() != 106) { return false; } expected = val.ReadInt(); list = new List<ItemData>(expected); for (int i = 0; i < expected; i++) { string text = val.ReadString(); int stack = val.ReadInt(); float durability = val.ReadSingle(); Vector2i gridPos = val.ReadVector2i(); bool equipped = val.ReadBool(); int quality = val.ReadInt(); int variant = val.ReadInt(); long crafterID = val.ReadLong(); string crafterName = val.ReadString(); int num = val.ReadInt(); Dictionary<string, string> dictionary = null; for (int j = 0; j < num; j++) { dictionary = dictionary ?? new Dictionary<string, string>(num); dictionary[val.ReadString()] = val.ReadString(); } int worldLevel = val.ReadInt(); bool pickedUp = val.ReadBool(); if (string.IsNullOrEmpty(text)) { continue; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text); ItemDrop val2 = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val2 == (Object)null || val2.m_itemData?.m_shared == null) { Plugin.Log.LogWarning((object)("Skipping unknown item prefab '" + text + "'.")); continue; } ItemData val3 = val2.m_itemData.Clone(); val3.m_dropPrefab = itemPrefab; val3.m_stack = stack; val3.m_durability = durability; val3.m_gridPos = gridPos; val3.m_equipped = equipped; val3.m_quality = quality; val3.m_variant = variant; val3.m_crafterID = crafterID; val3.m_crafterName = crafterName; val3.m_worldLevel = worldLevel; val3.m_pickedUp = pickedUp; if (dictionary != null) { val3.m_customData = dictionary; } list.Add(val3); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Fast inventory read failed, falling back to the game's loader: " + ex.Message)); return false; } inventory.m_inventory.Clear(); inventory.m_inventory.AddRange(list); return true; } } internal interface IChestStore { int Count { get; } bool IsReady { get; } bool TryGet(string storeId, out byte[] contents); void Put(string storeId, byte[] contents); void Flush(); void Unload(); } internal sealed class SidecarStore : IChestStore { private sealed class Entry { internal byte[] Contents; internal long LastWrittenUtcTicks; } private const int FormatVersion = 1; private const uint Magic = 1112294193u; private const string Extension = ".bottomless.dat"; private readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry>(StringComparer.Ordinal); private string _loadedWorld; private bool _dirty; private bool _warnedNotAuthority; internal static SidecarStore Instance { get; } = new SidecarStore(); public int Count => _entries.Count; internal static bool IsServerAuthority { get { ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null)) { return instance.IsServer(); } return true; } } public bool IsReady { get { if (!IsServerAuthority) { return false; } EnsureLoaded(); World world = ZNet.m_world; if (world != null) { return _loadedWorld == world.m_fileName; } return false; } } internal IEnumerable<(string StoreId, int StackCount)> Describe() { EnsureLoaded(); foreach (KeyValuePair<string, Entry> entry in _entries) { int item; try { ZPackage val = new ZPackage(entry.Value.Contents); val.ReadInt(); item = val.ReadInt(); } catch { item = -1; } yield return (StoreId: entry.Key, StackCount: item); } } public bool TryGet(string storeId, out byte[] contents) { EnsureLoaded(); if (_entries.TryGetValue(storeId, out var value)) { contents = value.Contents; return true; } contents = null; return false; } public void Put(string storeId, byte[] contents) { EnsureLoaded(); _entries[storeId] = new Entry { Contents = contents, LastWrittenUtcTicks = DateTime.UtcNow.Ticks }; _dirty = true; } public void Unload() { _entries.Clear(); _loadedWorld = null; _dirty = false; } private void EnsureLoaded() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) if (!IsServerAuthority) { if (!_warnedNotAuthority) { _warnedNotAuthority = true; Plugin.Log.LogWarning((object)"Connected to a remote server. Chest contents live on the server and client-side sync is not implemented yet, so chests will read as empty here. Nothing is written locally and nothing stored is at risk."); } return; } World world = ZNet.m_world; if (world != null && !(_loadedWorld == world.m_fileName)) { _entries.Clear(); _dirty = false; _loadedWorld = world.m_fileName; string text = SavePath(world); if (!TryRead(text, world.m_fileSource) && !TryRead(text + ".old", world.m_fileSource) && !TryRead(text + ".old2", world.m_fileSource)) { Plugin.Log.LogInfo((object)("No existing chest store for world '" + world.m_fileName + "'. Starting empty.")); } } } private static string SavePath(World world) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return World.GetWorldSavePath(world.m_fileSource) + "/" + world.m_fileName + ".bottomless.dat"; } private bool TryRead(string path, FileSource source) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (!FileHelpers.Exists(path, source)) { return false; } FileReader val = null; try { val = new FileReader(path, source, (FileHelperType)0); BinaryReader binary = val.m_binary; if (binary.ReadUInt32() != 1112294193) { throw new InvalidDataException("bad magic - not a bottomless chest store"); } int num = binary.ReadInt32(); if (num > 1) { throw new InvalidDataException($"file is format v{num}, this build understands up to v{1}"); } int num2 = binary.ReadInt32(); for (int i = 0; i < num2; i++) { string text = binary.ReadString(); long lastWrittenUtcTicks = binary.ReadInt64(); int num3 = binary.ReadInt32(); byte[] array = binary.ReadBytes(num3); if (array.Length != num3) { throw new EndOfStreamException("entry '" + text + "' is truncated"); } _entries[text] = new Entry { Contents = array, LastWrittenUtcTicks = lastWrittenUtcTicks }; } Plugin.Log.LogInfo((object)$"Loaded {_entries.Count} chest store(s) from {Path.GetFileName(path)}."); return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Could not read chest store '" + path + "': " + ex.Message)); _entries.Clear(); return false; } finally { if (val != null) { val.Dispose(); } } } public void Flush() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0140: 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) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) if (!_dirty) { return; } World world = ZNet.m_world; if (world == null || _loadedWorld != world.m_fileName) { return; } Stopwatch stopwatch = Stopwatch.StartNew(); string text = SavePath(world); string text2 = text + ".new"; string text3 = text + ".old"; FileWriter val = null; try { FileHelpers.EnsureDirectoryExists(World.GetWorldSavePath(world.m_fileSource)); val = new FileWriter(text2, (FileHelperType)0, world.m_fileSource); BinaryWriter binary = val.m_binary; binary.Write(1112294193u); binary.Write(1); binary.Write(_entries.Count); foreach (KeyValuePair<string, Entry> entry in _entries) { binary.Write(entry.Key); binary.Write(entry.Value.LastWrittenUtcTicks); binary.Write(entry.Value.Contents.Length); binary.Write(entry.Value.Contents); } val.Finish(); val = null; if (FileHelpers.Exists(text3, world.m_fileSource)) { string text4 = text + ".old2"; if (FileHelpers.Exists(text4, world.m_fileSource)) { FileHelpers.Delete(text4, world.m_fileSource); } FileHelpers.Copy(text3, world.m_fileSource, text4, world.m_fileSource); } FileHelpers.ReplaceOldFile(text, text2, text3, world.m_fileSource); _dirty = false; long num = 0L; foreach (Entry value in _entries.Values) { num += value.Contents.Length; } Plugin.Log.LogInfo((object)$"Saved {_entries.Count} chest store(s), {num / 1024}KB, in {stopwatch.ElapsedMilliseconds}ms."); } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to save chest store: {arg}"); if (val != null) { val.Finish(); } } } } internal static class WorldSaveHooks { private static Action _onSaveStarted; internal static void Install(IChestStore store) { _onSaveStarted = delegate { try { ChestSessions.PersistAll(); ChestSessions.ReleaseIdle(TimeSpan.FromMinutes(10.0)); store.Flush(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Chest store flush failed during world save: {arg}"); } }; ZNet.WorldSaveStarted = (Action)Delegate.Combine(ZNet.WorldSaveStarted, _onSaveStarted); } internal static void Uninstall() { if (_onSaveStarted != null) { ZNet.WorldSaveStarted = (Action)Delegate.Remove(ZNet.WorldSaveStarted, _onSaveStarted); _onSaveStarted = null; } } } } namespace BottomlessChest.Settings { internal static class ModConfig { internal static ConfigEntry<string> RecipeRequirements; internal static ConfigEntry<float> SnapshotDebounceSeconds; internal static ConfigEntry<float> ModelScale; internal static ConfigEntry<string> BodyTint; internal static ConfigEntry<string> LidTint; internal static ConfigEntry<string> GlowColour; internal static ConfigEntry<float> GlowStrength; internal static void Bind(ConfigFile cfg) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Expected O, but got Unknown ModelScale = cfg.Bind<float>("Appearance", "ModelScale", 1f, new ConfigDescription("Scale multiplier on the chest model, relative to the vanilla personal chest - which is already small. Colliders scale too, so low values make it fiddly to click. Requires a restart.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 3f), Array.Empty<object>())); SnapshotDebounceSeconds = cfg.Bind<float>("Multiplayer", "SnapshotDebounceSeconds", 1.5f, new ConfigDescription("How long to wait after the last change before sending a chest's contents to the server. Batches a burst of item moves into one message.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>())); BodyTint = cfg.Bind<string>("Appearance", "BodyTint", "#C4C2BC", "Hex colour multiplied over the chest body. Darker values read as a solid object; avoid blue-cyan, which is the colour of the placement ghost."); LidTint = cfg.Bind<string>("Appearance", "LidTint", "#2E2E33", "Hex colour multiplied over the chest lid. Darker than the body makes the glow read against it."); GlowColour = cfg.Bind<string>("Appearance", "GlowColour", "#8FA86B", "Hex colour of the emissive glow on the chest lid."); GlowStrength = cfg.Bind<float>("Appearance", "GlowStrength", 0.35f, new ConfigDescription("Multiplier on the lid glow. Around 0.5 is a subtle sheen; above 1 pushes it into bloom and reads as a light source.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); RecipeRequirements = cfg.Bind<string>("Crafting", "Requirements", "FineWood:20,BlackMetal:10,SurtlingCore:5", "Build cost, as Item:Amount pairs separated by commas. Item names are prefab names, not display names."); } internal static RequirementConfig[] ParseRequirements() { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown List<RequirementConfig> list = new List<RequirementConfig>(); string[] array = RecipeRequirements.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { string[] array2 = text.Split(new char[1] { ':' }); if (array2.Length != 2 || !int.TryParse(array2[1].Trim(), out var result) || result <= 0) { Plugin.Log.LogWarning((object)("Ignoring malformed build requirement '" + text.Trim() + "'. Expected Item:Amount.")); continue; } list.Add(new RequirementConfig { Item = array2[0].Trim(), Amount = result, Recover = true }); } return list.ToArray(); } } } namespace BottomlessChest.Piece { internal static class BottomlessChestPiece { internal const string PrefabName = "bottomless_chest"; private const string DisplayNameToken = "$piece_bottomlesschest"; private const string BasePrefabName = "piece_chest_private"; private static readonly Color FallbackTint = new Color(0.77f, 0.76f, 0.74f); private static readonly Color FallbackLid = new Color(0.18f, 0.18f, 0.2f); private static readonly Color FallbackGlow = new Color(0.56f, 0.66f, 0.42f); private static readonly string[] LidMarkers = new string[4] { "top", "lid", "open", "closed" }; private static Color ReadColour(string hex, Color fallback) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); if (!string.IsNullOrWhiteSpace(hex) && ColorUtility.TryParseHtmlString(hex.Trim(), ref result)) { return result; } Plugin.Log.LogWarning((object)("Could not read colour '" + hex + "'; using the default instead.")); return fallback; } internal static void Register() { PrefabManager.OnVanillaPrefabsAvailable += AddPiece; CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); string text = "English"; localization.AddTranslation(ref text, new Dictionary<string, string> { { "piece_bottomlesschest", "Bottomless Chest" }, { "piece_bottomlesschest_desc", "Holds everything. Type to find it again." }, { "bottomless_search", "Search..." }, { "bottomless_toolarge", "This chest holds too much to open over the network." } }); } private static void AddPiece() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) PrefabManager.OnVanillaPrefabsAvailable -= AddPiece; try { PieceConfig val = new PieceConfig { Name = "$piece_bottomlesschest", Description = "$piece_bottomlesschest_desc", PieceTable = PieceTables.Hammer, Category = PieceCategories.Furniture, CraftingStation = CraftingStations.Workbench, Requirements = ModConfig.ParseRequirements() }; CustomPiece val2 = new CustomPiece("bottomless_chest", "piece_chest_private", val); if ((Object)(object)val2.PiecePrefab == (Object)null) { Plugin.Log.LogError((object)"Could not clone 'piece_chest_private'. The chest will not be buildable."); return; } Container component = val2.PiecePrefab.GetComponent<Container>(); if ((Object)(object)component != (Object)null) { component.m_name = "$piece_bottomlesschest"; if ((int)component.m_privacy != 2) { Plugin.Log.LogInfo((object)(string.Format("Base prefab '{0}' is {1}; forcing Public so ", "piece_chest_private", component.m_privacy) + "other players can use the chest.")); component.m_privacy = (PrivacySetting)2; } } else { Plugin.Log.LogWarning((object)"Cloned chest has no Container component; its name will be wrong."); } ApplyScale(val2.PiecePrefab); ApplyTint(val2.PiecePrefab); val2.PiecePrefab.AddComponent<BottomlessContainer>(); PieceManager.Instance.AddPiece(val2); Plugin.Log.LogInfo((object)"Registered piece 'bottomless_chest'."); } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to register the bottomless chest piece: {arg}"); } } private static void ApplyScale(GameObject prefab) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) float value = ModConfig.ModelScale.Value; if (!Mathf.Approximately(value, 1f)) { Vector3 localScale = prefab.transform.localScale; prefab.transform.localScale = localScale * value; Plugin.Log.LogInfo((object)$"Chest model scaled by {value:0.##}x: {localScale} -> {prefab.transform.localScale}."); } } private static void ApplyTint(GameObject prefab) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) Color val = ReadColour(ModConfig.BodyTint.Value, FallbackTint); Color val2 = ReadColour(ModConfig.LidTint.Value, FallbackLid); Color val3 = ReadColour(ModConfig.GlowColour.Value, FallbackGlow) * ModConfig.GlowStrength.Value; List<string> list = new List<string>(); Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(true); foreach (Renderer renderer in componentsInChildren) { Material[] sharedMaterials = renderer.sharedMaterials; Material[] array = (Material[])(object)new Material[sharedMaterials.Length]; for (int j = 0; j < sharedMaterials.Length; j++) { if (!((Object)(object)sharedMaterials[j] == (Object)null)) { Material val4 = new Material(sharedMaterials[j]); string[] obj = new string[6] { ((Object)renderer).name, "/", ((Object)sharedMaterials[j]).name, " (shader ", null, null }; Shader shader = sharedMaterials[j].shader; obj[4] = ((shader != null) ? ((Object)shader).name : null); obj[5] = ")"; list.Add(string.Concat(obj)); bool flag = Array.Exists(LidMarkers, (string marker) => ((Object)renderer).name.IndexOf(marker, StringComparison.OrdinalIgnoreCase) >= 0); if (val4.HasProperty("_Color")) { val4.SetColor("_Color", val4.GetColor("_Color") * (flag ? val2 : val)); } if (flag && val4.HasProperty("_EmissionColor")) { val4.EnableKeyword("_EMISSION"); val4.SetColor("_EmissionColor", val3); } array[j] = val4; } } renderer.sharedMaterials = array; } Plugin.Log.LogDebug((object)("Chest renderers: " + string.Join(", ", list))); } } } namespace BottomlessChest.Net { internal enum ChestMessage { Open, Page, PageResult, Take, Granted, Put, Accepted, Close, Fill, Clear, StackAll, Stacked, Counts } internal static class ChestRpc { private const string RpcName = "BottomlessChest_Sync"; private static CustomRPC _rpc; internal static bool Ready => _rpc != null; internal static void Register() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0027: Expected O, but got Unknown _rpc = NetworkManager.Instance.AddRPC("BottomlessChest_Sync", new CoroutineHandler(OnServerReceive), new CoroutineHandler(OnClientReceive)); Plugin.Log.LogInfo((object)"Registered RPC 'BottomlessChest_Sync'."); } private static void ToServer(ZPackage package) { if (_rpc != null && ZRoutedRpc.instance != null) { _rpc.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), package); } } internal static void Open(string storeId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(0); val.Write(storeId); ToServer(val); } internal static void RequestPage(string storeId, string query, int scrollRow) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(storeId); val.Write(query ?? string.Empty); val.Write(scrollRow); ToServer(val); } internal static void Take(string storeId, long version, IReadOnlyList<int> indices) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(3); val.Write(storeId); val.Write(version); val.Write(indices.Count); foreach (int index in indices) { val.Write(index); } ToServer(val); } internal static void Put(string storeId, byte[] itemBytes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(5); val.Write(storeId); val.Write(itemBytes); ToServer(val); } internal static void Fill(string storeId, int stacks, string prefabName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(8); val.Write(storeId); val.Write(stacks); val.Write(prefabName ?? string.Empty); ToServer(val); } internal static void Clear(string storeId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(9); val.Write(storeId); ToServer(val); } internal static void StackAll(string storeId, byte[] candidateBytes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(10); val.Write(storeId); val.Write(candidateBytes); ToServer(val); } internal static void Close(string storeId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(7); val.Write(storeId); ToServer(val); } private static IEnumerator OnServerReceive(long sender, ZPackage package) { if (package == null || package.Size() == 0) { yield break; } ChestMessage chestMessage = (ChestMessage)package.ReadInt(); string text = package.ReadString(); switch (chestMessage) { case ChestMessage.Open: { ChestSession chestSession2 = ChestSessions.Acquire(text); if (chestSession2 != null) { SendPage(sender, chestSession2, 0); } break; } case ChestMessage.Page: { string query = package.ReadString(); int scrollRow = package.ReadInt(); ChestSession chestSession3 = ChestSessions.Acquire(text); if (chestSession3 != null) { chestSession3.SetQuery(query); SendPage(sender, chestSession3, scrollRow); } break; } case ChestMessage.Take: { long num = package.ReadLong(); int num2 = package.ReadInt(); List<int> list4 = new List<int>(num2); for (int j = 0; j < num2; j++) { list4.Add(package.ReadInt()); } if (ChestSessions.TryGet(text, out var session3)) { if (session3.Version != num) { Plugin.Log.LogWarning((object)($"Rejecting stale take on chest {text}: client is at v{num}, " + $"session is at v{session3.Version}.")); SendPage(sender, session3, -1); break; } List<ItemData> list5 = session3.Take(list4); ChestSessions.Persist(session3); Plugin.Log.LogDebug((object)($"Take from {text}: {list4.Count} requested, {list5.Count} removed, " + $"{session3.TotalCount} left.")); ZPackage val4 = new ZPackage(); val4.Write(4); val4.Write(text); val4.Write(Serialize(list5)); _rpc.SendPackage(sender, val4); SendCounts(sender, session3); } break; } case ChestMessage.Put: { byte[] bytes = package.ReadByteArray(); if (!ChestSessions.TryGet(text, out var session2)) { break; } foreach (ItemData item in Deserialize(bytes)) { session2.Add(item); } ChestSessions.Persist(session2); ZPackage val3 = new ZPackage(); val3.Write(6); val3.Write(text); _rpc.SendPackage(sender, val3); SendPage(sender, session2, -1); break; } case ChestMessage.Fill: { int stacks = package.ReadInt(); string prefabName = package.ReadString(); ChestSession chestSession5 = ChestSessions.Acquire(text); if (chestSession5 == null) { break; } string error; List<ItemData> list3 = TestData.Build(stacks, prefabName, out error); if (error != null) { Plugin.Log.LogWarning((object)("Fill refused for chest " + text + ": " + error)); break; } foreach (ItemData item2 in list3) { chestSession5.Add(item2); } ChestSessions.Persist(chestSession5); Plugin.Log.LogDebug((object)$"Added {list3.Count} filler stacks to chest {text}."); SendPage(sender, chestSession5, -1); break; } case ChestMessage.Clear: { ChestSession chestSession4 = ChestSessions.Acquire(text); if (chestSession4 != null) { int totalCount = chestSession4.TotalCount; chestSession4.Inventory.m_inventory.Clear(); chestSession4.Touch(); ChestSessions.Persist(chestSession4); Plugin.Log.LogDebug((object)$"Emptied chest {text} of {totalCount} stacks."); SendPage(sender, chestSession4, 0); } break; } case ChestMessage.StackAll: { List<ItemData> list = Deserialize(package.ReadByteArray()); ChestSession session; bool flag = ChestSessions.TryGet(text, out session); ChestSession chestSession = ChestSessions.Acquire(text); if (chestSession == null) { break; } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (ItemData item3 in chestSession.Inventory.m_inventory) { hashSet.Add(StackKey(item3)); } List<int> list2 = new List<int>(); for (int i = 0; i < list.Count; i++) { ItemData val = list[i]; if (val.m_shared.m_maxStackSize > 1 && hashSet.Contains(StackKey(val))) { chestSession.Add(val); list2.Add(i); } } Plugin.Log.LogDebug((object)($"Deposit into {text}: {list.Count} offered, {hashSet.Count} distinct types held, " + $"{list2.Count} kept.")); if (list2.Count > 0) { ChestSessions.Persist(chestSession); } ZPackage val2 = new ZPackage(); val2.Write(11); val2.Write(text); val2.Write(list2.Count); foreach (int item4 in list2) { val2.Write(item4); } _rpc.SendPackage(sender, val2); if (flag) { SendPage(sender, chestSession, -1); } else { ChestSessions.Release(text); } break; } case ChestMessage.Close: ChestSessions.Release(text); break; } } private static void SendCounts(long peer, ChestSession session) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(12); val.Write(session.StoreId); val.Write(session.Version); val.Write(session.TotalCount); val.Write(session.MatchCount); val.Write(session.TotalWeight); _rpc.SendPackage(peer, val); } private static void SendPage(long peer, ChestSession session, int scrollRow) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown int scrollRow2 = ((scrollRow < 0) ? session.LastScrollRow : scrollRow); List<ItemData> items = session.Page(scrollRow2, ChestView.PageSlots); int lastScrollRow = session.LastScrollRow; ZPackage val = new ZPackage(); val.Write(2); val.Write(session.StoreId); val.Write(session.Version); val.Write(session.TotalCount); val.Write(session.MatchCount); val.Write(session.TotalWeight); val.Write(lastScrollRow); val.Write(Serialize(items)); _rpc.SendPackage(peer, val); } private static string StackKey(ItemData item) { return $"{item.m_shared.m_name}|{item.m_quality}|{item.m_variant}"; } private static byte[] Serialize(List<ItemData> items) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown Inventory val = new Inventory("page", (Sprite)null, 8, 6); val.m_inventory.AddRange(items); ZPackage val2 = new ZPackage(); val.Save(val2); return val2.GetArray(); } private static List<ItemData> Deserialize(byte[] bytes) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown Inventory val = new Inventory("page", (Sprite)null, 8, 4096); if (bytes != null && bytes.Length != 0 && !FastInventoryReader.TryLoad(val, bytes, out var _)) { val.Load(new ZPackage(bytes)); } return new List<ItemData>(val.m_inventory); } private static IEnumerator OnClientReceive(long sender, ZPackage package) { if (package == null || package.Size() == 0) { yield break; } ChestMessage chestMessage = (ChestMessage)package.ReadInt(); string storeId = package.ReadString(); switch (chestMessage) { case ChestMessage.PageResult: { long version = package.ReadLong(); int total = package.ReadInt(); int matches = package.ReadInt(); float weight = package.ReadSingle(); int scrollRow = package.ReadInt(); List<ItemData> items = Deserialize(package.ReadByteArray()); ChestView.ApplyPage(storeId, version, total, matches, weight, scrollRow, items); break; } case ChestMessage.Granted: ChestView.ApplyGranted(Deserialize(package.ReadByteArray())); break; case ChestMessage.Counts: { long version2 = package.ReadLong(); int total2 = package.ReadInt(); int matches2 = package.ReadInt(); float weight2 = package.ReadSingle(); ChestView.ApplyCounts(storeId, version2, total2, matches2, weight2); break; } case ChestMessage.Accepted: ChestView.ApplyAccepted(); break; case ChestMessage.Stacked: { int num = package.ReadInt(); List<int> list = new List<int>(num); for (int i = 0; i < num; i++) { list.Add(package.ReadInt()); } ChestView.ApplyStacked(list); break; } } } } } namespace BottomlessChest.Gui { internal sealed class ChestScrollbarBridge : MonoBehaviour { private const float MinimumHandle = 0.12f; private Scrollbar _bar; private ScrollRect _detachedFrom; private bool _writing; internal void Bind(InventoryGrid grid) { if ((Object)(object)grid == (Object)null) { return; } _bar = grid.m_scrollbar; if (!((Object)(object)_bar == (Object)null)) { ScrollRect component = ((Component)grid).GetComponent<ScrollRect>(); if ((Object)(object)component != (Object)null && (Object)(object)component.verticalScrollbar == (Object)(object)_bar) { _detachedFrom = component; component.verticalScrollbar = null; } ((UnityEvent<float>)(object)_bar.onValueChanged).AddListener((UnityAction<float>)OnBarMoved); Sync(); } } private void OnBarMoved(float value) { if (!_writing && ChestView.IsOpen) { ChestView.ScrollTo(Mathf.RoundToInt(TopFraction(value) * (float)ChestView.MaxScroll)); } } private void Update() { if ((Object)(object)_bar != (Object)null && ChestView.IsOpen) { Sync(); } } private void Sync() { int totalRows = ChestView.TotalRows; int rowsOnScreen = ChestView.RowsOnScreen; int maxScroll = ChestView.MaxScroll; _writing = true; _bar.size = ((totalRows <= rowsOnScreen) ? 1f : Mathf.Clamp((float)rowsOnScreen / (float)totalRows, 0.12f, 1f)); if (!ChestView.PageRequestPending) { float value = ((maxScroll <= 0) ? 0f : ((float)ChestView.ScrollRow / (float)maxScroll)); _bar.SetValueWithoutNotify(TopFraction(value)); } _writing = false; } private float TopFraction(float value) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if ((int)_bar.direction != 2 && (int)_bar.direction != 1) { return value; } return 1f - value; } private void OnDestroy() { if ((Object)(object)_bar != (Object)null) { ((UnityEvent<float>)(object)_bar.onValueChanged).RemoveListener((UnityAction<float>)OnBarMoved); _bar.size = 1f; } if ((Object)(object)_detachedFrom != (Object)null) { _detachedFrom.verticalScrollbar = _bar; _detachedFrom = null; } } } internal sealed class ChestScroller : MonoBehaviour { private void Update() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (ChestView.IsOpen) { ChestView.Tick(); float y = Input.mouseScrollDelta.y; if (!(Mathf.Abs(y) < 0.01f)) { ChestView.Scroll((!(y > 0f)) ? 1 : (-1)); } } } } internal static class ContainerWeightPatch { [HarmonyPatch(typeof(InventoryGui), "UpdateContainerWeight")] private static class Patch { private static bool Prefix(InventoryGui __instance) { if (Plugin.Degraded || !ChestView.IsRemote || (Object)(object)__instance.m_currentContainer == (Object)null) { return true; } __instance.m_containerWeight.text = Mathf.CeilToInt(ChestView.RemoteWeight).ToString(); return false; } } } internal static class DropSlotHighlight { [HarmonyPatch(typeof(InventoryGrid), "UpdateGui")] private static class Patch { private static void Postfix(InventoryGrid __instance) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) RectTransform gridRoot = __instance.m_gridRoot; if ((Object)(object)gridRoot == (Object)null) { return; } int num = ((ChestView.IsRemote && __instance.m_inventory == ChestView.TargetInventory) ? __instance.m_inventory.m_inventory.Count : (-1)); for (int i = 0; i < ((Transform)gridRoot).childCount; i++) { Image component = ((Component)((Transform)gridRoot).GetChild(i)).GetComponent<Image>(); if (!((Object)(object)component == (Object)null)) { ((Graphic)component).color = ((i == num) ? DropTint : Color.white); } } } } private static readonly Color DropTint = new Color(0.65f, 0.85f, 0.55f, 0.55f); } internal static class ScrollbarProbe { [HarmonyPatch(typeof(InventoryGui), "Show")] private static class Patch { private static void Postfix(InventoryGui __instance, Container container) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (_reported || (Object)(object)container == (Object)null || (Object)(object)__instance.m_containerGrid == (Object)null || !BottomlessContainer.TryResolve(container, out var _)) { return; } _reported = true; StringBuilder stringBuilder = new StringBuilder(); Scrollbar scrollbar = __instance.m_containerGrid.m_scrollbar; stringBuilder.AppendLine("--- container scrollbar probe ---"); stringBuilder.AppendLine("scrollbar: " + (((Object)(object)scrollbar == (Object)null) ? "NULL" : ((Object)scrollbar).name)); if ((Object)(object)scrollbar != (Object)null) { stringBuilder.AppendLine($" direction: {scrollbar.direction}, size: {scrollbar.size:0.###}, value: {scrollbar.value:0.###}"); stringBuilder.AppendLine(" ancestors: " + Chain(((Component)scrollbar).transform)); Transform parent = ((Component)scrollbar).transform.parent; if ((Object)(object)parent != (Object)null) { stringBuilder.AppendLine(" siblings:"); for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); stringBuilder.AppendLine(" " + ((Object)child).name + " [" + Components(child) + "]"); } } } stringBuilder.AppendLine(" gridRoot ancestors: " + Chain((Transform)(object)__instance.m_containerGrid.m_gridRoot)); ScrollRect[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<ScrollRect>(true); foreach (ScrollRect val in componentsInChildren) { bool flag = (Object)(object)scrollbar != (Object)null && (Object)(object)val.verticalScrollbar == (Object)(object)scrollbar; stringBuilder.AppendLine($" ScrollRect '{((Object)val).name}' ownsThisBar={flag} " + "content='" + (((Object)(object)val.content == (Object)null) ? "null" : ((Object)val.content).name) + "' viewport='" + (((Object)(object)val.viewport == (Object)null) ? "null" : ((Object)val.viewport).name) + "'"); } Plugin.Log.LogInfo((object)stringBuilder.ToString()); } } private static bool _reported; internal static void Reset() { _reported = false; } private static string Chain(Transform t) { if ((Object)(object)t == (Object)null) { return "null"; } StringBuilder stringBuilder = new StringBuilder(((Object)t).name); Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Append(" < ").Append(((Object)parent).name); parent = parent.parent; } return stringBuilder.ToString(); } private static string Components(Transform t) { StringBuilder stringBuilder = new StringBuilder(); Component[] components = ((Component)t).GetComponents<Component>(); foreach (Component val in components) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(((object)val).GetType().Name); } return stringBuilder.ToString(); } } internal static class SearchBox { [HarmonyPatch(typeof(InventoryGui), "Show")] private static class ShowPatch { private static void Postfix(InventoryGui __instance, Container container) { if (!((Object)(object)container == (Object)null) && BottomlessContainer.TryResolve(container, out var bottomless)) { bottomless.RefreshFromServer(); Teardown(); ChestView.Begin(container.GetInventory(), bottomless); Build(__instance); } } } [HarmonyPatch(typeof(InventoryGui), "Hide")] private static class HidePatch { private static void Postfix() { if (_hideCancelled) { _hideCancelled = false; return; } Teardown(); BottomlessContainer.FlushAllPending(); } } [HarmonyPatch(typeof(InventoryGui), "Update")] private static class StatusRefreshPatch { private static void Postfix() { if ((Object)(object)_status != (Object)null) { UpdateStatus(); } } } [HarmonyPatch(typeof(InventoryGui), "Hide")] private static class EscapeGuard { private static bool Prefix() { _hideCancelled = TryConsumeEscape(); return !_hideCancelled; } } private const float SearchBoxLift = 6f; private const float StatusGap = 26f; private static GameObject _field; private static GameObject _hiddenTitle; private static int _escapeFrame = -1000; private const int EscapeGraceFrames = 3; private static bool _hideCancelled; private static InputField _input; private static Text _status; internal static void NoteEscapePressed() { _escapeFrame = Time.frameCount; } private static void Build(InventoryGui gui) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gui.m_container == (Object)null) { return; } try { TMP_Text containerName = gui.m_containerName; RectTransform val = (((Object)(object)containerName != (Object)null) ? containerName.rectTransform : null); Transform val2 = (((Object)(object)val != (Object)null) ? ((Transform)val).parent : ((Component)gui.m_container).transform); _field = GUIManager.Instance.CreateInputField(val2, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, 44f), (ContentType)0, "$bottomless_search", 16, 280f, 30f); if ((Object)(object)val != (Object)null) { RectTransform component = _field.GetComponent<RectTransform>(); component.anchorMin = val.anchorMin; component.anchorMax = val.anchorMax; component.pivot = val.pivot; component.anchoredPosition = val.anchoredPosition + new Vector2(0f, 6f); component.sizeDelta = new Vector2(280f, 30f); _hiddenTitle = ((Component)containerName).gameObject; _hiddenTitle.SetActive(false); } _input = _field.GetComponent<InputField>() ?? _field.GetComponentInChildren<InputField>(); if ((Object)(object)_input == (Object)null) { Plugin.Log.LogError((object)"Search field was created but carries no InputField."); DestroyWidgets(); return; } ((UnityEvent<string>)(object)_input.onValueChanged).AddListener((UnityAction<string>)OnChanged); _field.AddComponent<SearchFocusGuard>().TakeFocus(); _field.AddComponent<ChestScroller>(); _field.AddComponent<ChestScrollbarBridge>().Bind(gui.m_containerGrid); _field.transform.SetAsLastSibling(); Plugin.Log.LogDebug((object)("Search box ready (parent '" + ((Object)gui.m_container).name + "', " + $"{ChestView.TotalCount} items in chest).")); _status = GUIManager.Instance.CreateText(string.Empty, _field.transform.parent, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), Vector2.zero, GUIManager.Instance.AveriaSerifBold, 13, GUIManager.Instance.ValheimOrange, true, Color.black, 280f, 20f, false).GetComponent<Text>(); RectTransform component2 = _field.GetComponent<RectTransform>(); RectTransform rectTransform = ((Graphic)_status).rectTransform; rectTransform.anchorMin = component2.anchorMin; rectTransform.anchorMax = component2.anchorMax; rectTransform.pivot = component2.pivot; rectTransform.anchoredPosition = component2.anchoredPosition - new Vector2(0f, 26f); UpdateStatus(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not build the chest search box: {arg}"); DestroyWidgets(); } } internal static bool TryConsumeEscape() { if ((Object)(object)_input == (Object)null || string.IsNullOrEmpty(_input.text)) { return false; } bool keyDown = Input.GetKeyDown((KeyCode)27); bool flag = Time.frameCount - _escapeFrame <= 3; if (!keyDown && !flag) { return false; } _escapeFrame = -1000; _input.text = string.Empty; _input.ActivateInputField(); ((Selectable)_input).Select(); return true; } private static void OnChanged(string text) { try { ChestView.SetQuery(text); UpdateStatus(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Filtering failed for '{text}': {arg}"); } } private static void UpdateStatus() { if (!((Object)(object)_status == (Object)null)) { if (ChestView.AwaitingContents) { _status.text = "Loading from server..."; return; } string text = (ChestView.IsFiltering ? $"{ChestView.MatchCount} of {ChestView.TotalCount} items" : $"{ChestView.TotalCount} items"); int totalRows = ChestView.TotalRows; int num = 6; _status.text = ((totalRows > num) ? $"{text} - rows {ChestView.ScrollRow + 1}-{Mathf.Min(ChestView.ScrollRow + num, totalRows)} of {totalRows}" : text); } } private static void Teardown() { DestroyWidgets(); ChestView.End(); } private static void DestroyWidgets() { if ((Object)(object)_hiddenTitle != (Object)null) { _hiddenTitle.SetActive(true); _hiddenTitle = null; } if ((Object)(object)_field != (Object)null) { GUIManager.BlockInput(false); Object.Destroy((Object)(object)_field); _field = null; _input = null; } if ((Object)(object)_status != (Object)null) { Object.Destroy((Object)(object)((Component)_status).gameObject); _status = null; } } } [DefaultExecutionOrder(-1000)] internal sealed class SearchFocusGuard : MonoBehaviour { private InputField _input; private bool _blocking; private int _focusAttemptsLeft; private bool _wasFocused; private void Awake() { _input = ((Component)this).GetComponent<InputField>(); } internal void TakeFocus() { _focusAttemptsLeft = 300; } private static bool UseKeyHeld() { try { if (ZInput.instance != null && ZInput.GetButton("Use")) { return true; } } catch { } return Input.GetKey((KeyCode)101); } private void Update() { if ((Object)(object)_input == (Object)null) { Release(); return; } if (_focusAttemptsLeft > 0) { _focusAttemptsLeft--; if (!UseKeyHeld()) { if (!_input.isFocused) { ((Selectable)_input).Select(); _input.ActivateInputField(); } else { _focusAttemptsLeft = 0; } } } if (Input.GetKeyDown((KeyCode)27)) { SearchBox.NoteEscapePressed(); } if ((_input.isFocused || _wasFocused) && Input.GetKeyDown((KeyCode)27)) { _wasFocused = false; HandleEscape(); return; } _wasFocused = _input.isFocused; if (_input.isFocused != _blocking) { _blocking = _input.isFocused; GUIManager.BlockInput(_blocking); Plugin.Log.LogDebug((object)$"Search box focus: {_blocking}"); } } private void HandleEscape() { if (!string.IsNullOrEmpty(_input.text)) { _input.text = string.Empty; return; } Release(); InventoryGui instance = InventoryGui.instance; if (instance != null) { instance.Hide(); } } private void OnDisable() { Release(); } private void OnDestroy() { Release(); } private void Release() { if (_blocking) { _blocking = false; GUIManager.BlockInput(false); } } } } namespace BottomlessChest.Filter { internal static class ChestView { [HarmonyPatch(typeof(InventoryGrid), "UpdateGui")] private static class GridRenderScope { private static Inventory _swappedOut; private static void Prefix(InventoryGrid __instance) { _swappedOut = null; if (!_remote && _target != null && __instance.m_inventory == _target) { _swappedOut = __instance.m_inventory; __instance.m_inventory = BuildView(); } } private static void Finalizer(InventoryGrid __instance) { if (_swappedOut != null) { __instance.m_inventory = _swappedOut; _swappedOut = null; } } } private static Inventory _target; private static Inventory _view; private static string _query = string.Empty; private static int _matchCount; private static int _scrollRow; private static int _windowStart; private static int _windowEnd; private static BottomlessContainer _owner; private static bool _remote; private static long _version; private static int _remoteTotal; private static float _remoteWeight; private static string _remoteStoreId; private static bool _awaitingPage; private static ItemData _pendingPut; private static float _pendingPutSentAt; private static readonly Dictionary<string, int> KnownTotals = new Dictionary<string, int>(StringComparer.Ordinal); private static readonly List<ItemData> Matched = new List<ItemData>(); private static readonly List<ItemData> Rest = new List<ItemData>(); internal const int Width = 8; internal const int VisibleRows = 6; private static List<ItemData> _offered; private static float _offerSentAt; private static float _lastPageRequestAt; private static bool _pageRequestPending; private const float PageRequestInterval = 0.08f; private const float OfferTimeoutSeconds = 5f; internal static float RemoteWeight => _remoteWeight; internal static int MatchCount => _matchCount; internal static int TotalCount { get { if (!_remote) { return _target?.m_inventory.Count ?? 0; } return _remoteTotal; } } internal static bool IsRemote => _remote; internal static Inventory TargetInventory => _target; internal static long Version => _version; internal static int ScrollRow => _scrollRow; internal static bool IsFiltering { get { if (_target != null) { return !string.IsNullOrWhiteSpace(_query); } return false; } } internal static bool IsOpen => _target != null; internal static int TotalRows => GridPacker.RowsNeeded(_matchCount, 8); internal static int WidthForSession => 8; internal static int WindowSlots => 48; internal static int PageSlots => 47; internal static bool AwaitingContents { get { if (!_remote) { if ((Object)(object)_owner != (Object)null) { return _owner.AwaitingContents; } return false; } return _awaitingPage; } } internal static int MaxScroll => MaxScrollRow(); private static int RowsCarried { get { if (!_remote) { return 6; } return 5; } } internal static int RowsOnScreen => RowsCarried; internal static bool PageRequestPending => _pageRequestPending; internal static int? KnownTotalFor(string storeId) { if (!string.IsNullOrEmpty(storeId) && KnownTotals.TryGetValue(storeId, out var value)) { return value; } return null; } internal static List<ItemData> MatchingItems() { List<ItemData> list = new List<ItemData>(); if (_target == null) { return list; } List<ItemData> inventory = _target.m_inventory; int num = Mathf.Min(_matchCount, inventory.Count); for (int i = 0; i < num; i++) { list.Add(inventory[i]); } return list; } internal static void Begin(Inventory inventory, BottomlessContainer owner) { _owner = owner; _target = inventory; _query = string.Empty; _scrollRow = 0; _remote = (Object)(object)owner != (Object)null && !SidecarStore.IsServerAuthority; _version = 0L; _remoteTotal = 0; _matchCount = 0; _remoteStoreId = owner?.CurrentStoreId; if (_remote) { _target.m_inventory.Clear(); _awaitingPage = true; ChestRpc.Open(_remoteStoreId); } else { Reapply(); } } internal static void End() { if (_remote && !string.IsNullOrEmpty(_remoteStoreId)) { ChestRpc.Close(_remoteStoreId); } bool remote = _remote; Inventory target = _target; _remote = false; _awaitingPage = false; _remoteStoreId = null; _owner = null; _target = null; _view = null; _query = string.Empty; _matchCount = 0; _scrollRow = 0; if (!remote && target != null && !InventoryCapacity.LayoutIsUsable(target, VisibleRowsFor(target))) { InventoryCapacity.Repack(target); } } internal static void SetQuery(string query) { if (_remote) { _query = query ?? string.Empty; _scrollRow = 0; _awaitingPage = true; ChestRpc.RequestPage(_remoteStoreId, _query, 0); } else { Stopwatch stopwatch = Stopwatch.StartNew(); _query = query ?? string.Empty; _scrollRow = 0; Reapply(); _ = stopwatch.ElapsedMilliseconds; Refresh(); } } internal static bool Scroll(int rows) { if (rows != 0) { return ScrollTo(_scrollRow + rows); } return false; } internal static bool ScrollTo(int row) { if (_target == null) { return false; } int num = Mathf.Clamp(row, 0, MaxScrollRow()); if (num == _scrollRow) { return false; } if (_remote) { _scrollRow = num; RequestPageThrottled(); return true; } _scrollRow = num; Reapply(); Refresh(); return true; } private static void RequestPageThrottled() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastPageRequestAt < 0.08f) { _pageRequestPending = true; return; } _pageRequestPending = false; _lastPageRequestAt = realtimeSinceStartup; _awaitingPage = true; ChestRpc.RequestPage(_remoteStoreId, _query, _scrollRow); } internal static void Tick() { if (_remote && _pageRequestPending) { RequestPageThrottled(); } } internal static void OnInventoryChanged(Inventory inventory) { if (!_remote && _target != null && _target == inventory) { Reapply(); } } private static int VisibleRowsFor(Inventory inventory) { return 6; } private static int MaxScrollRow() { int num = TotalRows - RowsCarried; if (num >= 0) { return num; } return 0; } private static void Reapply() { //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) if (_target == null || _remote) { return; } List<ItemData> inventory = _target.m_inventory; if (string.IsNullOrWhiteSpace(_query)) { _matchCount = inventory.Count; } else { ItemQuery val = ItemQuery.Parse(_query); List<ItemData> matched = Matched; List<ItemData> rest = Rest; matched.Clear(); rest.Clear(); foreach (ItemData item in inventory) { if (val.Matches((IStorableItem)(object)new ItemAdapter(item))) { matched.Add(item); } else { rest.Add(item); } } matched.Sort((ItemData left, ItemData right) => ItemOrdering.ByName.Compare((IStorableItem)(object)new ItemAdapter(left), (IStorableItem)(object)new ItemAdapter(right))); _matchCount = matched.Count; inventory.Clear(); inventory.AddRange(matched); inventory.AddRange(rest); } _scrollRow = Mathf.Clamp(_scrollRow, 0, MaxScrollRow()); if (string.IsNullOrWhiteSpace(_query) && _scrollRow <= 0 && InventoryCapacity.LayoutIsUsable(_target, 6)) { _windowStart = 0; _windowEnd = inventory.Count; InventoryCapacity.Apply(_target); return; } _windowStart = _scrollRow * 8; _windowEnd = Mathf.Min(_matchCount, _windowStart + WindowSlots); int num = 0; for (int num2 = 0; num2 < inventory.Count; num2++) { GridPos val2 = ((num2 < _windowStart || num2 >= _windowEnd) ? GridPacker.PositionOf(WindowSlots + num++, 8) : GridPacker.PositionOf(num2 - _windowStart, 8)); inventory[num2].m_gridPos = new Vector2i(((GridPos)(ref val2)).X, ((GridPos)(ref val2)).Y); } InventoryCapacity.Apply(_target); } private static void Refresh() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.m_currentContainer != (Object)null) { instance.m_containerGrid.UpdateInventory(instance.m_currentContainer.GetInventory(), (Player)null, instance.m_dragItem); } } internal static void ApplyPage(string storeId, long version, int total, int matches, float weight, int scrollRow, List<ItemData> items) { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) if (_remote && _target != null && !(storeId != _remoteStoreId)) { _version = version; _remoteTotal = total; _matchCount = matches; _remoteWeight = weight; KnownTotals[storeId] = total; _scrollRow = scrollRow; _awaitingPage = false; Plugin.Log.LogDebug((object)($"Page: {items.Count} items at row {scrollRow}, {matches}/{total} match, " + $"max row {MaxScrollRow()}, v{version}.")); List<ItemData> inventory = _target.m_inventory; inventory.Clear(); inventory.AddRange(items); for (int i = 0; i < inventory.Count; i++) { GridPos val = GridPacker.PositionOf(i, 8); inventory[i].m_gridPos = new Vector2i(((GridPos)(ref val)).X, ((GridPos)(ref val)).Y); } _target.m_width = 8; _target.m_height = 6; Refresh(); } } internal static void ApplyCounts(string storeId, long version, int total, int matches, float weight) { if (_remote && !(storeId != _remoteStoreId)) { _version = version; _remoteTotal = total; _matchCount = matches; _remoteWeight = weight; _awaitingPage = false; KnownTotals[storeId] = total; Refresh(); } } internal static void ApplyGranted(List<ItemData> items) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val == null) { return; } foreach (ItemData item in items) { if (!val.AddItem(item)) { ItemDrop.DropItem(item, item.m_stack, ((Component)Player.m_localPlayer).transform.position, Quaternion.identity); } } } internal static void ApplyAccepted() { if (_pendingPut == null) { return; } Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (inventory != null) { inventory.RemoveItem(_pendingPut); } } _pendingPut = null; } internal static bool RequestStackAll(string storeId) { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown if (string.IsNullOrEmpty(storeId)) { return false; } Player localPlayer = Player.m_localPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val == null) { return false; } if (_offered != null && Time.realtimeSinceStartup - _offerSentAt < 5f) { return true; } _offered = new List<ItemData>(); foreach (ItemData item in val.m_inventory) { if (item.m_shared.m_maxStackSize > 1 && !item.m_equipped) { _offered.Add(item); } } Plugin.Log.LogDebug((object)$"Offering {_offered.Count} stackable item(s) to chest {storeId}."); if (_offered.Count == 0) { return true; } Inventory val2 = new Inventory("offer", (Sprite)null, 8, 64); val2.m_inventory.AddRange(_offered); ZPackage val3 = new ZPackage(); val2.Save(val3); _offerSentAt = Time.realtimeSinceStartup; ChestRpc.StackAll(storeId, val3.GetArray()); return true; } internal static void ApplyStacked(List<int> keptIndices) { Player localPlayer = Player.m_localPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val == null || _offered == null) { return; } foreach (int keptIndex in keptIndices) { if (keptIndex >= 0 && keptIndex < _offered.Count) { val.RemoveItem(_offered[keptIndex]); } } if (keptIndices.Count > 0 && (Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, $"$msg_added {keptIndices.Count}", 0, (Sprite)null); } _offered = null; } internal static List<int> TrimToCapacity(IReadOnlyList<int> pageSlots) { Player localPlayer = Player.m_localPlayer; int? obj; if (localPlayer == null) { obj = null; } else { Inventory inventory = ((Humanoid)localPlayer).GetInventory(); obj = ((inventory != null) ? new int?(inventory.GetEmptySlots()) : ((int?)null)); } int? num = obj; int valueOrDefault = num.GetValueOrDefault(); List<int> list = new List<int>(Math.Min(valueOrDefault, pageSlots.Count)); for (int i = 0; i < pageSlots.Count; i++) { if (list.Count >= valueOrDefault) { break; } list.Add(pageSlots[i]); } return list; } internal static void RequestTake(IReadOnlyList<int> pageSlots) { if (!_remote || pageSlots.Count == 0) { return; } List<int> list = new List<int>(pageSlots.Count); foreach (int pageSlot in pageSlots) { list.Add(_scrollRow * 8 + pageSlot); } RemoveSlotsLocally(pageSlots); Plugin.Log.LogDebug((object)($"Requesting {list.Count} item(s) from chest {_remoteStoreId} at v{_version}, " + $"row {_scrollRow} (first index {((list.Count > 0) ? list[0] : (-1))}).")); ChestRpc.Take(_remoteStoreId, _version, list); } internal static bool RequestPut(ItemData item) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown if (!_remote || item == null) { return false; } if (_pendingPut != null && Time.realtimeSinceStartup - _pendingPutSentAt < 5f) { return false; } Inventory val = new Inventory("put", (Sprite)null, 8, 1) { m_inventory = { item } }; ZPackage val2 = new ZPackage(); val.Save(val2); _pendingPut = item; _pendingPutSentAt = Time.realtimeSinceStartup; ChestRpc.Put(_remoteStoreId, val2.GetArray()); return true; } private static void RemoveSlotsLocally(IReadOnlyList<int> pageSlots) { if (_target == null) { return; } HashSet<ItemData> doomed = new HashSet<ItemData>(); foreach (int pageSlot in pageSlots) { if (pageSlot >= 0 && pageSlot < _target.m_inventory.Count) { doomed.Add(_target.m_inventory[pageSlot]); } } _target.m_inventory.RemoveAll((ItemData item) => doomed.Contains(item)); } internal static bool IsRemotePage(Inventory inventory) { if (_remote && _target != null) { return _target == inventory; } return false; } internal static int PageSlotOf(ItemData item) { if (_target != null) { return _target.m_inventory.IndexOf(item); } return -1; } private static Inventory BuildView() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if (_view == null) { _view = new Inventory(_target.m_name, _target.m_bkg, 8, 6); } _view.m_width = 8; _view.m_height = 6; List<ItemData> inventory = _view.m_inventory; inventory.Clear(); List<ItemData> inventory2 = _target.m_inventory; int num = Mathf.Min(_windowEnd, inventory2.Count); for (int i = _windowStart; i < num; i++) { inventory.Add(inventory2[i]); } return _view; } } internal readonly struct ItemAdapter : IStorableItem { private static readonly Dictionary<string, string> SearchKeys = new Dictionary<string, string>(512, StringComparer.Ordinal); private readonly ItemData _item; public string ItemId { get { if (!((Object)(object)_item.m_dropPrefab != (Object)null)) { return _item.m_shared.m_name; } return ((Object)_item.m_dropPrefab).name; } } public string DisplayName => Localize(_item.m_shared.m_name); public string SearchKey { get { string name = _item.m_shared.m_name; if (name == null) { return string.Empty; } if (SearchKeys.TryGetValue(name, out var value)) { return value; } value = TextKey.Of(Localize(name)); SearchKeys[name] = value; return value; } } public ItemKind Kind => Classify(_item.m_shared.m_itemType); public int Stack => _item.m_stack; public int MaxStackSize => _item.m_shared.m_maxStackSize; public int Quality => _item.m_quality; public int Variant => _item.m_variant; public string CustomData => null; internal static void ClearCache() { SearchKeys.Clear(); } internal ItemAdapter(ItemData item) { _item = item; } private static string Localize(string token) { if (string.IsNullOrEmpty(token)) { return string.Empty; } Localization instance = Localization.instance; if (instance == null) { return token.TrimStart(new char[1] { '$' }); } return instance.Localize(token); } private static ItemKind Classify(ItemType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected I4, but got Unknown switch (type - 1) { case 0: return (ItemKind)1; case 1: case 20: return (ItemKind)2; case 2: case 3: case 13: case 14: case 19: case 21: return (ItemKind)3; case 4: case 5: case 6: case 10: case 11: case 16: return (ItemKind)4; case 8: case 22: return (ItemKind)5; case 18: return (ItemKind)6; case 12: return (ItemKind)7; default: return (ItemKind)8; } } } } namespace BottomlessChest.Core { [DisallowMultipleComponent] public class BottomlessContainer : MonoBehaviour { internal const string StoreIdKey = "BottomlessChest_id"; private static readonly Dictionary<Container, BottomlessContainer> Registry = new Dictionary<Container, BottomlessContainer>(); private Container _container; private ZNetView _nview; private bool _contentsLoaded; private bool _warnedAboutUnloadedSave; private bool _loadWasPartial; internal static IEnumerable<BottomlessContainer> Loaded => Registry.Values; internal Vector3 Position => ((Component)this).transform.position; internal Inventory Inventory => _container?.m_inventory; internal bool AwaitingContents { get { if (!_contentsLoaded) { return SidecarStore.IsServerAuthority; } return false; } } internal string CurrentStoreId { get { ZDO obj = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null); if (obj == null) { return null; } return obj.GetString("BottomlessChest_id", string.Empty); } } internal static bool TryResolve(Container container, out BottomlessContainer bottomless) { return Registry.TryGetValue(container, out bottomless); } internal static bool TryResolveByStoreId(string storeId, out BottomlessContainer found) { foreach (BottomlessContainer value in Registry.Values) { if (value.CurrentStoreId == storeId) { found = value; return true; } } found = null; return false; } internal static void FlushAllPending() { } internal void RefreshFromServer() { } internal void NotifyFilled() { if (!((Object)(object)_container == (Object)null)) { InventoryCapacity.Repack(_container.m_inventory); InventoryCapacity.Apply(_container.m_inventory); SaveToStore(); } } internal bool Rebind(string storeId) { ZDO val = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null); if (val == null || !_nview.IsOwner()) { return false; } val.Set("BottomlessChest_id", storeId); _container.m_loading = true; _container.m_inventory.RemoveAll(); _container.m_loading = false; _contentsLoaded = false; _warnedAboutUnloadedSave = false; return LoadFromStore(); } internal void EnsureRegistered() { if ((Object)(object)_container != (Object)null) { InventoryCapacity.Register(_container.m_inventory); } } private void Awake() { _container = ((Component)this).GetComponent<Container>(); _nview = ((Component)this).GetComponent<ZNetView>(); if ((Object)(object)_container == (Object)null) { Plugin.Log.LogError((object)("BottomlessContainer on '" + ((Object)this).name + "' has no Container. Disabling.")); ((Behaviour)this).enabled = false; } else { Registry[_container] = this; } } private void OnDestroy() { if ((Object)(object)_container != (Object)null) { InventoryCapacity.Forget(_container.m_inventory); Registry.Remove(_container); } } internal string GetOrCreateStoreId() { ZDO val = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null); if (val == null) { return null; } string text = val.GetString("BottomlessChest_id", string.Empty); if (!string.IsNullOrEmpty(text)) { return text; } if (!_nview.IsOwner()) { return null; } string text2 = Guid.NewGuid().ToString("N"); val.Set("BottomlessChest_id", text2); Plugin.Log.LogInfo((object)("Minted store id " + text2 + " for a new bottomless chest.")); return text2; } private bool AdoptLegacyZdoContents(string storeId) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown ZDO obj = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null); string text = ((obj != null) ? obj.GetString(ZDOVars.s_items, string.Empty) : null); if (string.IsNullOrEmpty(text)) { return false; } try { _container.m_loading = true; InventoryCapacity.Suspended = true; LoadIntoInventory(Convert.FromBase64String(text)); InventoryCapacity.Repack(_container.m_inventory); InventoryCapacity.Apply(_container.m_inventory); } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not adopt legacy contents for chest {storeId}: {arg}"); return false; } finally { InventoryCapacity.Suspended = false; _container.m_loading = false; } int num = _container.m_inventory.NrOfItems(); Plugin.Log.LogInfo((object)$"Adopted {num} item stack(s) from the ZDO into chest store {storeId}."); ZPackage val = new ZPackage(); _container.m_inventory.Save(val); SidecarStore.Instance.Put(storeId, val.GetArray()); return true; } internal void SaveToStore() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown string orCreateStoreId = GetOrCreateStoreId(); if (orCreateStoreId == null) { return; } if (_loadWasPartial) { if (!_warnedAboutUnloadedSave) { _warnedAboutUnloadedSave = true; Plugin.Log.LogError((object)("Refusing to save chest " + orCreateStoreId + ": it only loaded partially, so saving would overwrite the full contents with this truncated copy.")); } } else if (!_contentsLoaded) { if (!_warnedAboutUnloadedSave) { _warnedAboutUnloadedSave = true; Plugin.Log.LogWarning((object)("Refusing to save chest " + orCreateStoreId + ": its contents were never loaded. The stored copy is intact and left untouched.")); } } else if (SidecarStore.IsServerAuthority) { ZPackage val = new ZPackage(); _container.m_inventory.Save(val); SidecarStore.Instance.Put(orCreateStoreId, val.GetArray()); } } private static int PeekItemCount(byte[] contents) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (contents == null || contents.Length < 8) { return 0; } try { ZPackage val = new ZPackage(contents); val.ReadInt(); return val.ReadInt(); } catch { return 0; } } private void LoadIntoInventory(byte[] contents) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown int expected = PeekItemCount(contents); _container.m_inventory.RemoveAll(); InventoryCapacity.ApplyFor(_container.m_inventory, expected); if (!FastInventoryReader.TryLoad(_container.m_inventory, contents, out expected)) { _container.m_inventory.Load(new ZPackage(contents)); } int count = _container.m_inventory.m_inventory.Count; _loadWasPartial = count != expected; if (_loadWasPartial) { Plugin.Log.LogError((object)($"Loaded {count} of {expected} stacks - {expected - count} were dropped " + $"because the grid was too small ({_container.m_inventory.m_width}x{_container.m_inventory.m_height}).")); } } internal void ApplyRemoteContents(byte[] contents) { if ((Object)(object)_container == (Object)null) { return; } Stopwatch stopwatch = Stopwatch.StartNew(); long num = 0L; long num2 = 0L; try { _container.m_loading = true; InventoryCapacity.Suspended = true; if (contents != null && contents.Length != 0) { LoadIntoInventory(contents); } else { _container.m_inventory.RemoveAll(); } num = stopwatch.ElapsedMilliseconds; InventoryCapacity.Repack(_container.m_inventory); InventoryCapacity.Apply(_container.m_inventory); num2 = stopwatch.ElapsedMilliseconds; } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not apply contents received for a chest: {arg}"); return; } finally { InventoryCapacity.Suspended = false; _container.m_loading = false; } int count = _container.m_inventory.m_inventory.Count; if (count >= 1000) { Plugin.Log.LogDebug((object)($"Applied {count} stacks in {stopwatch.ElapsedMilliseconds}ms " + $"(deserialise {num}ms, layout {num2 - num}ms, rest {stopwatch.ElapsedMilliseconds - num2}ms).")); } _contentsLoaded = true; if (!_loadWasPartial) { _warnedAboutUnloadedSave = false; } } internal bool LoadFromStore() { if (_contentsLoaded) { return false; } string orCreateStoreId = GetOrCreateStoreId(); if (orCreateStoreId == null) { return false; } if (!SidecarStore.IsServerAuthority) { return false; } if (!SidecarStore.Instance.IsReady) { return false; } _contentsLoaded = true; if (!SidecarStore.Instance.TryGet(orCreateStoreId, out var contents) || contents == null || contents.Length == 0) { return AdoptLegacyZdoContents(orCreateStoreId); } try { _container.m_loading = true; InventoryCapacity.Suspended = true; LoadIntoInventory(contents); InventoryCapacity.Repack(_container.m_inventory); InventoryCapacity.Apply(_container.m_inventory); return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not load contents for chest {orCreateStoreId}: {arg}"); return false; } finally { _container.m_loading = false; } } } internal sealed class ChestSession { private readonly List<ItemData> _ordered = new List<ItemData>(); private string _query = string.Empty; private bool _orderDirty = true; private float _totalWeight; private bool _weightDirty = true; internal string StoreId { get; } internal Inventory Inventory { get; } internal long Version { get; private set; } internal int LastScrollRow { get; private set; } internal DateTime LastUsedUtc { get; private set; } = DateTime.UtcNow; internal int TotalCount => Inventory.m_inventory.Count; internal int MatchCount { get { EnsureOrder(); return _ordered.Count; } } internal float TotalWeight { get { if (_weightDirty) { _weightDirty = false; _totalWeight = 0f; foreach (ItemData item in Inventory.m_inventory) { _totalWeight += item.GetWeight(-1); } } return _totalWeight; } } internal ChestSession(string storeId, Inventory inventory) { StoreId = storeId; Inventory = inventory; } internal void MarkUsed() { LastUsedUtc = DateTime.UtcNow; } internal void SetQuery(string query) { string text = query ?? string.Empty; if (!(text == _query)) { _query = text; _orderDirty = true; } } internal void Touch() { Version++; _orderDirty = true; _weightDirty = true; } internal List<ItemData> Page(int scrollRow, int count) { EnsureOrder(); int num = Mathf.Max(0, GridPacker.RowsNeeded(_ordered.Count, 8) - 5); LastScrollRow = Mathf.Clamp(scrollRow, 0, num); List<ItemData> list = new List<ItemData>(count); for (int i = LastScrollRow * 8; i < _ordered.Count; i++) { if (list.Count >= count) { break; } list.Add(_ordered[i]); } return list; } internal List<ItemData> Take(IEnumerable<int> indices) { EnsureOrder(); List<ItemData> list = new List<ItemData>(); foreach (int index in indices) { if (index >= 0 && index < _ordered.Count) { ItemData item = _ordered[index]; if (Inventory.m_inventory.Remove(item)) { list.Add(item); } } } if (list.Count > 0) { Touch(); } return list; } internal void Add(ItemData item) { if (item != null) { Inventory.m_inventory.Add(item); Touch(); } } private void EnsureOrder() { if (!_orderDirty) { return; } _orderDirty = false; _ordered.Clear(); List<ItemData> inventory = Inventory.m_inventory; if (string.IsNullOrWhiteSpace(_query)) { _ordered.AddRange(inventory); return; } ItemQuery val = ItemQuery.Parse(_query); foreach (ItemData item in inventory) { if (val.Matches((IStorableItem)(object)new ItemAdapter(item))) { _ordered.Add(item); } } _ordered.Sort((ItemData left, ItemData right) => ItemOrdering.ByName.Compare((IStorableItem)(object)new ItemAdapter(left), (IStorableItem)(object)new ItemAdapter(right))); } } internal static class ChestSessions { private static readonly Dictionary<string, ChestSession> Open = new Dictionary<string, ChestSession>(StringComparer.Ordinal); internal static ChestSession Acquire(string storeId) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown if (string.IsNullOrEmpty(storeId)) { return null; } if (Open.TryGetValue(storeId, out var value)) { value.MarkUsed(); return value; } if (!SidecarStore.Instance.IsReady) { return null; } Inventory val = new Inventory("bottomless", (Sprite)null, ChestView.WidthForSession, 1); if (SidecarStore.Instance.TryGet(storeId, out var contents) && contents != null && contents.Length != 0 && !FastInventoryReader.TryLoad(val, contents, out var _)) { val.Load(new ZPackage(contents)); } ChestSession chestSession = new ChestSession(storeId, val); Open[storeId] = chestSession; Plugin.Log.LogDebug((object)$"Opened session for chest {storeId} with {chestSession.TotalCount} stacks."); return chestSession; } internal static bool TryGet(string storeId, out ChestSession session) { return Open.TryGetValue(storeId ?? string.Empty, out session); } internal static void Release(string storeId) { if (Open.TryGetValue(storeId ?? string.Empty, out var value)) { Persist(value); Open.Remove(storeId); } } internal static void Persist(ChestSession session) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown if (session != null) { ZPackage val = new ZPackage(); session.Inventory.Save(val); SidecarStore.Instance.Put(session.StoreId, val.GetArray()); } } internal static void ReleaseIdle(TimeSpan olderThan) { DateTime dateTime = DateTime.UtcNow - olderThan; List<string> list = null; foreach (KeyValuePair<string, ChestSession> item in Open) { if (item.Value.LastUsedUtc < dateTime) { list = list ?? new List<string>(); list.Add(item.Key); } } if (list == null) { return; } foreach (string item2 in list) { Plugin.Log.LogInfo((object)("Releasing idle chest session " + item2 + ".")); Release(item2); } } internal static void PersistAll() { foreach (ChestSession value in Open.Values) { Persist(value); } } internal static void Clear() { Open.Clear(); } } internal static class ContainerPersistencePatches { [HarmonyPatch(typeof(Container), "Save")] private static class SavePatch { private static bool Prefix(Container __instance) { if (Plugin.Degraded || !BottomlessContainer.TryResolve(__instance, out var bottomless)) { return true; } bottomless.EnsureRegistered(); bottomless.SaveToStore(); return false; } } [HarmonyPatch(typeof(Container), "AddDefaultItems")] private static class NoDefaultItems { private static bool Prefix(Container __instance) { BottomlessContainer bottomless; if (!Plugin.Degraded) { return !BottomlessContainer.TryResolve(__instance, out bottomless); } return true; } } [HarmonyPatch(typeof(Container), "Load")] private static class LoadPatch { private static bool Prefix(Container __instance, ref bool __result) { if (Plugin.Degraded || !BottomlessContainer.TryResolve(__instance, out var bottomless)) { return true; } bottomless.EnsureRegistered(); __result = bottomless.LoadFromStore(); return false; } } } internal static class DestructionGuards { [HarmonyPatch(typeof(Container), "CanBeRemoved")] private static class RemovalGuard { private static bool Prefix(Container __instance, ref bool __result) { if (Plugin.Degraded || !BottomlessContainer.TryResolve(__instance, out var bottomless)) { return true; } int? num = StackCount(bottomless); __result = num.HasValue && num.Value == 0; return false; } } [HarmonyPatch(typeof(Container), "DropAllItems", new Type[] { })] private static class DropGuard { private static bool Prefix(Container __instance) { return SuppressDrop(__instance); } } [HarmonyPatch(typeof(Container), "DropAllItems", new Type[] { typeof(GameObject) })] private static class DropIntoContainerGuard { private static bool Prefix(Container __instance) { return SuppressDrop(__instance); } } private static int? StackCount(BottomlessContainer bottomless) { string currentStoreId = bottomless.CurrentStoreId; if (string.IsNullOrEmpty(currentStoreId)) { return 0; } if (SidecarStore.IsServerAuthority) { if (ChestSessions.TryGet(currentStoreId, out var session)) { return session.TotalCount; } return bottomless.Inventory?.m_inventory.Count ?? 0; } return ChestView.KnownTotalFor(currentStoreId); } private static bool SuppressDrop(Container container) { if (Plugin.Degraded || !BottomlessContainer.TryResolve(container, out var bottomless)) { return true; } Plugin.Log.LogWarning((object)("A bottomless chest was destroyed. Its contents were NOT dropped; store " + bottomless.CurrentStoreId + " is intact and can be reattached with 'bottomless rebind'.")); return false; } } internal static class HoverTextPatch { [HarmonyPatch(typeof(Container), "GetHoverText")] private static class Patch { private static bool Prefix(Container __instance, ref string __result) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Degraded || !BottomlessContainer.TryResolve(__instance, out var _)) { return true; } if (__instance.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false)) { return true; } __result = Localization.instance.Localize(__instance.m_name + "\n[<color=yellow><b>$KEY_Use</b></color>] $piece_container_open"); return false; } } } internal static class InventoryCapacity { [HarmonyPatch(typeof(Inventory), "TopFirst")] private static class TopFirstPatch { private static bool Prefix(Inventory __instance, ref bool __result) { if (!Unbounded.Contains(__instance)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(Inventory), "Changed")] private static class ChangedPatch { private static void Postfix(Inventory __instance) { if (Unbounded.Contains(__instance) && !Suspended) { Apply(__instance); ChestView.OnInventoryChanged(__instance); } } } private static readonly HashSet<Inventory> Unbounded = new HashSet<Inventory>(); internal static bool Suspended { get; set; } internal static void Register(Inventory inventory) { if (inventory != null && Unbounded.Add(inventory)) { Apply(inventory); } } internal static void Forget(Inventory inventory) { if (inventory != null) { Unbounded.Remove(inventory); } } internal static bool IsUnbounded(Inventory inventory) { return Unbounded.Contains(inventory); } internal static bool LayoutIsUsable(Inventory inventory, int rows) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) int num = 8; if (inventory.m_inventory.Count > num * rows) { return false; } List<GridPos> list = new List<GridPos>(inventory.m_inventory.Count); foreach (ItemData item in inventory.m_inventory) { list.Add(new GridPos(item.m_gridPos.x, item.m_gridPos.y)); } return GridPacker.LayoutFitsWindow((IReadOnlyList<GridPos>)list, num, rows); } internal static void Repack(Inventory inventory) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) int num = 8; List<ItemData> inventory2 = inventory.m_inventory; for (int i = 0; i < inventory2.Count; i++) { GridPos val = GridPacker.PositionOf(i, num); inventory2[i].m_gridPos = new Vector2i(((GridPos)(ref val)).X, ((GridPos)(ref val)).Y); } } internal static void Apply(Inventory inventory) { ApplyFor(inventory, inventory.m_inventory.Count); } internal static void ApplyFor(Inventory inventory, int count) { int num = 8; int windowSlots = ChestView.WindowSlots; int num2 = GridPacker.RowsForChest(count + windowSlots, num, 6); foreach (ItemData item in inventory.m_inventory) { if (item.m_gridPos.y >= num2) { num2 = item.m_gridPos.y + 1; } } inventory.m_width = num; inventory.m_height = num2; } } internal static class RemoteMovePatches { [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData) })] private static class MoveWhole { private static bool Prefix(Inventory __instance, Inventory fromInventory, ItemData item) { return Intercept(__instance, fromInventory, item); } } [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData), typeof(int), typeof(int), typeof(int) })] private static class MovePart { private static bool Prefix(Inventory __instance, Inventory fromInventory, ItemData item, ref bool __result) { if (!Intercept(__instance, fromInventory, item)) { __result = true; return false; } return true; } } private static readonly List<int> OneSlot = new List<int>(1); private static bool Intercept(Inventory destination, Inventory source, ItemData item) { if (Plugin.Degraded || item == null) { return true; } if (ChestView.IsRemotePage(source)) { int num = ChestView.PageSlotOf(item); if (num < 0) { return true; } OneSlot.Clear(); OneSlot.Add(num); ChestView.RequestTake(OneSlot); return false; } if (ChestView.IsRemotePage(destination)) { return !ChestView.RequestPut(item); } return true; } } internal static class StackAllButtonPatch { [HarmonyPatch(typeof(InventoryGui), "OnStackAll")] private static class Patch { private static bool Prefix(InventoryGui __instance) { if (Plugin.Degraded || (Object)(object)__instance.m_currentContainer == (Object)null || (Object)(object)Player.m_loc