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 list; try { ZPackage val = new ZPackage(contents); if (val.ReadInt() != 106) { return false; } expected = val.ReadInt(); list = new List(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 dictionary = null; for (int j = 0; j < num; j++) { dictionary = dictionary ?? new Dictionary(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() : 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 _entries = new Dictionary(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 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 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 RecipeRequirements; internal static ConfigEntry SnapshotDebounceSeconds; internal static ConfigEntry ModelScale; internal static ConfigEntry BodyTint; internal static ConfigEntry LidTint; internal static ConfigEntry GlowColour; internal static ConfigEntry 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("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(0.25f, 3f), Array.Empty())); SnapshotDebounceSeconds = cfg.Bind("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(0.1f, 10f), Array.Empty())); BodyTint = cfg.Bind("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("Appearance", "LidTint", "#2E2E33", "Hex colour multiplied over the chest lid. Darker than the body makes the glow read against it."); GlowColour = cfg.Bind("Appearance", "GlowColour", "#8FA86B", "Hex colour of the emissive glow on the chest lid."); GlowStrength = cfg.Bind("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(0f, 5f), Array.Empty())); RecipeRequirements = cfg.Bind("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 list = new List(); 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 { { "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(); 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(); 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 list = new List(); Renderer[] componentsInChildren = prefab.GetComponentsInChildren(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 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 list4 = new List(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 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 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 list = Deserialize(package.ReadByteArray()); ChestSession session; bool flag = ChestSessions.TryGet(text, out session); ChestSession chestSession = ChestSessions.Acquire(text); if (chestSession == null) { break; } HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (ItemData item3 in chestSession.Inventory.m_inventory) { hashSet.Add(StackKey(item3)); } List list2 = new List(); 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 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 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 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(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 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 list = new List(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(); if ((Object)(object)component != (Object)null && (Object)(object)component.verticalScrollbar == (Object)(object)_bar) { _detachedFrom = component; component.verticalScrollbar = null; } ((UnityEvent)(object)_bar.onValueChanged).AddListener((UnityAction)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)(object)_bar.onValueChanged).RemoveListener((UnityAction)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(); 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(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(); 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(); 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() ?? _field.GetComponentInChildren(); if ((Object)(object)_input == (Object)null) { Plugin.Log.LogError((object)"Search field was created but carries no InputField."); DestroyWidgets(); return; } ((UnityEvent)(object)_input.onValueChanged).AddListener((UnityAction)OnChanged); _field.AddComponent().TakeFocus(); _field.AddComponent(); _field.AddComponent().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(); RectTransform component2 = _field.GetComponent(); 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(); } 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 KnownTotals = new Dictionary(StringComparer.Ordinal); private static readonly List Matched = new List(); private static readonly List Rest = new List(); internal const int Width = 8; internal const int VisibleRows = 6; private static List _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 MatchingItems() { List list = new List(); if (_target == null) { return list; } List 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 inventory = _target.m_inventory; if (string.IsNullOrWhiteSpace(_query)) { _matchCount = inventory.Count; } else { ItemQuery val = ItemQuery.Parse(_query); List matched = Matched; List 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 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 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 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(); 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 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 TrimToCapacity(IReadOnlyList 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 list = new List(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 pageSlots) { if (!_remote || pageSlots.Count == 0) { return; } List list = new List(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 pageSlots) { if (_target == null) { return; } HashSet doomed = new HashSet(); 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 inventory = _view.m_inventory; inventory.Clear(); List 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 SearchKeys = new Dictionary(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 Registry = new Dictionary(); private Container _container; private ZNetView _nview; private bool _contentsLoaded; private bool _warnedAboutUnloadedSave; private bool _loadWasPartial; internal static IEnumerable 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(); _nview = ((Component)this).GetComponent(); 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 _ordered = new List(); 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 Page(int scrollRow, int count) { EnsureOrder(); int num = Mathf.Max(0, GridPacker.RowsNeeded(_ordered.Count, 8) - 5); LastScrollRow = Mathf.Clamp(scrollRow, 0, num); List list = new List(count); for (int i = LastScrollRow * 8; i < _ordered.Count; i++) { if (list.Count >= count) { break; } list.Add(_ordered[i]); } return list; } internal List Take(IEnumerable indices) { EnsureOrder(); List list = new List(); 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 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 Open = new Dictionary(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 list = null; foreach (KeyValuePair item in Open) { if (item.Value.LastUsedUtc < dateTime) { list = list ?? new List(); 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[$KEY_Use] $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 Unbounded = new HashSet(); 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 list = new List(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)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 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 OneSlot = new List(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_localPlayer == (Object)null) { return true; } if (!BottomlessContainer.TryResolve(__instance.m_currentContainer, out var bottomless) || SidecarStore.IsServerAuthority) { return true; } return !ChestView.RequestStackAll(bottomless.CurrentStoreId); } } } internal static class StackAllPatch { [HarmonyPatch(typeof(Container), "RPC_StackResponse")] private static class Patch { private static bool Prefix(Container __instance, bool granted) { if (Plugin.Degraded || !granted || (Object)(object)Player.m_localPlayer == (Object)null) { return true; } if (!BottomlessContainer.TryResolve(__instance, out var bottomless) || SidecarStore.IsServerAuthority) { return true; } Plugin.Log.LogDebug((object)("Deposit requested for chest " + bottomless.CurrentStoreId + ".")); return !ChestView.RequestStackAll(bottomless.CurrentStoreId); } } } internal static class TakeAllButtonPatch { [HarmonyPatch(typeof(InventoryGui), "OnTakeAll")] private static class Patch { private static bool Prefix(InventoryGui __instance) { if (Plugin.Degraded || (Object)(object)__instance.m_currentContainer == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return true; } if (!BottomlessContainer.TryResolve(__instance.m_currentContainer, out var _) || !ChestView.IsRemote) { return true; } Inventory inventory = __instance.m_currentContainer.GetInventory(); List list = new List(inventory.m_inventory.Count); for (int i = 0; i < inventory.m_inventory.Count; i++) { list.Add(i); } ChestView.RequestTake(ChestView.TrimToCapacity(list)); return false; } } } internal static class TakeAllPatch { [HarmonyPatch(typeof(Container), "RPC_TakeAllRespons")] private static class Patch { private static bool Prefix(Container __instance, long uid, bool granted) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Degraded || !granted || (Object)(object)Player.m_localPlayer == (Object)null) { return true; } if (!BottomlessContainer.TryResolve(__instance, out var _)) { return true; } if (ChestView.IsRemote) { List list = new List(); for (int i = 0; i < __instance.m_inventory.m_inventory.Count; i++) { list.Add(i); } ChestView.RequestTake(ChestView.TrimToCapacity(list)); return false; } if (!ChestView.IsFiltering) { return true; } __instance.m_nview.ClaimOwnership(); ZDOMan.instance.ForceSendZDO(uid, __instance.m_nview.GetZDO().m_uid); Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory(); int num = 0; foreach (ItemData item in ChestView.MatchingItems()) { if (inventory.AddItem(item, item.m_stack, item.m_gridPos.x, item.m_gridPos.y)) { __instance.m_inventory.RemoveItem(item); num++; } else if (inventory.AddItem(item)) { __instance.m_inventory.RemoveItem(item); num++; } } ((Character)Player.m_localPlayer).Message((MessageType)2, (num > 0) ? $"$msg_added {num}" : "$inventory_full", 0, (Sprite)null); __instance.m_onTakeAllSuccess?.Invoke(); return false; } } } internal static class TestData { internal static bool IsDisplayable(ItemDrop drop) { SharedData val = ((!((Object)(object)drop != (Object)null)) ? null : drop.m_itemData?.m_shared); if (val != null && val.m_icons != null && val.m_icons.Length != 0) { return !string.IsNullOrEmpty(val.m_name); } return false; } internal static List Build(int stacks, string prefabName, out string error) { error = null; List list = new List(); ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { error = "Item database is not loaded."; return list; } List list2 = new List(); if (!string.IsNullOrEmpty(prefabName)) { GameObject itemPrefab = instance.GetItemPrefab(prefabName); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if (!IsDisplayable(val)) { error = "'" + prefabName + "' is not an item that can sit in a chest."; return list; } list2.Add(val); } else { foreach (GameObject item in instance.m_items) { ItemDrop val2 = (((Object)(object)item != (Object)null) ? item.GetComponent() : null); if (IsDisplayable(val2)) { list2.Add(val2); } } } if (list2.Count == 0) { error = "No usable item prefabs found."; return list; } for (int i = 0; i < stacks; i++) { ItemDrop val3 = list2[i % list2.Count]; ItemData val4 = val3.m_itemData.Clone(); val4.m_dropPrefab = ((Component)val3).gameObject; val4.m_stack = Mathf.Max(1, val4.m_shared.m_maxStackSize); val4.m_quality = 1; val4.m_variant = 0; list.Add(val4); } return list; } } } namespace BottomlessChest.Commands { internal class BottomlessCommand : ConsoleCommand { private const float SearchRadius = 8f; public override string Name => "bottomless"; public override string Help => "bottomless list | here | rebind | fill [prefab] | empty (fill and empty need devcommands)"; public override void Run(string[] args) { if ((Object)(object)Console.instance == (Object)null) { Plugin.Log.LogInfo((object)"bottomless: no console available (headless server)."); return; } if (args.Length == 0) { Console.instance.Print(((ConsoleCommand)this).Help); return; } switch (args[0].ToLowerInvariant()) { case "list": List(); break; case "here": Here(); break; case "rebind": Rebind(args); break; case "fill": if (RequireCheats()) { Fill(args); } break; case "empty": if (RequireCheats()) { Empty(); } break; default: Console.instance.Print(((ConsoleCommand)this).Help); break; } } private static void List() { List<(string, int)> list = SidecarStore.Instance.Describe().ToList(); if (list.Count == 0) { Console.instance.Print("No chest stores in this world."); return; } HashSet hashSet = new HashSet(from c in BottomlessContainer.Loaded select c.CurrentStoreId into id where !string.IsNullOrEmpty(id) select id); foreach (var item in list) { string arg = (hashSet.Contains(item.Item1) ? "in use" : "ORPHANED"); Console.instance.Print($"{item.Item1} {item.Item2} stacks [{arg}]"); } } private static void Here() { BottomlessContainer bottomlessContainer = Nearest(); Console.instance.Print(((Object)(object)bottomlessContainer == (Object)null) ? $"No bottomless chest within {8f}m." : ("Nearest chest is bound to store " + bottomlessContainer.CurrentStoreId + ".")); } private static void Rebind(IReadOnlyList args) { if (args.Count < 2) { Console.instance.Print("Usage: bottomless rebind (stand next to the chest)"); return; } BottomlessContainer bottomlessContainer = Nearest(); if ((Object)(object)bottomlessContainer == (Object)null) { Console.instance.Print($"No bottomless chest within {8f}m. Stand closer."); return; } string text = args[1].Trim(); if (!SidecarStore.Instance.TryGet(text, out var _)) { Console.instance.Print("No store named '" + text + "'. Run 'bottomless list' to see them."); return; } string currentStoreId = bottomlessContainer.CurrentStoreId; Console.instance.Print(bottomlessContainer.Rebind(text) ? ("Chest rebound from " + currentStoreId + " to " + text + ".") : "Could not rebind - the chest is not owned by this client. Try again in a moment."); } private static bool RequireCheats() { if ((Object)(object)Console.instance != (Object)null && ((Terminal)Console.instance).IsCheatsEnabled()) { return true; } Console instance = Console.instance; if (instance != null) { instance.Print("This is a testing command. Enable it with 'devcommands' first."); } return false; } private static void Fill(IReadOnlyList args) { BottomlessContainer bottomlessContainer = Nearest(); if ((Object)(object)bottomlessContainer == (Object)null) { Console.instance.Print($"No bottomless chest within {8f}m."); return; } if (args.Count < 2 || !int.TryParse(args[1], out var result) || result < 1) { Console.instance.Print("Usage: bottomless fill [prefab] e.g. 'bottomless fill 500'"); return; } if (!SidecarStore.IsServerAuthority) { ChestRpc.Fill(bottomlessContainer.CurrentStoreId, result, (args.Count > 2) ? args[2] : null); Console.instance.Print($"Asked the server for {result} filler stack(s)."); return; } string error; List list = TestData.Build(result, (args.Count > 2) ? args[2] : null, out error); if (error != null) { Console.instance.Print(error); return; } bottomlessContainer.Inventory.m_inventory.AddRange(list); bottomlessContainer.NotifyFilled(); Console.instance.Print($"Added {list.Count} stack(s); chest now holds {bottomlessContainer.Inventory.m_inventory.Count}."); } private static void Empty() { BottomlessContainer bottomlessContainer = Nearest(); if ((Object)(object)bottomlessContainer == (Object)null) { Console.instance.Print($"No bottomless chest within {8f}m."); return; } if (!SidecarStore.IsServerAuthority) { ChestRpc.Clear(bottomlessContainer.CurrentStoreId); Console.instance.Print("Asked the server to empty the chest."); return; } int count = bottomlessContainer.Inventory.m_inventory.Count; bottomlessContainer.Inventory.RemoveAll(); bottomlessContainer.NotifyFilled(); Console.instance.Print($"Removed {count} stack(s)."); } private static BottomlessContainer Nearest() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0045: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } BottomlessContainer result = null; float num = 64f; foreach (BottomlessContainer item in BottomlessContainer.Loaded) { Vector3 val = item.Position - ((Component)localPlayer).transform.position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; result = item; } } return result; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }