Decompiled source of RunicVault v0.7.0

plugins/RunicStorage/RunicStorage.dll

Decompiled a week ago
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