Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RunicVault v0.7.0
plugins/RunicStorage/RunicStorage.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("RunicStorage")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.7.0.0")] [assembly: AssemblyInformationalVersion("0.7.0")] [assembly: AssemblyProduct("RunicStorage")] [assembly: AssemblyTitle("RunicStorage")] [assembly: AssemblyVersion("0.7.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RunicStorage { internal sealed class Lab : MonoBehaviour { internal sealed class SaveFailure : IOException { internal SaveFailure() : base("LAB injected save failure") { } } private static readonly FieldInfo BuildGhost = AccessTools.Field(typeof(Player), "m_placementGhost"); private static readonly FieldInfo BuildStatus = AccessTools.Field(typeof(Player), "m_placementStatus"); private static readonly FieldInfo BuildPress = AccessTools.Field(typeof(Player), "m_placePressedTime"); private static readonly FieldInfo BuildLast = AccessTools.Field(typeof(Player), "m_lastToolUseTime"); private static readonly MethodInfo NativePlacement = AccessTools.Method(typeof(Player), "UpdatePlacement", (Type[])null, (Type[])null); private static readonly MethodInfo NativeGhost = AccessTools.Method(typeof(Player), "UpdatePlacementGhost", (Type[])null, (Type[])null); private int stage; private bool legacyContinuation; private float readyAt; private string output; private Node core; private Node unit; internal static bool AudioProbe; internal static int AudioRequests; internal static bool HoldBuildReply; internal static Action BufferedReply; internal static bool FailNextPersist; internal static volatile bool HoldJournal; internal static volatile bool FailNextJournalWrite; internal static int SaveFailures; private IEnumerator TransferSettled() { float deadline = Time.unscaledTime + 15f; yield return (object)new WaitUntil((Func<bool>)(() => (!Client.Busy && !InventoryJournal.DeferredRecovery && !InventoryJournal.Writing && StorageUI.Instance.Settled) || Time.unscaledTime > deadline)); Check(!Client.Busy, "async: transfer and queue settle within the bounded test deadline"); yield return null; yield return (object)new WaitForEndOfFrame(); } private IEnumerator AsyncTransferChecks() { Check(!InventoryJournal.InlineWrites, "async: production background writer is enabled"); Player player = Player.m_localPlayer; Inventory inv = ((Humanoid)player).GetInventory(); StorageUI ui = StorageUI.Instance; Client client = Client.Instance; PlayerProfile profile = Game.instance.GetPlayerProfile(); ui.Open(core); AccessTools.Method(typeof(StorageUI), "Tab", (Type[])null, (Type[])null).Invoke(ui, new object[1] { "storage" }); yield return null; ItemData wood = AddLabItem("Wood", 3); string woodName = wood.m_shared.m_name; int stock = Items.Count(Read().Bags, woodName); int frames = Time.frameCount; HoldJournal = true; client.Deposit(wood, 1); yield return (object)new WaitForSecondsRealtime(0.2f); Check(Time.frameCount - frames >= 3 && client.CheckpointBusy && Client.Busy && Items.Count(Read().Bags, woodName) == stock, "async: paused disk worker leaves frames running and sends no unprepared operation"); HoldJournal = false; yield return TransferSettled(); Check(Items.Count(Read().Bags, woodName) == stock + 1 && InventoryJournal.LastWriterThread != Thread.CurrentThread.ManagedThreadId, "async: prepared deposit commits once and filesystem writes use a different thread"); wood = inv.GetAllItems().First((ItemData item2) => Plugin.Prefab(item2) == "Wood"); string sequence = player.m_customData["runic.sequence"]; string path = InventoryJournal.PathFor(profile); long revision = InventoryJournal.Current(player); byte[] previous = File.ReadAllBytes(path); int pocket = inv.CountItems(woodName, -1, true); stock = Items.Count(Read().Bags, woodName); HoldJournal = true; client.Deposit(wood, 1); ItemData val = AddLabItem("Resin", 2); string resinName = val.m_shared.m_name; int resin = inv.CountItems(resinName, -1, true); Directory.CreateDirectory(path + ".tmp"); HoldJournal = false; yield return TransferSettled(); Directory.Delete(path + ".tmp"); Check(inv.CountItems(woodName, -1, true) == pocket && inv.CountItems(resinName, -1, true) == resin && Items.Count(Read().Bags, woodName) == stock && player.m_customData["runic.sequence"] == sequence, "async: preparation disk failure refunds only escrow and preserves intervening pickups without sending RPC"); Check(InventoryJournal.Current(player) == revision && File.ReadAllBytes(path).SequenceEqual(previous), "async: failed worker preserves prior checkpoint bytes and revision"); ItemData item = ui.Stored.GetAllItems().First((ItemData value) => Plugin.Prefab(value) == "Wood"); stock = Items.Count(Read().Bags, woodName); pocket = inv.CountItems(woodName, -1, true); int callbacks = 0; HoldBuildReply = true; BufferedReply = null; client.BeginRequest("withdraw", Items.Key(item), null, 1, 1, "", null, delegate { callbacks++; }); yield return (object)new WaitUntil((Func<bool>)(() => BufferedReply != null)); Action receipt = BufferedReply; BufferedReply = null; HoldBuildReply = false; HoldJournal = true; receipt(); receipt(); Check(inv.CountItems(woodName, -1, true) == pocket && Client.Busy && client.CheckpointBusy && callbacks == 0, "async: duplicate receipt during completion write exposes no delivery and fires no callback"); frames = Time.frameCount; yield return (object)new WaitForSecondsRealtime(0.15f); Check(Time.frameCount - frames >= 3 && inv.CountItems(woodName, -1, true) == pocket, "async: completion disk wait does not block frames or deliver before durability"); int failures = SaveFailures; FailNextJournalWrite = true; HoldJournal = false; yield return (object)new WaitUntil((Func<bool>)(() => !client.CheckpointBusy)); Check(SaveFailures == failures + 1 && Client.Busy && inv.CountItems(woodName, -1, true) == pocket && callbacks == 0, "async: failed completion remains pending and leaves delivery untouched"); receipt(); receipt(); yield return TransferSettled(); receipt(); Check(callbacks == 1 && inv.CountItems(woodName, -1, true) == pocket + 1 && Items.Count(Read().Bags, woodName) == stock - 1 && !player.m_customData.ContainsKey("runic.pending"), "async: retry and repeated committed receipt deliver and consume exactly once"); profile.SavePlayerData(player); HoldBuildReply = true; BufferedReply = null; wood = inv.GetAllItems().First((ItemData value) => Items.Key(value) == Items.Key(item)); stock = Items.Count(Read().Bags, woodName); pocket = inv.CountItems(woodName, -1, true); client.Deposit(wood, 1); yield return (object)new WaitUntil((Func<bool>)(() => BufferedReply != null)); receipt = BufferedReply; BufferedReply = null; HoldBuildReply = false; string text = Items.Save(inv); revision = InventoryJournal.Current(player); LoadLabProfile(profile, player); Check(Items.Save(inv) == text && InventoryJournal.Current(player) == revision && player.m_customData.ContainsKey("runic.pending"), "async: older native save recovers the prepared inventory and pending operation"); receipt(); receipt(); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == pocket - 1 && Items.Count(Read().Bags, woodName) == stock + 1, "async: recovered committed deposit settles without another server effect"); item = ui.Stored.GetAllItems().First((ItemData value) => Plugin.Prefab(value) == "Wood"); stock = Items.Count(Read().Bags, woodName); pocket = inv.CountItems(woodName, -1, true); int intermediateSaves = 0; Action saveInsideChange = delegate { if (player.m_customData.ContainsKey("runic.pending")) { profile.SavePlayerData(player); intermediateSaves++; } }; inv.m_onChanged = (Action)Delegate.Combine(inv.m_onChanged, saveInsideChange); client.BeginRequest("withdraw", Items.Key(item)); yield return TransferSettled(); inv.m_onChanged = (Action)Delegate.Remove(inv.m_onChanged, saveInsideChange); LoadLabProfile(profile, player); Check(intermediateSaves > 0 && !player.m_customData.ContainsKey("runic.pending") && inv.CountItems(woodName, -1, true) == pocket + 1 && Items.Count(Read().Bags, woodName) == stock - 1, "async: recovery outranks a native save captured inside delivery without duplicating its item"); profile.SavePlayerData(player); wood = inv.GetAllItems().First((ItemData value) => Plugin.Prefab(value) == "Wood"); stock = Items.Count(Read().Bags, woodName); pocket = inv.CountItems(woodName, -1, true); HoldJournal = true; client.Deposit(wood, 1); LoadLabProfile(profile, player); Check(InventoryJournal.DeferredRecovery && InventoryJournal.Blocked, "async: character reload during preparation defers recovery and blocks new operations"); HoldJournal = false; yield return TransferSettled(); Check(!InventoryJournal.Blocked && inv.CountItems(woodName, -1, true) == pocket - 1 && Items.Count(Read().Bags, woodName) == stock + 1, "async: deferred preparation recovery retries the durable request once"); profile.SavePlayerData(player); item = ui.Stored.GetAllItems().First((ItemData value) => Plugin.Prefab(value) == "Wood"); stock = Items.Count(Read().Bags, woodName); pocket = inv.CountItems(woodName, -1, true); HoldBuildReply = true; BufferedReply = null; client.BeginRequest("withdraw", Items.Key(item)); yield return (object)new WaitUntil((Func<bool>)(() => BufferedReply != null)); receipt = BufferedReply; BufferedReply = null; HoldBuildReply = false; HoldJournal = true; receipt(); LoadLabProfile(profile, player); HoldJournal = false; yield return (object)new WaitUntil((Func<bool>)(() => !InventoryJournal.DeferredRecovery)); yield return TransferSettled(); Check(!player.m_customData.ContainsKey("runic.pending") && inv.CountItems(woodName, -1, true) == pocket + 1 && Items.Count(Read().Bags, woodName) == stock - 1, "async: deferred completion recovery invalidates old callbacks and does not deliver twice"); wood = AddLabItem("Wood", 2); InventoryGrid grid = InventoryGui.instance.m_playerGrid; stock = Items.Count(Read().Bags, woodName); pocket = inv.CountItems(woodName, -1, true); grid.m_onSelected(grid, wood, wood.m_gridPos, (Modifier)2); grid.m_onSelected(grid, wood, wood.m_gridPos, (Modifier)2); Check(client.QueuedDeposits == 1 && inv.ContainsItem(wood), "queue: repeated Move clicks keep one intention without removing unsent items"); ui.Close(); yield return (object)new WaitForSecondsRealtime(0.1f); Check(client.QueuedDeposits == 0 && !Client.Busy && inv.CountItems(woodName, -1, true) == pocket && Items.Count(Read().Bags, woodName) == stock, "queue: closing cancels unsent intentions without touching inventory or storage"); ui.Open(core); yield return null; grid.m_onSelected(grid, wood, wood.m_gridPos, (Modifier)2); ui.SetTarget("another-core", ((Component)core).transform.position, 5f); yield return null; Check(client.QueuedDeposits == 0 && inv.CountItems(woodName, -1, true) == pocket, "queue: changing target cancels instead of depositing into another core"); ui.Open(core); yield return null; grid.m_onSelected(grid, wood, wood.m_gridPos, (Modifier)2); inv.RemoveItem(wood); yield return TransferSettled(); Check(Items.Count(Read().Bags, woodName) == stock, "queue: an item removed before sending is skipped without phantom storage stock"); HoldBuildReply = true; BufferedReply = null; item = ui.Stored.GetAllItems().First((ItemData value) => Plugin.Prefab(value) == "Wood"); client.BeginRequest("withdraw", Items.Key(item)); yield return (object)new WaitUntil((Func<bool>)(() => BufferedReply != null)); ItemData a = AddLabItem("Stone", 2); ItemData b = AddLabItem("Coal", 2); grid.m_onSelected(grid, a, a.m_gridPos, (Modifier)2); grid.m_onSelected(grid, b, b.m_gridPos, (Modifier)2); yield return (object)new WaitForSecondsRealtime(0.1f); Check(client.QueuedDeposits == 2 && inv.ContainsItem(a) && inv.ContainsItem(b), "queue: new native Move clicks wait while a real server receipt is pending"); receipt = BufferedReply; BufferedReply = null; HoldBuildReply = false; receipt(); receipt(); yield return TransferSettled(); Check(!inv.ContainsItem(a) && !inv.ContainsItem(b) && ui.Stored.GetAllItems().Any((ItemData value) => Plugin.Prefab(value) == "Coal"), "queue: queued clicks flush after acknowledgement without being ignored"); ui.Close(); Client.Persist(); } private ItemData AddLabItem(string name, int amount = 1) { ItemData val = Items.Prefab(name).Clone(); val.m_dropPrefab = ObjectDB.instance.GetItemPrefab(name); val.m_stack = amount; Items.Add(((Humanoid)Player.m_localPlayer).GetInventory(), val); return ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems().First((ItemData i) => Plugin.Prefab(i) == name); } private static Vector3 LabLandPoint(Vector3 center, float distance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 72; i++) { float num = (float)i * (float)Math.PI / 36f; Vector3 point = center + new Vector3(Mathf.Cos(num) * distance, 0f, Mathf.Sin(num) * distance); float height = WorldGenerator.instance.GetHeight(point.x, point.z); if (!(height < 35f) && !(height > 90f) && !((IEnumerable<Vector3>)(object)new Vector3[4] { Vector3.forward * 8f, Vector3.back * 8f, Vector3.right * 8f, Vector3.left * 8f }).Any((Vector3 offset) => Mathf.Abs(WorldGenerator.instance.GetHeight(point.x + offset.x, point.z + offset.z) - height) > 3f)) { point.y = height + 3f; return point; } } throw new InvalidOperationException("No dry, flat lab teleport site found at range " + distance); } private IEnumerator LabTeleport(Vector3 point, Quaternion rotation) { //IL_000e: 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_0015: 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) Player player = Player.m_localPlayer; yield return (object)new WaitForSecondsRealtime(2.1f); Check(((Character)player).TeleportTo(point, rotation, true), "remote: native teleport begins"); float deadline = Time.unscaledTime + 30f; yield return (object)new WaitUntil((Func<bool>)(() => !((Character)player).IsTeleporting() || Time.unscaledTime > deadline)); Check(!((Character)player).IsTeleporting() && Vector3.Distance(((Component)player).transform.position, point) < 10f, "remote: player physically arrives at the requested location"); yield return (object)new WaitForSecondsRealtime(2f); } private int PieceCount(Piece piece, Vector3 origin) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) return Network.Nearby(origin, 20f).Count((ZDO z) => z.GetPrefab() == StringExtensionMethods.GetStableHashCode(((Object)((Component)piece).gameObject).name)); } private void AimBuild(Player player, Piece piece, Vector3 target) { //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_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_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) Camera mainCamera = Utils.GetMainCamera(); ((Component)mainCamera).transform.position = ((Component)player).transform.position + Vector3.up * 2f + Vector3.back * 0.4f; ((Component)mainCamera).transform.LookAt(target); NativeGhost.Invoke(player, new object[1] { false }); } private Vector3 AimValidBuild(Player player, Piece piece, Vector3 origin) { //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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: 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_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[6] { new Vector3(-3f, 0f, -2f), new Vector3(3f, 0f, -2f), new Vector3(-3f, 0f, 2f), new Vector3(3f, 0f, 2f), new Vector3(-4f, 0f, 0f), new Vector3(4f, 0f, 0f) }; GameObject ghost = default(GameObject); foreach (Vector3 val in array) { Vector3 val2 = origin + val; val2.y = ZoneSystem.instance.GetGroundHeight(val2); AimBuild(player, piece, val2); ref GameObject reference = ref ghost; object? value = BuildGhost.GetValue(player); reference = (GameObject)((value is GameObject) ? value : null); if ((int)(PlacementStatus)BuildStatus.GetValue(player) == 0 && Object.op_Implicit((Object)(object)ghost) && !Network.Nearby(origin, 20f).Any(delegate(ZDO z) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (z.GetPrefab() == StringExtensionMethods.GetStableHashCode(((Object)((Component)piece).gameObject).name)) { Vector3 val3 = z.GetPosition() - ghost.transform.position; return ((Vector3)(ref val3)).sqrMagnitude < 2.25f; } return false; })) { return val2; } } throw new InvalidOperationException("No valid unused native placement ray for the lab floor"); } private unsafe IEnumerator BuildRemoteChecks() { Player player = Player.m_localPlayer; Inventory inv = ((Humanoid)player).GetInventory(); Vector3 origin = ((Component)player).transform.position; Vector3 returnOrigin = origin; Quaternion rotation = ((Component)player).transform.rotation; ZDOID coreId = core.View.GetZDO().m_uid; ZDOID unitId = unit.View.GetZDO().m_uid; Vector3 corePos = ((Component)core).transform.position; string originalInventory = Items.Save(inv); string coreData = Network.ReadData(core.View.GetZDO()); string unitData = Network.ReadData(unit.View.GetZDO()); float originalLimit = Plugin.RemoteRange.Value; Plugin.RemoteRange.Value = 100f; StorageUI.Instance.Close(); inv.RemoveAll(); ItemData remote = AddLabItem("MagicStorageWirelessRemote"); ItemData hammer = AddLabItem("MagicStorageHammer"); hammer.m_durability = hammer.GetMaxDurability(); Check(!RemoteControl.TryTarget(remote, out var id, out var pos, out var range, quiet: true), "remote: unbound remote cannot open a network"); Check(RemoteControl.Bind(remote, core) && remote.m_customData["runic.world"] == ZNet.instance.GetWorld().m_uid.ToString(), "remote: binding records the real core and current world"); Check(!Client.Busy && RemoteControl.Tier(remote) == 0 && RemoteControl.Limit(0) == 100f, "remote: starting remote has a server-confirmed 100m range"); Network network = Read(); string name = Items.Prefab("SurtlingCore").m_shared.m_name; int num = Items.Count(network.Bags, name); if (num > 0) { Items.Consume(network.Bags, name, num); network.Commit(); } RemoteControl.Upgrade(remote); Check(!Client.Busy && RemoteControl.Tier(remote) == 0, "remote: an upgrade without its biome ingredients is rejected"); Network.WriteData(core.View.GetZDO(), coreData); Network.WriteData(unit.View.GetZDO(), unitData); Network.Sync(core.View.GetZDO()); Network.Sync(unit.View.GetZDO()); for (int i = 1; i <= 3; i++) { RemoteControl.UpgradeIngredient[] array = RemoteControl.UpgradeCosts[i]; RemoteControl.UpgradeIngredient[] array2 = array; for (int j = 0; j < array2.Length; j++) { RemoteControl.UpgradeIngredient upgradeIngredient = array2[j]; AddLabItem(upgradeIngredient.Prefab, upgradeIngredient.Amount); } Dictionary<string, int> before = array.ToDictionary((RemoteControl.UpgradeIngredient upgradeIngredient4) => upgradeIngredient4.Prefab, (RemoteControl.UpgradeIngredient upgradeIngredient4) => inv.CountItems(upgradeIngredient4.Item.m_shared.m_name, -1, true) + Items.Count(Read().Bags, upgradeIngredient4.Item.m_shared.m_name)); HoldBuildReply = true; BufferedReply = null; RemoteControl.Upgrade(remote); Check(BufferedReply != null, "remote: upgrade gets a real routed receipt " + i); Action bufferedReply = BufferedReply; BufferedReply = null; HoldBuildReply = false; bufferedReply(); bufferedReply(); Check(RemoteControl.Tier(remote) == i && RemoteControl.ServerTier(core.View.GetZDO(), player.GetPlayerID()) == i && RemoteControl.Limit(i) == 100f * Mathf.Pow(2f, (float)i), "remote: client and server agree on doubled range " + i); Check(array.All((RemoteControl.UpgradeIngredient upgradeIngredient4) => inv.CountItems(upgradeIngredient4.Item.m_shared.m_name, -1, true) + Items.Count(Read().Bags, upgradeIngredient4.Item.m_shared.m_name) == before[upgradeIngredient4.Prefab] - upgradeIngredient4.Amount), "remote: duplicate upgrade receipt consumes every biome ingredient exactly once " + i); } string value = remote.m_customData["runic.world"]; string value2 = remote.m_customData["magicstorage.remote.corepos"]; remote.m_customData["runic.world"] = "-123"; Check(!RemoteControl.TryTarget(remote, out id, out pos, out range, quiet: true), "remote: binding from another world is rejected"); remote.m_customData["runic.world"] = value; remote.m_customData["magicstorage.remote.corepos"] = "NaN,0,0"; Check(!RemoteControl.TryTarget(remote, out id, out pos, out range, quiet: true), "remote: invalid stored coordinates are rejected"); remote.m_customData["magicstorage.remote.corepos"] = value2; core.ToggleRange(); LineRenderer ring = ((Component)core).GetComponentInChildren<LineRenderer>(); Check(Object.op_Implicit((Object)(object)ring) && ((Renderer)ring).enabled && ring.useWorldSpace && Enumerable.Range(0, ring.positionCount).All((int num9) => Mathf.Abs(ring.GetPosition(num9).y - ZoneSystem.instance.GetGroundHeight(ring.GetPosition(num9)) - 0.08f) < 0.015f), "range: the visible network circle follows the actual terrain"); core.ToggleRange(); RemoteControl.Use(remote); Check(StorageUI.Instance.Visible && StorageUI.Instance.RemoteAccess && StorageUI.Instance.Stored.NrOfItems() > 0, "remote: using the bound item opens the live network through routed RPC"); StorageUI.Instance.Close(); PieceTable table = hammer.m_shared.m_buildPieces; Piece floor = table.m_pieces.Select((GameObject g) => g.GetComponent<Piece>()).First((Piece p) => ((Object)((Component)p).gameObject).name == "wood_floor"); AccessTools.Method(typeof(Player), "AddKnownPiece", (Type[])null, (Type[])null).Invoke(player, new object[1] { floor }); CraftingStation[] array3 = Object.FindObjectsOfType<CraftingStation>(); foreach (CraftingStation val in array3) { player.AddKnownStation(val); } ((Humanoid)player).EquipItem(hammer, false); yield return (object)new WaitForSecondsRealtime(1f); Check(NetworkBuild.Tool(player) == hammer && !player.PlacementCostDisabled, "build: real runic hammer equipped with placement costs enabled"); table.UpdateAvailable((HashSet<string>)AccessTools.Field(typeof(Player), "m_knownRecipes").GetValue(player), player, true, false); Check(player.SetSelectedPiece(floor), "build: floor is selectable with all wood stored in the network"); Camera camera = Utils.GetMainCamera(); GameCamera controller = GameCamera.instance; bool cameraEnabled = ((Behaviour)controller).enabled; Vector3 cameraPosition = ((Component)camera).transform.position; Quaternion cameraRotation = ((Component)camera).transform.rotation; ((Behaviour)controller).enabled = false; Vector3 val2 = origin + new Vector3(-3f, 0f, -2f); val2.y = ZoneSystem.instance.GetGroundHeight(val2); AimBuild(player, floor, val2); if ((int)(PlacementStatus)BuildStatus.GetValue(player) == 3) { int num2 = Items.Count(Read().Bags, Items.Prefab("Wood").m_shared.m_name); Check(!player.TryPlacePiece(floor) && !Client.Busy && Items.Count(Read().Bags, Items.Prefab("Wood").m_shared.m_name) == num2, "build: the starting altar no-build zone rejects placement without withdrawing materials"); } ((Behaviour)controller).enabled = cameraEnabled; ((Humanoid)player).UnequipItem(hammer, false); Vector3 point = returnOrigin + new Vector3(45f, 5f, 0f); yield return LabTeleport(point, rotation); origin = ((Component)player).transform.position; Vector3 val3 = origin + Vector3.forward * 6f; val3.y = ZoneSystem.instance.GetGroundHeight(val3); GameObject obj = Object.Instantiate<GameObject>(ZNetScene.instance.GetPrefab("piece_workbench"), val3, Quaternion.identity); obj.GetComponent<ZNetView>().GetZDO().Set("runic.lab.fixture", true); obj.GetComponent<WearNTear>().m_noSupportWear = true; obj.GetComponent<WearNTear>().m_noRoofWear = true; ((Humanoid)player).EquipItem(hammer, false); yield return (object)new WaitForSecondsRealtime(1f); player.SetSelectedPiece(floor); ((Behaviour)controller).enabled = false; Vector3[] candidates = (Vector3[])(object)new Vector3[4] { new Vector3(-3f, 0f, -2f), new Vector3(3f, 0f, -2f), new Vector3(-3f, 0f, 2f), new Vector3(3f, 0f, 2f) }; Vector3[] array4 = candidates; Vector3 val5; foreach (Vector3 val4 in array4) { val5 = origin + val4; val5.y = ZoneSystem.instance.GetGroundHeight(val5); AimBuild(player, floor, val5); string[] obj2 = new string[10] { "LAB build ray: status=", BuildStatus.GetValue(player)?.ToString(), " origin=", null, null, null, null, null, null, null }; pos = origin; obj2[3] = ((object)(*(Vector3*)(&pos))/*cast due to .constrained prefix*/).ToString(); obj2[4] = " target="; pos = val5; obj2[5] = ((object)(*(Vector3*)(&pos))/*cast due to .constrained prefix*/).ToString(); obj2[6] = " camera="; obj2[7] = ((object)((Component)camera).transform.position/*cast due to .constrained prefix*/).ToString(); obj2[8] = " gameCamera="; obj2[9] = ((object)((Component)controller).transform.position/*cast due to .constrained prefix*/).ToString(); Plugin.Log(string.Concat(obj2)); if ((int)(PlacementStatus)BuildStatus.GetValue(player) == 0) { break; } } Check((int)(PlacementStatus)BuildStatus.GetValue(player) == 0, "build: the native floor ghost has a valid real placement ray"); string woodName = Items.Prefab("Wood").m_shared.m_name; int cost = floor.m_resources.First((Requirement r) => r.m_resItem.m_itemData.m_shared.m_name == woodName).m_amount; for (int scenario = 0; scenario < 3; scenario++) { if (scenario == 2) { ((Humanoid)player).UnequipItem(hammer, false); hammer = AddLabItem("Hammer"); ((Humanoid)player).EquipItem(hammer, false); yield return (object)new WaitForSecondsRealtime(0.3f); player.SetSelectedPiece(floor); Check(NetworkBuild.Tool(player) == hammer && Plugin.Prefab(hammer) == "Hammer", "build: the third real placement uses the ordinary vanilla hammer"); } inv.RemoveItem(woodName, 9999, -1, true); if (scenario == 1) { AddLabItem("Wood"); } AimValidBuild(player, floor, origin); Check((int)(PlacementStatus)BuildStatus.GetValue(player) == 0, "build: delayed supply scenario has a valid unused native placement " + scenario); int before2 = PieceCount(floor, origin); int stored = Items.Count(Read().Bags, woodName); int pocket = inv.CountItems(woodName, -1, true); float durability = hammer.m_durability; float stamina = player.GetStamina(); Check(player.HaveRequirements(floor, (RequirementMode)0), "build: network plus pocket satisfies floor requirements " + scenario); string[] obj3 = new string[18] { "LAB build before press ", scenario.ToString(), ": status=", BuildStatus.GetValue(player)?.ToString(), " selected=", null, null, null, null, null, null, null, null, null, null, null, null, null }; Piece selectedPiece = player.GetSelectedPiece(); obj3[5] = ((selectedPiece != null) ? ((Object)selectedPiece).name : null); obj3[6] = " stamina="; obj3[7] = player.GetStamina().ToString(); obj3[8] = " tool="; obj3[9] = Plugin.Prefab(NetworkBuild.Tool(player)); obj3[10] = " ghost="; obj3[11] = (BuildGhost.GetValue(player) != null).ToString(); obj3[12] = " pieceMenu="; obj3[13] = Hud.IsPieceSelectionVisible().ToString(); obj3[14] = " pocket="; obj3[15] = pocket.ToString(); obj3[16] = " cost="; obj3[17] = cost.ToString(); Plugin.Log(string.Concat(obj3)); HoldBuildReply = true; BufferedReply = null; BuildLast.SetValue(player, -9999f); BuildPress.SetValue(player, Time.time); NativePlacement.Invoke(player, new object[2] { true, 0.02f }); Plugin.Log("LAB build after press " + scenario + ": status=" + BuildStatus.GetValue(player)?.ToString() + " busy=" + Client.Busy + " receipt=" + (BufferedReply != null) + " floors=" + PieceCount(floor, origin) + " before=" + before2 + " stamina=" + player.GetStamina()); Check(Client.Busy && BufferedReply != null && PieceCount(floor, origin) == before2, "build: delayed supply does not place an unpaid floor " + scenario); yield return (object)new WaitForSecondsRealtime(0.25f); Check(Client.Busy && PieceCount(floor, origin) == before2, "build: waiting across frames keeps placement pending " + scenario); Action bufferedReply2 = BufferedReply; HoldBuildReply = false; BufferedReply = null; bufferedReply2(); bufferedReply2(); NativePlacement.Invoke(player, new object[2] { true, 0.02f }); string[] obj4 = new string[14] { "LAB build resumed ", scenario.ToString(), ": status=", BuildStatus.GetValue(player)?.ToString(), " busy=", Client.Busy.ToString(), " floors=", PieceCount(floor, origin).ToString(), " before=", before2.ToString(), " wood=", inv.CountItems(woodName, -1, true).ToString(), " selected=", null }; Piece selectedPiece2 = player.GetSelectedPiece(); obj4[13] = ((selectedPiece2 != null) ? ((Object)selectedPiece2).name : null); Plugin.Log(string.Concat(obj4)); Check(PieceCount(floor, origin) == before2 + 1 && !Client.Busy, "build: exactly one real floor placed after duplicate delivery " + scenario); Check(Items.Count(Read().Bags, woodName) == stored - (cost - pocket) && inv.CountItems(woodName, -1, true) == 0, "build: only the shortfall is pulled, full cost consumed once " + scenario); Check(hammer.m_durability < durability && player.GetStamina() < stamina, "build: the native hammer durability and stamina costs apply " + scenario); foreach (ZDO item3 in from z in Network.Nearby(origin, 20f) where z.GetPrefab() == StringExtensionMethods.GetStableHashCode(((Object)((Component)floor).gameObject).name) select z) { item3.Set("runic.lab.fixture", true); } yield return (object)new WaitForSecondsRealtime(0.5f); } yield return (object)new WaitForEndOfFrame(); ScreenCapture.CaptureScreenshot(Path.Combine(output, "network-building-game.png")); while (inv.NrOfItems() < inv.GetWidth() * inv.GetHeight()) { ItemData val6 = Items.Prefab("Stone").Clone(); val6.m_dropPrefab = ObjectDB.instance.GetItemPrefab("Stone"); val6.m_stack = 50; val6.m_customData["runic.lab.filler"] = inv.NrOfItems().ToString(); Items.Add(inv, val6); } val5 = AimValidBuild(player, floor, origin); Check((int)(PlacementStatus)BuildStatus.GetValue(player) == 0, "build: full inventory test uses a valid native placement"); int num3 = PieceCount(floor, origin); int num4 = Items.Count(Read().Bags, woodName); BuildLast.SetValue(player, -9999f); BuildPress.SetValue(player, Time.time); NativePlacement.Invoke(player, new object[2] { true, 0.02f }); NativePlacement.Invoke(player, new object[2] { true, 0.02f }); Check(!Client.Busy && PieceCount(floor, origin) == num3 && Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { Client.Inbox }, woodName) == cost && Items.Count(Read().Bags, woodName) == num4 - cost, "build: a full inventory prevents placement and preserves all supplied wood in delivery"); ItemData[] array5 = (from val15 in inv.GetAllItems() where val15.m_customData.ContainsKey("runic.lab.filler") select val15).ToArray(); foreach (ItemData val7 in array5) { inv.RemoveItem(val7); } Client.Collect(); Check(Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { Client.Inbox }, woodName) == 0 && inv.CountItems(woodName, -1, true) == cost, "build: freeing slots collects the preserved materials exactly once"); inv.RemoveItem(woodName, 9999, -1, true); AimBuild(player, floor, val5); int oldCount = PieceCount(floor, origin); HoldBuildReply = true; BufferedReply = null; BuildLast.SetValue(player, -9999f); BuildPress.SetValue(player, Time.time); NativePlacement.Invoke(player, new object[2] { true, 0.02f }); Check(BufferedReply != null, "build: cancellation test has a genuinely pending routed reply"); ((Humanoid)player).UnequipItem(hammer, false); HoldBuildReply = false; Action bufferedReply3 = BufferedReply; BufferedReply = null; bufferedReply3(); yield return null; Check(PieceCount(floor, origin) == oldCount && inv.CountItems(woodName, -1, true) == cost, "build: changing tool cancels placement and keeps delivered materials"); ((Component)camera).transform.position = cameraPosition; ((Component)camera).transform.rotation = cameraRotation; ((Behaviour)controller).enabled = cameraEnabled; Vector3 point2 = LabLandPoint(corePos, 500f); yield return LabTeleport(point2, rotation); Check(Vector3.Distance(((Component)player).transform.position, corePos) > 450f, "remote: actual player is over 450m from the core"); ZDOID[] farStale = (from z in Network.Nearby(((Component)player).transform.position, 60f) where z.GetBool("runic.lab.fixture", false) select z.m_uid).ToArray(); ZDOID[] array6 = farStale; foreach (ZDOID val8 in array6) { ZDO zDO = ZDOMan.instance.GetZDO(val8); zDO.SetOwner(ZDOMan.GetSessionID()); ZDOMan.instance.DestroyZDO(zDO); } float farCleanup = Time.unscaledTime + 5f; yield return (object)new WaitUntil((Func<bool>)(() => farStale.All((ZDOID val15) => !Object.op_Implicit((Object)(object)ZNetScene.instance.FindInstance(val15))) || Time.unscaledTime > farCleanup)); Check(farStale.All((ZDOID val15) => !Object.op_Implicit((Object)(object)ZNetScene.instance.FindInstance(val15))), "remote: persisted far lab fixtures are removed before checking station requirements"); float unloadDeadline = Time.unscaledTime + 15f; yield return (object)new WaitUntil((Func<bool>)(() => !Object.op_Implicit((Object)(object)ZNetScene.instance.FindInstance(coreId)) || Time.unscaledTime > unloadDeadline)); Check(!Object.op_Implicit((Object)(object)ZNetScene.instance.FindInstance(coreId)), "remote: core GameObject really unloaded before accessing storage"); RemoteControl.Use(remote); Check(StorageUI.Instance.Visible && StorageUI.Instance.RemoteAccess && StorageUI.Instance.Stored.NrOfItems() > 0, "remote: bound item opens unloaded storage from 500m away"); int num5 = Items.Count(Network.Read("lab-core", corePos, player.GetPlayerID()).Bags, woodName); ItemData item = inv.GetAllItems().First((ItemData item3) => Plugin.Prefab(item3) == "Wood"); Client.Instance.Deposit(item, 1); Check(!Client.Busy && Items.Count(Network.Read("lab-core", corePos, player.GetPlayerID()).Bags, woodName) == num5 + 1, "remote: actual routed deposit changes unloaded storage exactly once"); ItemData item2 = StorageUI.Instance.Stored.GetAllItems().First((ItemData item3) => Plugin.Prefab(item3) == "Wood"); int num6 = inv.CountItems(woodName, -1, true); Client.Instance.BeginRequest("withdraw", Items.Key(item2)); Check(!Client.Busy && inv.CountItems(woodName, -1, true) == num6 + 1 && Items.Count(Network.Read("lab-core", corePos, player.GetPlayerID()).Bags, woodName) == num5, "remote: actual routed withdrawal delivers one from unloaded storage"); AccessTools.Method(typeof(StorageUI), "Tab", (Type[])null, (Type[])null).Invoke(StorageUI.Instance, new object[1] { "storage" }); yield return (object)new WaitForSecondsRealtime(0.3f); Check(InventoryGui.instance.m_playerGrid.GetInventory() == inv, "remote: native inventory grid shows the actual carried items"); yield return (object)new WaitForEndOfFrame(); ScreenCapture.CaptureScreenshot(Path.Combine(output, "remote-storage-game.png")); StorageUI.Instance.Close(); ((Humanoid)player).EquipItem(hammer, false); yield return null; Client.Instance.Poll(); Check(!player.HaveRequirements(floor, (RequirementMode)0), "build: remote materials do not bypass the nearby workbench requirement"); ((Humanoid)player).UnequipItem(hammer, false); Vector3 remoteOrigin = ((Component)player).transform.position; Vector3 val9 = remoteOrigin + Vector3.forward * 6f; val9.y = ZoneSystem.instance.GetGroundHeight(val9); GameObject obj5 = Object.Instantiate<GameObject>(ZNetScene.instance.GetPrefab("piece_workbench"), val9, Quaternion.identity); obj5.GetComponent<ZNetView>().GetZDO().Set("runic.lab.fixture", true); obj5.GetComponent<WearNTear>().m_noSupportWear = true; obj5.GetComponent<WearNTear>().m_noRoofWear = true; inv.RemoveItem(woodName, 9999, -1, true); bool equipped = ((Humanoid)player).EquipItem(hammer, false); yield return (object)new WaitForSecondsRealtime(1f); Client.Instance.Poll(); Check(equipped && NetworkBuild.Tool(player) == hammer, "build: remote fixture equips its hammer before selecting a floor; owned=" + inv.ContainsItem(hammer) + " durability=" + hammer.m_durability + " equipped=" + hammer.m_equipped + " swimming=" + ((Character)player).IsSwimming()); player.SetSelectedPiece(floor); Vector3 farCameraPosition = ((Component)camera).transform.position; Quaternion farCameraRotation = ((Component)camera).transform.rotation; ((Behaviour)controller).enabled = false; array4 = candidates; foreach (Vector3 val10 in array4) { val5 = remoteOrigin + val10; val5.y = ZoneSystem.instance.GetGroundHeight(val5); AimBuild(player, floor, val5); if ((int)(PlacementStatus)BuildStatus.GetValue(player) == 0) { break; } } Check((int)(PlacementStatus)BuildStatus.GetValue(player) == 0 && player.HaveRequirements(floor, (RequirementMode)0), "build: a workbench at 500m allows normal placement using remote resources"); int num7 = PieceCount(floor, remoteOrigin); int num8 = Items.Count(Network.Read("lab-core", corePos, player.GetPlayerID()).Bags, woodName); BuildLast.SetValue(player, -9999f); BuildPress.SetValue(player, Time.time); NativePlacement.Invoke(player, new object[2] { true, 0.02f }); NativePlacement.Invoke(player, new object[2] { true, 0.02f }); Check(PieceCount(floor, remoteOrigin) == num7 + 1 && inv.CountItems(woodName, -1, true) == 0 && Items.Count(Network.Read("lab-core", corePos, player.GetPlayerID()).Bags, woodName) == num8 - cost && !Object.op_Implicit((Object)(object)ZNetScene.instance.FindInstance(coreId)), "build: one real floor at 500m consumes only unloaded storage materials once"); foreach (ZDO item4 in from z in Network.Nearby(remoteOrigin, 20f) where z.GetPrefab() == StringExtensionMethods.GetStableHashCode(((Object)((Component)floor).gameObject).name) select z) { item4.Set("runic.lab.fixture", true); } yield return (object)new WaitForSecondsRealtime(0.35f); yield return (object)new WaitForEndOfFrame(); ScreenCapture.CaptureScreenshot(Path.Combine(output, "remote-building-game.png")); ((Component)camera).transform.position = farCameraPosition; ((Component)camera).transform.rotation = farCameraRotation; ((Behaviour)controller).enabled = cameraEnabled; ((Humanoid)player).UnequipItem(hammer, false); Plugin.RemoteRange.Value = 25f; Check(!RemoteControl.TryTarget(remote, out id, out pos, out range, quiet: true), "remote: 25m base at tier 3 gives 200m and rejects the distant client"); Request request = new Request { PlayerId = player.GetPlayerID(), World = ZNet.instance.GetWorld().m_uid, CoreId = "lab-core", CorePosition = corePos, Operation = "snapshot", Remote = true, Range = 1000000f }; Check(!Server.Execute(request, ((Component)player).transform.position).Success, "remote: server rejects a client attempting to exceed configured range"); Plugin.RemoteRange.Value = 0f; request.Range = float.NaN; Check(!Server.Execute(request, ((Component)player).transform.position).Success, "remote: NaN cannot bypass server distance checks"); yield return LabTeleport(returnOrigin, rotation); ZDO coreZdo = ZDOMan.instance.GetZDO(coreId); ZDO unitZdo = ZDOMan.instance.GetZDO(unitId); GameObject val11 = ZNetScene.instance.FindInstance(coreId); if (!Object.op_Implicit((Object)(object)val11)) { val11 = (GameObject)AccessTools.Method(typeof(ZNetScene), "CreateObject", (Type[])null, (Type[])null).Invoke(ZNetScene.instance, new object[1] { coreZdo }); } GameObject val12 = ZNetScene.instance.FindInstance(unitId); if (!Object.op_Implicit((Object)(object)val12)) { val12 = (GameObject)AccessTools.Method(typeof(ZNetScene), "CreateObject", (Type[])null, (Type[])null).Invoke(ZNetScene.instance, new object[1] { unitZdo }); } core = val11.GetComponent<Node>(); unit = val12.GetComponent<Node>(); Plugin.RemoteRange.Value = 100f; for (int scenario = 4; scenario <= 7; scenario++) { if (scenario == 4) { Network network2 = Read(); RemoteControl.UpgradeIngredient[] array2 = RemoteControl.UpgradeCosts[scenario]; for (int j = 0; j < array2.Length; j++) { RemoteControl.UpgradeIngredient upgradeIngredient2 = array2[j]; ItemData val13 = upgradeIngredient2.Item.Clone(); val13.m_dropPrefab = ObjectDB.instance.GetItemPrefab(upgradeIngredient2.Prefab); val13.m_stack = upgradeIngredient2.Amount; Items.Add(network2.Bags.First(), val13); } network2.Commit(); RemoteControl.Use(remote); AccessTools.Field(typeof(StorageUI), "help").SetValue(StorageUI.Instance, true); AccessTools.Method(typeof(StorageUI), "RefreshDetail", (Type[])null, (Type[])null).Invoke(StorageUI.Instance, null); yield return (object)new WaitForSecondsRealtime(0.3f); yield return (object)new WaitForEndOfFrame(); ScreenCapture.CaptureScreenshot(Path.Combine(output, "remote-upgrade-game.png")); Button val14 = StorageUI.Instance.Panel.GetComponentsInChildren<Button>().First(delegate(Button b) { Text componentInChildren = ((Component)b).GetComponentInChildren<Text>(); return ((componentInChildren != null) ? componentInChildren.text : null) == "Улучшить пульт"; }); Check(((Selectable)val14).interactable, "remote: upgrade button is enabled when all biome ingredients are stored in the network"); ((UnityEvent)val14.onClick).Invoke(); Check(RemoteControl.UpgradeCosts[scenario].All((RemoteControl.UpgradeIngredient upgradeIngredient4) => Items.Count(Read().Bags, upgradeIngredient4.Item.m_shared.m_name) == 0), "remote: upgrade button consumes the complete recipe directly from storage"); AccessTools.Field(typeof(StorageUI), "help").SetValue(StorageUI.Instance, false); StorageUI.Instance.Close(); } else { RemoteControl.UpgradeIngredient[] array2 = RemoteControl.UpgradeCosts[scenario]; for (int j = 0; j < array2.Length; j++) { RemoteControl.UpgradeIngredient upgradeIngredient3 = array2[j]; AddLabItem(upgradeIngredient3.Prefab, upgradeIngredient3.Amount); } RemoteControl.Upgrade(remote); } Check(!Client.Busy && RemoteControl.Tier(remote) == scenario && RemoteControl.ServerTier(coreZdo, player.GetPlayerID()) == scenario, "remote: biome-resource progression reaches tier " + scenario); } Check(RemoteControl.Limit(7) == 1000000f, "remote: the final biome recipe unlocks world-wide access"); Network.WriteData(coreZdo, coreData); Network.WriteData(unitZdo, unitData); Network.Sync(coreZdo); Network.Sync(unitZdo); Plugin.RemoteRange.Value = originalLimit; inv.RemoveAll(); foreach (ItemData allItem in Items.Load(originalInventory, inv.GetWidth(), inv.GetHeight()).GetAllItems()) { inv.GetAllItems().Add(allItem); } Client.Persist(); Check(!Client.Busy && !HoldBuildReply, "build/remote: lab requests settled before existing UI regressions"); } private ItemData CapacityItem(string prefab, int count, string tag = null) { ItemData val = Items.Prefab(prefab).Clone(); val.m_dropPrefab = ObjectDB.instance.GetItemPrefab(prefab); val.m_stack = count; if (tag != null) { val.m_customData["capacity.fixture"] = tag; } return val; } private void FillPocket(int slots) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory(); inventory.RemoveAll(); for (int i = 0; i < slots; i++) { ItemData val = CapacityItem("Stone", 50, "pocket-" + i); val.m_gridPos = new Vector2i(i % inventory.GetWidth(), i / inventory.GetWidth()); inventory.GetAllItems().Add(val); } AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null).Invoke(inventory, new object[2] { false, false }); } private IEnumerator CapacityStock(int wood = 125, bool full = false, int partialWood = 0, int freeSlots = 0) { ZDO zDO = core.View.GetZDO(); ZDO zDO2 = unit.View.GetZDO(); zDO2.Set("magicstorage_unit_tier", 1); Inventory val = Items.Bag(4, 4); Inventory val2 = Items.Bag(9, 3); if (full) { int num = 0; Inventory[] array = (Inventory[])(object)new Inventory[2] { val, val2 }; foreach (Inventory val3 in array) { for (int j = 0; j < val3.GetWidth() * val3.GetHeight(); j++) { ItemData val4 = CapacityItem("Stone", 50, "stock-" + num++); val4.m_gridPos = new Vector2i(j % val3.GetWidth(), j / val3.GetWidth()); val3.GetAllItems().Add(val4); } } if (partialWood > 0) { val2.GetAllItems().RemoveAt(val2.NrOfItems() - 1); ItemData val5 = CapacityItem("Wood", partialWood); val5.m_gridPos = new Vector2i(8, 2); val2.GetAllItems().Add(val5); } for (int k = 0; k < freeSlots; k++) { val2.GetAllItems().RemoveAt(val2.NrOfItems() - 1); } } else { Items.Add(val2, CapacityItem("Wood", wood)); } Network.WriteData(zDO, Items.Save(val)); Network.WriteData(zDO2, Items.Save(val2)); Network.Sync(zDO); Network.Sync(zDO2); StorageUI instance = StorageUI.Instance; instance.Open(core); AccessTools.Method(typeof(StorageUI), "Tab", (Type[])null, (Type[])null).Invoke(instance, new object[1] { "storage" }); Client.Instance.Poll(); yield return (object)new WaitForSecondsRealtime(0.2f); yield return TransferSettled(); } private Button CapacityWithdraw(string prefab, string label) { StorageUI instance = StorageUI.Instance; ItemData item = instance.Stored.GetAllItems().First((ItemData value) => Plugin.Prefab(value) == prefab); AccessTools.Field(typeof(StorageUI), "selected").SetValue(instance, Items.Key(item)); AccessTools.Method(typeof(StorageUI), "Refresh", (Type[])null, (Type[])null).Invoke(instance, null); return instance.Panel.GetComponentsInChildren<Button>().First((Button button) => ((Object)button).name == "Runic button " + label && ((Component)button).gameObject.activeInHierarchy); } private string DeliveryLabel() { return ((Component)StorageUI.Instance.Panel.GetComponentsInChildren<Button>().First((Button button) => ((Object)button).name == "Runic button Получить (0)")).GetComponentInChildren<Text>().text; } private IEnumerator CapacityChecks() { Player player = Player.m_localPlayer; Inventory inv = ((Humanoid)player).GetInventory(); StorageUI ui = StorageUI.Instance; Client client = Client.Instance; int slots = inv.GetWidth() * inv.GetHeight(); string woodName = Items.Prefab("Wood").m_shared.m_name; Check(!InventoryJournal.InlineWrites, "capacity: production background writer is enabled"); ((Humanoid)player).UnequipAllItems(); player.m_customData["runic.inbox"] = Items.Save(Items.Bag()); yield return CapacityStock(); FillPocket(slots); yield return null; string sequence = player.m_customData["runic.sequence"]; int writes = InventoryJournal.Writes; int stock = Items.Count(Read().Bags, woodName); ((UnityEvent)CapacityWithdraw("Wood", "Забрать всё").onClick).Invoke(); yield return TransferSettled(); File.WriteAllLines(Path.Combine(output, "capacity-first-observation.txt"), new string[8] { "stock before=" + stock, "stock after=" + Items.Count(Read().Bags, woodName), "inventory stacks=" + inv.NrOfItems() + " / " + slots, "inbox wood=" + Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { Client.Inbox }, woodName), "inbox stacks=" + Client.Inbox.NrOfItems(), "sequence before=" + sequence, "sequence after=" + player.m_customData["runic.sequence"], "journal writes=" + (InventoryJournal.Writes - writes) }); Check(Items.Count(Read().Bags, woodName) == stock && inv.NrOfItems() == slots && Client.Inbox.NrOfItems() == 0 && player.m_customData["runic.sequence"] == sequence && InventoryJournal.Writes == writes, "capacity: withdraw-all from a completely full inventory leaves every item in storage without RPC, journal or hidden delivery"); FillPocket(slots - 1); Items.Add(inv, CapacityItem("Wood", 47)); yield return null; ((UnityEvent)CapacityWithdraw("Wood", "Забрать всё").onClick).Invoke(); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == 50 && Items.Count(Read().Bags, woodName) == stock - 3 && Client.Inbox.NrOfItems() == 0 && inv.NrOfItems() == slots, "capacity: withdraw-all fills only the three free units of an existing stack"); FillPocket(slots - 1); yield return null; stock = Items.Count(Read().Bags, woodName); ((UnityEvent)CapacityWithdraw("Wood", "Забрать всё").onClick).Invoke(); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == 50 && Items.Count(Read().Bags, woodName) == stock - 50 && Client.Inbox.NrOfItems() == 0 && inv.NrOfItems() == slots, "capacity: one free slot permits only one native stack and leaves the rest in storage"); sequence = player.m_customData["runic.sequence"]; stock = Items.Count(Read().Bags, woodName); NativeInventory.PickStorage(ui.Stored.GetAllItems().First((ItemData item) => Plugin.Prefab(item) == "Wood"), (Modifier)2); yield return TransferSettled(); Check(Items.Count(Read().Bags, woodName) == stock && player.m_customData["runic.sequence"] == sequence && Client.Inbox.NrOfItems() == 0, "capacity: native Ctrl withdrawal also respects a full inventory"); FillPocket(slots - 1); Items.Add(inv, CapacityItem("Wood", 47)); yield return null; ItemData wood = ui.Stored.GetAllItems().First((ItemData item) => Plugin.Prefab(item) == "Wood"); NativeInventory.PickStorage(wood, (Modifier)1); InventoryGui.instance.m_splitDialog.SliderValue = 10f; AccessTools.Method(typeof(InventoryGui), "OnSplitOk", (Type[])null, (Type[])null).Invoke(InventoryGui.instance, null); ItemData carried = inv.GetAllItems().First((ItemData item) => Plugin.Prefab(item) == "Wood"); stock = Items.Count(Read().Bags, woodName); InventoryGui.instance.m_playerGrid.m_onSelected(InventoryGui.instance.m_playerGrid, carried, carried.m_gridPos, (Modifier)0); yield return TransferSettled(); Check(carried.m_stack == 50 && Items.Count(Read().Bags, woodName) == stock - 3 && NativeInventory.DragAmount == 7 && Client.Inbox.NrOfItems() == 0, "capacity: native split into a nearly full slot transfers only three and keeps seven on the preview cursor"); NativeInventory.ClearCursor(); FillPocket(slots - 1); ItemData variant = CapacityItem("Wood", 1, "different-identity"); Items.Add(inv, variant); yield return null; sequence = player.m_customData["runic.sequence"]; ((UnityEvent)CapacityWithdraw("Wood", "Забрать 1").onClick).Invoke(); yield return TransferSettled(); Check(variant.m_stack == 1 && player.m_customData["runic.sequence"] == sequence && Client.Inbox.NrOfItems() == 0, "capacity: partial stacks with different custom data do not count as free space"); FillPocket(slots - 1); Items.Add(inv, CapacityItem("Wood", 40)); Inventory inv2 = Items.Bag(); Items.Add(inv2, CapacityItem("Wood", 20)); player.m_customData["runic.inbox"] = Items.Save(inv2); Client.Persist(); ui.RefreshDelivery(); yield return null; stock = Items.Count(Read().Bags, woodName); sequence = player.m_customData["runic.sequence"]; ((UnityEvent)CapacityWithdraw("Wood", "Забрать всё").onClick).Invoke(); yield return TransferSettled(); Check(Items.Count(Read().Bags, woodName) == stock && player.m_customData["runic.sequence"] == sequence && Client.Inbox.NrOfItems() == 1, "capacity: existing deliveries reserve available space before any new withdrawal"); Client.Collect(); Check(inv.CountItems(woodName, -1, true) == 50 && Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { Client.Inbox }, woodName) == 10 && DeliveryLabel().Contains("(1)"), "capacity: collecting an older delivery fills only available stack space and visibly retains the remainder"); inv.RemoveItem(inv.GetAllItems().First((ItemData item) => item.m_customData.ContainsKey("capacity.fixture"))); Client.Collect(); Check(inv.CountItems(woodName, -1, true) == 60 && Client.Inbox.NrOfItems() == 0 && DeliveryLabel().Contains("(0)") && Items.Count(Read().Bags, woodName) == stock, "capacity: freeing a slot collects the remainder exactly once and updates its counter without a stock snapshot"); FillPocket(slots - 1); yield return CapacityStock(); stock = Items.Count(Read().Bags, woodName); HoldJournal = true; ((UnityEvent)CapacityWithdraw("Wood", "Забрать стак").onClick).Invoke(); Items.Add(inv, CapacityItem("Stone", 50, "late-pickup")); HoldJournal = false; yield return TransferSettled(); Check(Items.Count(Read().Bags, woodName) == stock - 50 && inv.CountItems(woodName, -1, true) == 0 && Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { Client.Inbox }, woodName) == 50 && DeliveryLabel().Contains("(1)"), "capacity: a pickup racing an already prepared withdrawal preserves its fifty items in a visible safety delivery"); PlayerProfile playerProfile = Game.instance.GetPlayerProfile(); playerProfile.SavePlayerData(player); LoadLabProfile(playerProfile, player); ui.RefreshDelivery(); Check(Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { Client.Inbox }, woodName) == 50 && DeliveryLabel().Contains("(1)"), "capacity: a native character reload preserves the safety delivery and its visible counter"); inv.RemoveItem(inv.GetAllItems().First()); Client.Collect(); Client.Collect(); Check(inv.CountItems(woodName, -1, true) == 50 && Client.Inbox.NrOfItems() == 0 && DeliveryLabel().Contains("(0)"), "capacity: after freeing a slot a raced delivery is recovered once without duplication"); inv.RemoveAll(); yield return CapacityStock(125, full: true); wood = AddLabItem("Wood", 12); sequence = player.m_customData["runic.sequence"]; writes = InventoryJournal.Writes; string token = ui.Stock.Token; ui.Stock.Token = null; client.Deposit(wood, 12); Check(!Client.Busy && inv.ContainsItem(wood) && wood.m_stack == 12 && player.m_customData["runic.sequence"] == sequence && InventoryJournal.Writes == writes, "capacity: before the first storage view arrives a direct deposit keeps its stack without escrow or I/O"); ui.Stock.Token = token; client.Deposit(wood, 12); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == 12 && Read().Used == Read().Capacity && Client.Inbox.NrOfItems() == 0 && player.m_customData["runic.sequence"] == sequence && InventoryJournal.Writes == writes, "capacity: a deposit into full storage leaves the original stack in the pocket without escrow or a hidden refund"); yield return CapacityStock(125, full: true, 47); wood = inv.GetAllItems().First((ItemData item) => Plugin.Prefab(item) == "Wood"); client.Deposit(wood, 12); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == 9 && Items.Count(Read().Bags, woodName) == 50 && Read().Used == 43 && Client.Inbox.NrOfItems() == 0, "capacity: full storage accepts exactly three units into its partial stack and leaves nine in the original pocket stack"); sequence = player.m_customData["runic.sequence"]; client.QueueDeposit(wood); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == 9 && player.m_customData["runic.sequence"] == sequence && Client.Inbox.NrOfItems() == 0, "capacity: queued Ctrl deposit revalidates a storage stack that has become full"); inv.RemoveAll(); yield return CapacityStock(125, full: true, 0, 1); ItemData first = CapacityItem("Wood", 30); first.m_gridPos = Vector2i.zero; ItemData second = CapacityItem("Wood", 20); second.m_gridPos = new Vector2i(1, 0); ItemData coal = CapacityItem("Coal", 20); coal.m_gridPos = new Vector2i(2, 0); inv.GetAllItems().AddRange((IEnumerable<ItemData>)(object)new ItemData[3] { first, second, coal }); AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null).Invoke(inv, new object[2] { false, false }); yield return null; HoldJournal = true; InventoryGrid playerGrid = InventoryGui.instance.m_playerGrid; ItemData[] array = (ItemData[])(object)new ItemData[3] { first, second, coal }; foreach (ItemData val in array) { playerGrid.m_onSelected(playerGrid, val, val.m_gridPos, (Modifier)2); } float deadline = Time.unscaledTime + 3f; yield return (object)new WaitUntil((Func<bool>)(() => client.CheckpointBusy || Time.unscaledTime > deadline)); Check(client.CheckpointBusy && !inv.ContainsItem(first) && !inv.ContainsItem(second) && inv.ContainsItem(coal) && coal.m_stack == 20, "capacity: the queued batch escrows only the two wood stacks that fit; the third resource stays visible in the pocket during disk wait"); HoldJournal = false; yield return TransferSettled(); Check(Items.Count(Read().Bags, woodName) == 50 && Read().Used == 43 && inv.ContainsItem(coal) && Client.Inbox.NrOfItems() == 0, "capacity: two matching carried stacks share one storage slot without stealing the third item's capacity"); inv.RemoveAll(); yield return CapacityStock(125, full: true, 0, 1); first = AddLabItem("Wood", 50); coal = AddLabItem("Coal", 20); HoldBuildReply = true; BufferedReply = null; client.Deposit(first, 50); deadline = Time.unscaledTime + 3f; yield return (object)new WaitUntil((Func<bool>)(() => BufferedReply != null || Time.unscaledTime > deadline)); Check(BufferedReply != null, "capacity: pending deposit has a real committed receipt before queuing more items"); client.QueueDeposit(coal); Action bufferedReply = BufferedReply; BufferedReply = null; HoldBuildReply = false; bufferedReply(); bufferedReply(); yield return TransferSettled(); Check(inv.ContainsItem(coal) && coal.m_stack == 20 && Read().Used == 43 && Items.Count(Read().Bags, woodName) == 50 && Client.Inbox.NrOfItems() == 0, "capacity: queued clicks after a delayed receipt respect the newly full storage and duplicate receipt is harmless"); inv.RemoveAll(); yield return CapacityStock(125, full: true, 47); Inventory ub = Items.Load(Network.ReadData(unit.View.GetZDO()), 9, 3); ItemData changedWood = ub.GetAllItems().First((ItemData item) => Plugin.Prefab(item) == "Wood"); changedWood.m_stack = 50; Network.WriteData(unit.View.GetZDO(), Items.Save(ub)); Network.Sync(unit.View.GetZDO()); yield return (object)new WaitForSecondsRealtime(0.2f); wood = AddLabItem("Wood", 5); sequence = player.m_customData["runic.sequence"]; client.Deposit(wood, 5); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == 5 && player.m_customData["runic.sequence"] == sequence, "capacity: an external storage delta removes obsolete partial-stack capacity before the next transfer"); ub.GetAllItems().Remove(changedWood); Network.WriteData(unit.View.GetZDO(), Items.Save(ub)); Network.Sync(unit.View.GetZDO()); yield return (object)new WaitForSecondsRealtime(0.2f); client.Deposit(wood, 5); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == 0 && Items.Count(Read().Bags, woodName) == 5 && Read().Used == 43 && Client.Inbox.NrOfItems() == 0, "capacity: an externally freed slot becomes available through the incremental delta"); ub = Items.Load(Network.ReadData(unit.View.GetZDO()), 9, 3); changedWood = ub.GetAllItems().First((ItemData item) => Plugin.Prefab(item) == "Wood"); ItemData val2 = ub.GetAllItems().First((ItemData item) => Plugin.Prefab(item) == "Stone"); ItemData val3 = changedWood.Clone(); val3.m_stack = 3; val3.m_gridPos = val2.m_gridPos; changedWood.m_stack = 2; ub.GetAllItems().Remove(val2); ub.GetAllItems().Add(val3); Network.WriteData(unit.View.GetZDO(), Items.Save(ub)); Network.Sync(unit.View.GetZDO()); yield return (object)new WaitForSecondsRealtime(0.2f); AddLabItem("Wood", 80); client.DepositAll(matching: false); yield return TransferSettled(); Check(inv.CountItems(woodName, -1, true) == 0 && Items.Count(Read().Bags, woodName) == 85 && Read().Used == 43 && Client.Inbox.NrOfItems() == 0, "capacity: unchanged aggregate count still refreshes capacity when one stack becomes two partial stacks"); yield return (object)new WaitForEndOfFrame(); ScreenCapture.CaptureScreenshot(Path.Combine(output, "capacity-game.png")); yield return (object)new WaitForSecondsRealtime(0.3f); inv.RemoveAll(); player.m_customData["runic.inbox"] = Items.Save(Items.Bag()); ui.Close(); } private IEnumerator ControlsChecks() { Player player = Player.m_localPlayer; Inventory inv = ((Humanoid)player).GetInventory(); StorageUI ui = StorageUI.Instance; ZDO cz = core.View.GetZDO(); ZDO uz = unit.View.GetZDO(); ZDOID coreId = cz.m_uid; ZDOID unitId = uz.m_uid; Vector3 position = ((Component)player).transform.position; Quaternion rotation = ((Component)player).transform.rotation; string pocket = Items.Save(inv); string coreData = Network.ReadData(cz); string unitData = Network.ReadData(uz); int originalTier = uz.GetInt("magicstorage_unit_tier", 0); int grant = RemoteControl.ServerTier(cz, player.GetPlayerID()); ui.Close(); ((Humanoid)player).UnequipAllItems(); inv.RemoveAll(); cz.Set(RemoteControl.Grant(player.GetPlayerID()), 0); uz.Set("magicstorage_unit_tier", 0); ItemData wood = Items.Prefab("Wood"); Inventory stock = Items.Bag(6, 3); ItemData val = wood.Clone(); val.m_dropPrefab = ObjectDB.instance.GetItemPrefab("Wood"); val.m_stack = 30; Items.Add(stock, val); Network.WriteData(uz, Items.Save(stock)); Network.WriteData(cz, Items.Save(Items.Bag(4, 4))); Network.Sync(uz); Network.Sync(cz); yield return null; string hoverText = unit.Container.GetHoverText(); Check(hoverText.Contains(Plugin.Name(Items.Prefab("Bronze")) + " ×3") && hoverText.Contains(Plugin.Name(Items.Prefab("FineWood")) + " ×10") && hoverText.Contains("27 ячеек"), "controls: the block hover shows its exact next-level price and capacity"); yield return CaptureUnitHover(player, hoverText); unit.Container.Interact((Humanoid)(object)player, false, true); Check(!ui.Visible && !Client.Busy && uz.GetInt("magicstorage_unit_tier", 0) == 0, "controls: native alternate E with missing resources keeps the window closed and the block unchanged"); unit.Container.Interact((Humanoid)(object)player, false, false); Check(ui.Visible, "controls: ordinary E still opens storage"); ui.Close(); yield return (object)new WaitForSecondsRealtime(0.2f); AddLabItem("Bronze", 3); AddLabItem("FineWood", 5); ItemData fine = Items.Prefab("FineWood").Clone(); fine.m_dropPrefab = ObjectDB.instance.GetItemPrefab("FineWood"); fine.m_stack = 5; Items.Add(stock, fine); Network.WriteData(uz, Items.Save(stock)); Network.Sync(uz); unit.Container.Interact((Humanoid)(object)player, false, true); yield return (object)new WaitForSecondsRealtime(0.2f); Check(!ui.Visible && !Client.Busy && uz.GetInt("magicstorage_unit_tier", 0) == 1 && Read().Capacity == 43, "controls: alternate E upgrades the actual block without opening the window"); Check(Items.Count(Read().Bags.Concat((IEnumerable<Inventory>)(object)new Inventory[1] { inv }), Items.Prefab("Bronze").m_shared.m_name) == 0 && Items.Count(Read().Bags.Concat((IEnumerable<Inventory>)(object)new Inventory[1] { inv }), fine.m_shared.m_name) == 0, "controls: the upgrade consumes exactly three bronze and ten fine wood across pocket and storage"); unit.Container.Interact((Humanoid)(object)player, true, true); Check(uz.GetInt("magicstorage_unit_tier", 0) == 1 && !ui.Visible, "controls: holding alternate E does not repeat the upgrade or open storage"); hoverText = unit.Container.GetHoverText(); Check(hoverText.Contains(Plugin.Name(Items.Prefab("Iron")) + " ×5") && hoverText.Contains(Plugin.Name(Items.Prefab("ElderBark")) + " ×10") && hoverText.Contains("40 ячеек"), "controls: after upgrade the hover shows the next tier and its new price"); ItemData remote = AddLabItem("MagicStorageWirelessRemote"); Check(RemoteControl.Bind(remote, core) && RemoteControl.Tier(remote) == 0 && RemoteControl.Limit(0) == 100f, "controls: a genuine base remote is bound with a 100m limit"); ItemData val2 = AddLabItem("Hammer"); ((Humanoid)player).EquipItem(val2, false); yield return (object)new WaitForSecondsRealtime(0.3f); Check(Client.Instance.BuildCount(wood.m_shared.m_name) == 30 && !ui.Visible, "controls: equipping the ordinary hammer obtains stored wood automatically"); yield return LabTeleport(LabLandPoint(cz.GetPosition(), 125f), rotation); ui.Close(); yield return (object)new WaitForSecondsRealtime(0.2f); Check(!RemoteControl.TryTarget(remote, out var id, out var pos, out var range, quiet: true) && Client.Instance.BuildCount(wood.m_shared.m_name) == 0, "controls: leaving the 100m radius clears the ordinary hammer counts and rejects the remote"); RemoteControl.Use(remote); Check(!ui.Visible, "controls: using the out-of-range remote does not open the window"); Request r = new Request { PlayerId = player.GetPlayerID(), World = ZNet.instance.GetWorld().m_uid, CoreId = cz.GetString("magicstorage_network_id", ""), CorePosition = cz.GetPosition(), Range = 1000000f, Remote = true, Operation = "buildSnapshot" }; Check(!Server.Execute(r, ((Component)player).transform.position).Success, "controls: the server rejects a base remote forging an unlimited requested range"); int requests = Client.SnapshotRequests; int pushes = StorageEvents.Snapshots; stock = Items.Load(Network.ReadData(uz), 9, 3); val = wood.Clone(); val.m_dropPrefab = ObjectDB.instance.GetItemPrefab("Wood"); val.m_stack = 1; Items.Add(stock, val); Network.WriteData(uz, Items.Save(stock)); Network.Sync(uz); yield return (object)new WaitForSecondsRealtime(0.2f); Check(Client.SnapshotRequests == requests && StorageEvents.Snapshots == pushes, "controls: storage changes outside range do not notify the ordinary hammer"); yield return LabTeleport(cz.GetPosition() + new Vector3(20f, 8f, 0f), rotation); yield return (object)new WaitForSecondsRealtime(0.2f); Check(RemoteControl.TryTarget(remote, out id, out pos, out range, quiet: true) && Client.SnapshotRequests == requests + 1 && Client.Instance.BuildCount(wood.m_shared.m_name) == 31 && !ui.Visible, "controls: returning into range requests once and shows the current wood without opening storage"); RemoteControl.Use(remote); yield return (object)new WaitForSecondsRealtime(0.2f); Check(ui.Visible && Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { ui.Stored }, wood.m_shared.m_name) == 31, "controls: the in-range base remote opens the current full storage"); ui.Close(); ((Humanoid)player).UnequipAllItems(); yield return LabTeleport(position, rotation); GameObject val3 = ZNetScene.instance.FindInstance(coreId); if (!Object.op_Implicit((Object)(object)val3)) { val3 = (GameObject)AccessTools.Method(typeof(ZNetScene), "CreateObject", (Type[])null, (Type[])null).Invoke(ZNetScene.instance, new object[1] { cz }); } core = val3.GetComponent<Node>(); GameObject val4 = ZNetScene.instance.FindInstance(unitId); if (!Object.op_Implicit((Object)(object)val4)) { val4 = (GameObject)AccessTools.Method(typeof(ZNetScene), "CreateObject", (Type[])null, (Type[])null).Invoke(ZNetScene.instance, new object[1] { uz }); } unit = val4.GetComponent<Node>(); cz.Set(RemoteControl.Grant(player.GetPlayerID()), grant); uz.Set("magicstorage_unit_tier", originalTier); Network.WriteData(cz, coreData); Network.WriteData(uz, unitData); Network.Sync(cz); Network.Sync(uz); inv.RemoveAll(); foreach (ItemData allItem in Items.Load(pocket, inv.GetWidth(), inv.GetHeight()).GetAllItems()) { inv.GetAllItems().Add(allItem); } Client.Persist(); Client.Instance.WatchChanged(); yield return (object)new WaitForSecondsRealtime(0.2f); } private IEnumerator CaptureUnitHover(Player player, string expected) { Camera camera = Utils.GetMainCamera(); GameCamera controller = GameCamera.instance; bool active = ((Behaviour)controller).enabled; Vector3 position = ((Component)camera).transform.position; Quaternion rotation = ((Component)camera).transform.rotation; Rigidbody body = ((Component)player).GetComponent<Rigidbody>(); Vector3 playerPosition = ((Component)player).transform.position; Vector3 val = ((Component)unit).transform.TransformPoint(new Vector3(0f, 0f, -1.7f)); val.y = ZoneSystem.instance.GetGroundHeight(val) + 0.1f; ((Component)player).transform.position = val; body.position = val; Renderer[] renderers = (from renderer in ((Component)player).GetComponentsInChildren<Renderer>() where renderer.enabled select renderer).ToArray(); Renderer[] array = renderers; for (int num = 0; num < array.Length; num++) { array[num].enabled = false; } if (Object.op_Implicit((Object)(object)MessageHud.instance)) { MessageHud.instance.ClearUnlockQueue(); } ((Behaviour)controller).enabled = false; ((Component)camera).transform.position = ((Component)unit).transform.TransformPoint(new Vector3(0f, 1.2f, -2f)); ((Component)camera).transform.LookAt(((Component)unit).transform.position + Vector3.up * 0.5f); yield return (object)new WaitForSecondsRealtime(0.3f); AccessTools.Method(typeof(Player), "UpdateHover", (Type[])null, (Type[])null).Invoke(player, null); AccessTools.Method(typeof(Hud), "UpdateCrosshair", (Type[])null, (Type[])null).Invoke(Hud.instance, new object[2] { player, 0f }); GameObject hovered = ((Humanoid)player).GetHoverObject(); int num2 = (int)AccessTools.Field(typeof(Player), "m_interactMask").GetValue(player); IEnumerable<string> values = from hit in Physics.RaycastAll(((Component)controller).transform.position, ((Component)controller).transform.forward, 50f, num2) orderby ((RaycastHit)(ref hit)).distance select $"{((Object)((RaycastHit)(ref hit)).collider).name}/layer {((Component)((RaycastHit)(ref hit)).collider).gameObject.layer}/ray {((RaycastHit)(ref hit)).distance}/eye {Vector3.Distance(((Character)player).m_eye.position, ((RaycastHit)(ref hit)).point)}"; Plugin.Log(string.Format("LAB hover ray: object {0}; place mode {1}; eye distance {2}; interaction limit {3}; camera {4}; native {5}/{6}; mask {7}; hits {8}; HUD {9}", Object.op_Implicit((Object)(object)hovered) ? ((Object)hovered).name : "none", ((Character)player).InPlaceMode(), Vector3.Distance(((Character)player).m_eye.position, ((Component)unit).transform.position), player.m_maxInteractDistance, ((Component)camera).transform.position, ((Component)controller).transform.position, ((Component)controller).transform.forward, num2, string.Join(";", values), ((TMP_Text)Hud.instance.m_hoverName).text)); yield return (object)new WaitForEndOfFrame(); ScreenCapture.CaptureScreenshot(Path.Combine(output, "unit-upgrade-hover-game.png")); yield return (object)new WaitForSecondsRealtime(0.2f); Check(Object.op_Implicit((Object)(object)hovered) && (Object)(object)hovered.GetComponentInParent<Node>() == (Object)(object)unit && ((TMP_Text)Hud.instance.m_hoverName).text == expected, "controls: the real world ray and native HUD display the block upgrade price"); array = renderers; foreach (Renderer val2 in array) { if (Object.op_Implicit((Object)(object)val2)) { val2.enabled = true; } } ((Component)player).transform.position = playerPosition; body.position = playerPosition; ((Component)camera).transform.position = position; ((Component)camera).transform.rotation = rotation; ((Behaviour)controller).enabled = active; } private IEnumerator DeliverySaveChecks() { StorageUI ui = StorageUI.Instance; ui.Open(core); yield return (object)new WaitForSecondsRealtime(0.2f); ItemData item = ui.Stored.GetAllItems().First((ItemData i) => Plugin.Prefab(i) == "Wood"); Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory(); string text = Items.Save(inventory); string text2 = Items.Save(Client.Inbox); int num = Items.Count(Read().Bags, item.m_shared.m_name); int saveFailures = SaveFailures; HoldBuildReply = true; BufferedReply = null; Client.Instance.BeginRequest("withdraw", Items.Key(item)); Action bufferedReply = BufferedReply; BufferedReply = null; HoldBuildReply = false; Check(bufferedReply != null && Client.Busy, "delivery save: a real committed withdrawal has a pending routed response"); FailNextPersist = true; bufferedReply(); Check(SaveFailures == saveFailures + 1 && Client.Busy && Items.Save(inventory) == text && Items.Save(Client.Inbox) == text2, "delivery save: an injected receipt-save failure restores exact inventory and mailbox and keeps the pending request"); bufferedReply(); bufferedReply(); Check(!Client.Busy && Items.Count(Read().Bags, item.m_shared.m_name) == num - 1 && inventory.CountItems(item.m_shared.m_name, -1, true) == Items.Load(text, inventory.GetWidth(), inventory.GetHeight()).CountItems(item.m_shared.m_name, -1, true) + 1, "delivery save: retrying the same response delivers once after rollback and consumes stock once"); Check(!Player.m_localPlayer.m_customData.ContainsKey("runic.pending") && Client.Inbox.NrOfItems() == 0, "delivery save: the saved completion has no pending request or duplicate mailbox item"); ItemData item2 = inventory.GetAllItems().First((ItemData i) => Items.Key(i) == Items.Key(item)); Client.Instance.Deposit(item2, 1); yield return (object)new WaitForSecondsRealtime(0.2f); ui.Close(); } private IEnumerator EventChecks() { Player player = Player.m_localPlayer; Inventory inv = ((Humanoid)player).GetInventory(); StorageUI ui = StorageUI.Instance; ItemData[] initialItems = inv.GetAllItems().ToArray(); ItemData originalTool = NetworkBuild.Tool(player); if (originalTool != null) { ((Humanoid)player).UnequipItem(originalTool, false); } ItemData remote = AddLabItem("MagicStorageWirelessRemote"); Check(RemoteControl.Bind(remote, core), "events: the hammer fixture carries a genuinely bound remote"); yield return (object)new WaitForSecondsRealtime(0.2f); ZDO z = unit.View.GetZDO(); string original = Network.ReadData(z); string wood = Items.Prefab("Wood").m_shared.m_name; ui.Open(core); yield return (object)new WaitForSecondsRealtime(0.3f); int requests = Client.SnapshotRequests; int pushes = StorageEvents.Snapshots; yield return (object)new WaitForSecondsRealtime(3.2f); Check(Client.SnapshotRequests == requests && StorageEvents.Snapshots == pushes, "events: an idle open storage sends no periodic requests or server snapshots for 3.2 seconds"); Network.WriteData(z, original); yield return (object)new WaitForSecondsRealtime(0.2f); Check(Client.SnapshotRequests == requests && StorageEvents.Snapshots == pushes, "events: writing identical item bytes does not produce an update"); int before = Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { ui.Stored }, wood); for (int i = 0; i < 3; i++) { Inventory inv2 = Items.Load(Network.ReadData(z), 9, 3); ItemData val = Items.Prefab("Wood").Clone(); val.m_dropPrefab = ObjectDB.instance.GetItemPrefab("Wood"); val.m_stack = 1; Items.Add(inv2, val); Network.WriteData(z, Items.Save(inv2)); Network.Sync(z); } yield return (object)new WaitForSecondsRealtime(0.2f); Check(Client.SnapshotRequests == requests && StorageEvents.Snapshots == pushes + 1 && Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { ui.Stored }, wood) == before + 3, "events: three real storage writes in one frame push one fresh snapshot without a client request"); ZDO unrelated = ZDOMan.instance.CreateNewZDO(((Component)core).transform.position + Vector3.right * 1000f, Network.UnitHash); unrelated.SetPrefab(Network.UnitHash); unrelated.SetOwner(ZDOMan.GetSessionID()); Network.WriteData(unrelated, original); yield return (object)new WaitForSecondsRealtime(0.2f); Check(StorageEvents.Snapshots == pushes + 1, "events: changes in a distant unrelated network do not notify this subscriber"); ZDOMan.instance.DestroyZDO(unrelated); Vector3 val2 = ((Component)core).transform.position + Vector3.right * 50f; ZDO neighborCore = ZDOMan.instance.CreateNewZDO(val2, Network.CoreHash); neighborCore.SetPrefab(Network.CoreHash); neighborCore.Set("magicstorage_network_id", "lab-neighbor"); neighborCore.Set("runic.lab.fixture", true); ZDO neighborUnit = ZDOMan.instance.CreateNewZDO(val2 + Vector3.right * 2f, Network.UnitHash); neighborUnit.SetPrefab(Network.UnitHash); neighborUnit.Set("runic.lab.fixture", true); yield return (object)new WaitForSecondsRealtime(0.2f); int neighborPushes = StorageEvents.Snapshots; Network.WriteData(neighborUnit, original); yield return (object)new WaitForSecondsRealtime(0.2f); Check(StorageEvents.Snapshots == neighborPushes, "events: item changes in a nearby separate network do not rebuild or send this storage snapshot"); neighborCore.SetOwner(ZDOMan.GetSessionID()); neighborUnit.SetOwner(ZDOMan.GetSessionID()); ZDOMan.instance.DestroyZDO(neighborUnit); ZDOMan.instance.DestroyZDO(neighborCore); Node added = Object.Instantiate<GameObject>(ZNetScene.instance.GetPrefab("MagicStorageUnit"), ((Component)core).transform.position + Vector3.right * 4f, Quaternion.identity).GetComponent<Node>(); added.View.GetZDO().Set("runic.lab.fixture", true); ((Component)added).GetComponent<WearNTear>().m_noSupportWear = true; ((Component)added).GetComponent<WearNTear>().m_noRoofWear = true; yield return (object)new WaitForSecondsRealtime(0.3f); Text stats = (Text)AccessTools.Field(typeof(StorageUI), "stats").GetValue(ui); Check(Read().Records.Count == 3 && stats.text.Contains("Блоков: 3"), "events: placing a real empty block updates capacity and membership without polling"); ZDOID addedId = added.View.GetZDO().m_uid; ((Component)added).GetComponent<WearNTear>().Remove(false); yield return (object)new WaitUntil((Func<bool>)(() => ZDOMan.instance.GetZDO(addedId) == null)); yield return (object)new WaitForSecondsRealtime(0.2f); Check(Read().Records.Count == 2 && stats.text.Contains("Блоков: 2"), "events: removing a real block updates membership without polling"); CraftingStation bench = Object.FindObjectsOfType<CraftingStation>().First((CraftingStation s) => s.m_name == "$piece_workbench" && Vector3.Distance(((Component)s).transform.position, ((Component)core).transform.position) < 20f); int stationLevel = ui.Stations[bench.m_name]; GameObject extension = Object.Instantiate<GameObject>(ZNetScene.instance.GetPrefab("piece_workbench_ext2"), ((Component)bench).transform.position + Vector3.right, Quaternion.identity); ZDO extensionZdo = extension.GetComponent<ZNetView>().GetZDO(); extensionZdo.Set("runic.lab.fixture", true); extension.GetComponent<WearNTear>().m_noSupportWear = true; extension.GetComponent<WearNTear>().m_noRoofWear = true; yield return (object)new WaitForSecondsRealtime(0.3f); Check(ui.Stations[bench.m_name] == stationLevel + 1, "events: adding a station extension updates crafting levels without polling"); ZDOID extensionId = extensionZdo.m_uid; extension.GetComponent<WearNTear>().Remove(false); yield return (object)new WaitUntil((Func<bool>)(() => ZDOMan.instance.GetZDO(extensionId) == null)); yield return (object)new WaitForSecondsRealtime(0.2f); Check(ui.Stations[bench.m_name] == stationLevel, "events: removing a station extension updates crafting levels without polling"); Network.WriteData(z, original); Network.Sync(z); yield return (object)new WaitForSecondsRealtime(0.2f); ui.Close(); yield return (object)new WaitForSecondsRealtime(0.2f); pushes = StorageEvents.Snapshots; requests = Client.SnapshotRequests; Inventory closedBag = Items.Load(original, 9, 3); ItemData val3 = Items.Prefab("Wood").Clone(); val3.m_dropPrefab = ObjectDB.instance.GetItemPrefab("Wood"); val3.m_stack = 1; Items.Add(closedBag, val3); Network.WriteData(z, Items.Save(closedBag)); Network.Sync(z); yield return (object)new WaitForSecondsRealtime(0.2f); Check(!ui.Visible && Client.SnapshotRequests == requests && StorageEvents.Snapshots == pushes, "events: closed storage without a held hammer is unsubscribed and receives no snapshots"); ItemData hammer = AddLabItem("MagicStorageHammer"); ((Humanoid)player).EquipItem(hammer, false); yield return (object)new WaitForSecondsRealtime(0.3f); ItemData val4 = NetworkBuild.Tool(player); Plugin.Log(string.Format("LAB hammer subscription: requests {0} -> {1}; wood {2} / {3}; actual tool {4}; intended equipped {5}; range {6}; position {7}; target {8}", requests, Client.SnapshotRequests, before + 1, Client.Instance.BuildCount(wood), (val4 == null) ? "none" : Plugin.Prefab(val4), hammer.m_equipped, RemoteControl.Limit(RemoteControl.Tier(remote)), ((Component)player).transform.position, ui.CorePosition)); Check(!ui.Visible && Client.SnapshotRequests == requests + 1 && Client.Instance.BuildCount(wood) == before + 1, "events: equipping the real hammer requests one initial snapshot without opening anyone's window"); requests = Client.SnapshotRequests; pushes = StorageEvents.Snapshots; yield return (object)new WaitForSecondsRealtime(3.2f); Check(Client.SnapshotRequests == requests && StorageEvents.Snapshots == pushes, "events: holding the hammer idle sends no repeated requests or snapshots"); Network.WriteData(z, original); Network.Sync(z); yield return (object)new WaitForSecondsRealtime(0.2f); Check(Client.SnapshotRequests == requests && StorageEvents.Snapshots == pushes + 1 && Client.Instance.BuildCount(wood) == before && !ui.Visible, "events: a real network change refreshes the held hammer without opening the storage UI"); Reply reply = Server.Execute(new Request { PlayerId = player.GetPlayerID(), CoreId = ui.CoreId, CorePosition = ui.CorePosition, Range = ui.Range, Remote = true, Operation = "buildSnapshot" }, ((Component)player).transform.position); Check(reply.BuildOnly && reply.Summary == "" && reply.Stations.Count == 0 && reply.BuildTotals[wood] == before, "build view: the hammer reply has only resource totals and no item inventory or station list"); ItemData cooked = Items.Prefab("CookedMeat"); Check(BuildResources.Contains(cooked.m_shared.m_name) && ZNetScene.instance.m_prefabs.Any((GameObject prefab) => Object.op_Implicit((Object)(object)prefab) && Object.op_Implicit((Object)(object)prefab.GetComponent<Piece>()) && prefab.GetComponent<Piece>().m_resources.Any((Requirement resource) => Object.op_Implicit((Object)(object)resource.m_resItem) && resource.m_resItem.m_itemData.m_shared.m_name == cooked.m_shared.m_name)), "build view: food used by a registered building recipe is classified as a building resource"); ItemData nonbuild = Items.Prefab("AxeStone").Clone(); nonbuild.m_dropPrefab = ObjectDB.instance.GetItemPrefab("AxeStone"); nonbuild.m_stack = 1; Check(!BuildResources.Contains(nonbuild.m_shared.m_name) && BuildResources.Contains(wood), "build view: registered piece requirements include wood and exclude the stone axe"); Inventory inv3 = Items.Load(original, 9, 3); Items.Add(inv3, nonbuild); Network.WriteData(z, Items.Save(inv3)); Network.Sync(z); yield return (object)new WaitForSecondsRealtime(0.2f); Check(StorageEvents.Snapshots == pushes + 1 && Client.Instance.BuildCount(nonbuild.m_shared.m_name) == 0, "build view: changing only nonbuilding stock does not send a hammer update"); requests = Client.SnapshotRequests; ui.Open(core); yield return (object)new WaitForSecondsRealtime(0.2f); Check(ui.Visible && Client.SnapshotRequests == requests + 1 && Items.Count((IEnumerable<Inventory>)(object)new Inventory[1] { ui.Stored }, nonbuild.m_shared.m_name) == 1, "build view: opening the full storage replaces the subscription and includes nonbuilding items"); ui.Close(); yield return (object)new WaitForSecondsRealtime(0.2f); _ = StorageEvents.Snapshots; _ = Client.SnapshotRequests; Check(!ui.Visible && Client.Instance.BuildCount(wood) == before, "build view: closing storage with the hammer held restores its compact view"); ((Humanoid)player).UnequipItem(hammer, false); yield return (object)new WaitForSecondsRealtime(0.2f); requests = Client.SnapshotRequests; pushes = StorageEvents.Snapshots; Network.WriteData(z, Items.Save(closedBag)); Network.Sync(z); yield return (object)new WaitForSecondsRealtime(0.2f); Check(Client.SnapshotRequests == requests && StorageEvents.Snapshots == pushes, "events: putting the hammer away stops network updates"); ((Humanoid)player).EquipItem(hammer, false); yield return (object)new WaitForSecondsRealtime(0.3f); Check(Client.SnapshotRequests == requests + 1 && Client.Instance.BuildCount(wood) == before + 1, "events: re-equipping the hammer obtains the current stock once"); ((Humanoid)player).UnequipItem(hammer, false); Network.WriteData(z, original); Network.Sync(z); ItemData[] array = (from val6 in inv.GetAllItems() where !initialItems.Contains(val6) && (Plugin.Prefab(val6) == "MagicStorageHammer" || Plugin.Prefab(val6) == "MagicStorageWirelessRemote") select val6).ToArray(); foreach (ItemData val5 in array) { inv.RemoveItem(val5); } if (originalTool != null && inv.ContainsItem(originalTool)) { ((Humanoid)player).EquipItem(originalTool, false); } yield return (object)new WaitForSecondsRealtime(0.2f); } private IEnumerator ItemDetailChecks() { StorageUI ui = StorageUI.Instance; Player player = Player.m_localPlayer; Inventory inv = ((Humanoid)player).GetInventory(); Check(!InventoryJournal.InlineWrites, "details: production background writer is enabled"); Lab lab = this; KeyboardShortcut value = Plugin.OpenKey.Value; lab.Check((int)((KeyboardShortcut)(ref value)).MainKey == 103, "controls: persisted E migrates to the separate G shortcut"); SetupStock(); Network.WriteData(core.View.GetZDO(), Items.Save(Items.Bag(4, 4))); Network.Sync(core.View.GetZDO()); inv.RemoveAll(); ui.Open(core); AccessTools.Method(typeof(StorageUI), "Tab", (Type[])null, (Type[])null).Invoke(ui, new object[1] { "storage" }); yield return (object)new WaitForSecondsRealtime(0.2f); yield return TransferSettled(); Action<string> select = delegate(string prefab) { AccessTools.Field(typeof(StorageUI), "selected").SetValue(ui, Items.Key(ui.Stored.GetAllItems().First((ItemData item2) => Plugin.Prefab(item2) == prefab))); AccessTools.Method(typeof(StorageUI), "Refresh", (Type[])null, (Type[])null).Invoke(ui, null); }; select("Wood"); yield return null; RectTransform detail = ui.Panel.GetComponentsInChildren<RectTransform>().First((RectTransform child) => ((Object)child).name == "Recipe details"); Button[] buttons = ((Component)detail).GetComponentsInChildren<Button>(); Check(buttons.Length == 3, "details: selected storage item exposes three withdrawal controls"); Button stackButton = buttons.First((Button button) => ((Object)button).name == "Runic button Забрать стак"); Button oneButton = buttons.First((Button button) => ((Object)button).name == "Runic button Забрать 1"); Button allButton = buttons.First((Button button) => ((Object)button).name == "Runic button Забрать всё"); int[] objects = (from child in ((Component)detail).GetComponentsInChildren<Transform>() select ((Object)child).GetInstanceID()).ToArray(); Func<bool> stable = () => (from child in ((Component)detail).GetComponentsInChildren<Transform>() select ((Object)child).GetInstanceID()).SequenceEqual(objects) && buttons.All((Button button) => Object.op_Implicit((Object)(object)button) && ((Selectable)button).interactable && ((Component)button).gameObject.activeInHierarchy); Func<string> quantity = () => ((Component)detail).GetComponentsInChildren<Text>().First((Text label) => label.text.StartsWith("Количество:") || label.text.StartsWith("Quantity:")).text; ItemData val = AddLabItem("Wood", 25); string woodName = val.m_shared.m_name; int stock = Items.Count(Read().Bags, woodName); PointerEventData val2 = new PointerEventData(EventSystem.current) { button = (InputButton)0 }; ExecuteEvents.Execute<IPointerEnterHandler>(((Component)oneButton).gameObject, (BaseEventData)(object)val2, ExecuteEvents.pointerEnterHandler); ColorBlock colors = ((Selectable)oneButton).colors; HoldJournal = true; Client.Instance.Deposit(val, 25); yield return (object)new WaitForSecondsRealtime(0.15f); AccessTools.Method(typeof(StorageUI), "Refresh", (Type[])null, (Type[])null).Invoke(ui, null); Check(Client.Busy && stable() && ((Selectable)oneButton).colors == colors, "details: held disk write preserves all controls, enabled appearance and hovered button"); string text = player.m_customData["runic.sequence"]; string text2 = player.m_customData["runic.pending"]; ((UnityEvent)allButton.onClick).Invoke(); Check(player.m_customData["runic.sequence"] == text && player.m_customData["runic.pending"] == text2 && Items.Count(Read().Bags, woodName) == stock, "details: a click while busy cannot start another transfer"); HoldJournal = false; yield return TransferSettled(); Check(stable() && Items.Count(Read().Bags, woodName) == stock + 25 && quantity().Contains((stock + 25).ToString()), "details: depositing updates the quantity without replacing text, icon or buttons"); ((UnityEvent)oneButton.onClick).Invoke(); yield return TransferSettled(); Check(stable() && inv.CountItems(woodName, -1, true) == 1 && quantity().Contains((stock + 24).ToString()), "details: withdraw-one delivers once and keeps the entire card alive"); ((UnityEvent)stackButton.onClick).Invoke(); yield return TransferSettled(); Check(stable() && inv.CountItems(woodName, -1, true) == 51 && Items.Count(Read().Bags, woodName) == stock - 26, "details: withdraw-stack uses the current stock and the native fifty-item stack limit"); select("Stone"); yield return null; ItemData stone = Items.Prefab("Stone"); int stones = Items.Count(Read().Bags, stone.m_shared.m_name); ((UnityEvent)oneButton.onClick).Invoke(); yield return TransferSettled(); Check(stable() && Items.Count(Read().Bags, stone.m_shared.m_name) == stones - 1 && Items.Count(Read().Bags, woodName) == stock - 26, "details: reused controls transfer the newly selected item instead of a stale closure"); val = inv.GetAllItems().First((ItemData item2) => Plugin.Prefab(item2) == "Wood"); Client.Instance.Deposit(val, 1); yield return TransferSettled(); Check(stable() && quantity().Contains((stones - 1).ToString()), "details: an unrelated deposit preserves the selected item's controls and quantity"); select("Wood"); ((UnityEvent)allButton.onClick).Invoke(); yield return TransferSettled(); Check(stable() && Items.Count(Read().Bags, woodName) == 0 && inv.CountItems(woodName, -1, true) == 75 && quantity().Contains((stones - 1).ToString()), "details: withdraw-all reads the latest amount and moves selection after the item disappears"); string language = L.Language; Localization.instance.SetLanguage("English"); yield return null; yield return null; Check(stable() && ((Component)allButton).GetComponentInChildren<Text>().text == "Withdraw all" && !quantity().Contains("Количество"), "details: language changes translate persistent controls without replacing them"); Localization.instance.SetLanguage(language); yield return null; yield return null; ((UnityEvent)ui.Panel.GetComponentsInChildren<Button>().First((Button button) => ((Object)button).name == "Runic button ? Управление").onClick).Invoke(); yield return null; Check(((Component)detail).GetComponentsInChildren<Text>().Any((Text label) => label.text.Contains("G —")), "controls: help names the separate remote key"); ((UnityEvent)ui.Panel.GetComponentsInChildren<Button>().First((Button button) => ((Object)button).name == "Runic button ? Управление").onClick).Invoke(); yield return null; Check(((Component)detail).GetComponentsInChildren<Button>().Length == 3 && ((Component)detail).GetComponentsInChildren<Image>().Any((Image image) => ((Object)image).name == "Item icon" && (Object)(object)image.sprite == (Object)(object)stone.GetIcon()), "details: returning from help rebuilds the current card correctly"); foreach (Recipe item2 in ObjectDB.instance.m_recipes.Where((Recipe recipe) => Object.op_Implicit((Object)(object)recipe.m_item) && ((Object)((Component)recipe.m_item).gameObject).name == "AxeStone")) { AccessTools.Method(typeof(Player), "AddKnownRecipe", (Type[])null, (Type[])null).Invoke(player, new object[1] { item2 }); } AccessTools.Method(typeof(StorageUI), "Tab", (Type[])null, (Type[])null).Invoke(ui, new object[1] { "craft" }); InputField search = ui.Panel.GetComponentsInChildren<InputField>().First(); search.text = Plugin.Name(Items.Prefab("AxeStone")); yield return null; Check(((Component)detail).GetComponentsInChildren<ScrollRect>().Length == 1 && ((Component)detail).GetComponentsInChildren<Button>().Length == 1, "details: recipe selection still exposes ingredient scrolling and its craft action"); int axes = inv.CountItems(Items.Prefab("AxeStone").m_shared.m_name, -1, true); ((UnityEvent)((Component)detail).GetComponentsInChildren<Button>().Single().onClick).Invoke(); yield return TransferSettled(); Check(inv.CountItems(Items.Prefab("AxeStone").m_shared.m_name, -1, true) == axes + 1, "details: crafting still works after switching away from persistent item controls"); search.text = ""; AccessTools.Method(typeof(StorageUI), "Tab", (Type[])null, (Type[])null).Invoke(ui, new object[1] { "storage" }); ItemData item = AddLabItem("MagicStorageWirelessRemote"); Check(RemoteControl.Bind(item, core), "controls: manual-test remote binds to the live core"); ui.Close(); yield return null; try { ItemDetailKeyProbe.Key = (KeyCode)101; AccessTools.Method(typeof(Client), "Update", (Type[])null, (Type[])null).Invoke(Client.Instance, null); Check(!ui.Visible, "controls: E in empty space cannot open the remote through the client update"); ItemDetailKeyProbe.Key = (KeyCode)103; AccessToo