Decompiled source of RunicStorageNetwork v0.5.6

plugins\RunicStorageNetwork\RunicStorageNetwork.dll

Decompiled 6 hours 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.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using RunicStorageNetwork.Logic;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("RunicStorageNetwork")]
[assembly: AssemblyFileVersion("0.5.6.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.5.6.0")]
namespace RunicStorageNetwork
{
	internal static class Actions
	{
		internal sealed class Pending
		{
			internal Operation Op;

			internal Player Player;

			internal InventoryGui Gui;

			internal Recipe Recipe;

			internal Piece Piece;

			internal ItemData Upgrade;

			internal ItemData Tool;

			internal int UpgradeQuality;

			internal int Variant;

			internal bool Multi;

			internal bool Output;

			internal bool Cancelled;

			internal bool Prepared;

			internal Vector3 Position;

			internal Quaternion Rotation;

			internal float Started;

			internal float LastPoll;

			internal bool Warned;

			internal RecoveryAttempt Recovery;

			internal string RollbackFailure;

			internal List<Debit> Plan;

			internal InventoryDelta Delta;
		}

		internal static Pending Waiting;

		internal static Pending Active;

		private static readonly OutcomeReceipts Outcomes = new OutcomeReceipts();

		internal static bool Locked(Inventory inv)
		{
			if (Waiting != null && Object.op_Implicit((Object)(object)Waiting.Player))
			{
				return ((Humanoid)Waiting.Player).GetInventory() == inv;
			}
			return false;
		}

		internal static void Clear()
		{
			Waiting = null;
			Active = null;
			Outcomes.Clear();
			Stockroom.ClearObservations();
			CraftPreparation.Clear();
			RecipeIndex.Invalidate();
			Plugin.ClearCritical();
		}

		internal static PieceTable BuildTable(Player p)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			if (!Object.op_Implicit((Object)(object)p) || (Object)(object)p != (Object)(object)Player.m_localPlayer || !Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				return null;
			}
			ItemData val = (ItemData)R.Call(p, "GetRightItem", Type.EmptyTypes);
			PieceTable val2 = val?.m_shared?.m_buildPieces;
			if (!Object.op_Implicit((Object)(object)val2) || !((Object)(object)p.GetBuildTool() == (Object)(object)val2) || !BuildToolPolicy.Tool(val))
			{
				return null;
			}
			return val2;
		}

		internal static bool BuildPiece(Player p, Piece piece)
		{
			PieceTable val = BuildTable(p);
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)piece) || piece.m_repairPiece || piece.m_removePiece)
			{
				return false;
			}
			GameObject val2 = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.GetPrefab(R.Id(((Component)piece).gameObject)) : ((Component)piece).gameObject);
			if (Object.op_Implicit((Object)(object)val2) && val.m_pieces != null && val.m_pieces.Contains(val2))
			{
				return BuildToolPolicy.PieceReason(val2) == null;
			}
			return false;
		}

		internal static bool LocalBuildMaterials(Player p, Piece piece, RequirementMode mode)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Invalid comparison between Unknown and I4
			if (!Object.op_Implicit((Object)(object)p) || !Object.op_Implicit((Object)(object)piece))
			{
				return false;
			}
			Requirement[] resources = piece.m_resources;
			foreach (Requirement val in resources)
			{
				if (Object.op_Implicit((Object)(object)val.m_resItem) && val.m_amount > 0)
				{
					int num = (((int)mode == 2) ? 1 : val.m_amount);
					if (((Humanoid)p).GetInventory().CountItems(val.m_resItem.m_itemData.m_shared.m_name, -1, true) < num)
					{
						return false;
					}
				}
			}
			return true;
		}

		internal static bool BuildToolUsable(Player p, ItemData tool)
		{
			if (tool != null && (!tool.m_shared.m_useDurability || tool.m_durability > 0f))
			{
				return ((Character)p).HaveStamina(tool.m_shared.m_attack.m_attackStamina);
			}
			return false;
		}

		internal static Core Context(Player p, bool craft)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)p) || (Object)(object)p != (Object)(object)Player.m_localPlayer || !Plugin.Enabled || (!craft && !Object.op_Implicit((Object)(object)BuildTable(p))))
			{
				return null;
			}
			CraftingStation currentCraftingStation = p.GetCurrentCraftingStation();
			if (craft && (!Object.op_Implicit((Object)(object)currentCraftingStation) || currentCraftingStation.m_upgrader))
			{
				return null;
			}
			return Core.Choose(craft ? ((Component)currentCraftingStation).transform.position : ((Component)p).transform.position, p.GetPlayerID());
		}

		internal static bool Craft(InventoryGui gui, Player p)
		{
			if (Active != null)
			{
				return true;
			}
			if (Waiting != null)
			{
				return false;
			}
			if (CraftPreparation.Claimed)
			{
				return CraftPreparation.Execute(gui, p);
			}
			Recipe obj = R.Get<Recipe>(gui, "m_craftRecipe");
			Core core = Context(p, craft: true);
			if (!Object.op_Implicit((Object)(object)obj) || !Object.op_Implicit((Object)(object)core) || p.NoCostCheat() || ZoneSystem.instance.GetGlobalKey((GlobalKeys)25))
			{
				return true;
			}
			return CraftPreparation.Execute(gui, p);
		}

		internal static bool Build(Player p, Piece piece)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Expected O, but got Unknown
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			if (Active != null || !BuildPiece(p, piece))
			{
				return true;
			}
			if (Waiting != null)
			{
				return false;
			}
			if (LocalBuildMaterials(p, piece, (RequirementMode)0))
			{
				return true;
			}
			Core core = Context(p, craft: false);
			if (!Object.op_Implicit((Object)(object)core) || p.NoCostCheat() || R.Get<bool>(p, "m_noPlacementCost") || ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey()))
			{
				return true;
			}
			Operation operation = Create(p, core, build: true, R.Id(((Component)piece).gameObject), 0, 1);
			if (Object.op_Implicit((Object)(object)piece.m_craftingStation))
			{
				CraftingStation val = CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)p).transform.position);
				if (Object.op_Implicit((Object)(object)val) && R.Valid(R.View((Component)(object)val)))
				{
					operation.Station = R.View((Component)(object)val).GetZDO().m_uid;
				}
			}
			if (!operation.Validate(out var _, out var _, out var _))
			{
				return false;
			}
			List<Debit> list = Planner.Plan(operation.Needs, Stockroom.Available(p, core, operation.Needs), compact: true);
			if (list == null)
			{
				return false;
			}
			if (list.All((Debit d) => d.Source == "player"))
			{
				return true;
			}
			R.Call(p, "UpdatePlacementGhost", new Type[1] { typeof(bool) }, false);
			if (R.Get<object>(p, "m_placementStatus").ToString() != "Valid")
			{
				return true;
			}
			GameObject val2 = R.Get<GameObject>(p, "m_placementGhost");
			if (!Object.op_Implicit((Object)(object)val2))
			{
				return true;
			}
			Start(new Pending
			{
				Op = operation,
				Player = p,
				Piece = piece,
				Tool = (ItemData)R.Call(p, "GetRightItem", Type.EmptyTypes),
				Position = val2.transform.position,
				Rotation = val2.transform.rotation
			}, list);
			return false;
		}

		internal static Operation Create(Player p, Core core, bool build, string target, int quality, int multiplier)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			return new Operation
			{
				Id = Guid.NewGuid().ToString("N"),
				Target = target,
				Build = build,
				Quality = quality,
				Multiplier = multiplier,
				Actor = ((Character)p).GetZDOID(),
				Station = (build ? ZDOID.None : R.View((Component)(object)p.GetCurrentCraftingStation()).GetZDO().m_uid),
				Core = core.Id,
				Network = ((Component)core).GetComponent<NetworkMember>().SavedNetwork,
				Peer = ZNet.GetUID(),
				PlayerId = p.GetPlayerID()
			};
		}

		internal static bool Propose(Pending pending, List<Debit> preview)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			Core core = Operation.CoreObject(pending.Op.Core);
			string network = (Object.op_Implicit((Object)(object)core) ? ((Component)core).GetComponent<NetworkMember>().Network : "");
			pending.Op.Nodes = (from m in Topology.Members
				where Object.op_Implicit((Object)(object)m) && m.Valid && m.Network == network
				select m.View.GetZDO().m_uid).Distinct().ToArray();
			if (pending.Op.Nodes.Length == 0 || pending.Op.Nodes.Length > 4096)
			{
				return false;
			}
			pending.Op.Sources = SourceSelection.Sources(preview);
			if (pending.Op.Sources == null)
			{
				return false;
			}
			pending.Op.PlayerStock = Stockroom.Snapshot(((Humanoid)pending.Player).GetInventory(), "player", pending.Op.Needs, chest: false);
			return true;
		}

		private static void Start(Pending pending, List<Debit> preview)
		{
			if (!Propose(pending, preview))
			{
				if (Waiting == pending)
				{
					Waiting = null;
				}
				return;
			}
			Waiting = pending;
			if (pending.Recovery == null)
			{
				pending.Started = Time.unscaledTime;
				pending.Recovery = new RecoveryAttempt(pending.Started);
			}
			pending.Recovery.Sent();
			pending.LastPoll = Time.unscaledTime;
			try
			{
				Transport.Instance.Begin(pending.Op);
			}
			catch (Exception ex)
			{
				Plugin.Debug(pending.Op.Id + " request; will replay same ID: " + ex.Message);
			}
		}

		internal static void RecoverCraft(Pending pending)
		{
			pending.Prepared = false;
			pending.Started = Time.unscaledTime;
			pending.Recovery = new RecoveryAttempt(pending.Started);
			pending.Recovery.Retry("offer expired", pending.Started);
			Waiting = pending;
			Topology.Dirty();
		}

		internal static void Tick()
		{
			Pending waiting = Waiting;
			if (waiting == null)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!Intent(waiting, out var reason))
			{
				waiting.Cancelled = true;
			}
			if (!waiting.Warned && unscaledTime - waiting.Started > 10f)
			{
				waiting.Warned = true;
				Plugin.Critical(waiting.Op.Id, "Craft/build acknowledgement unresolved after 10 seconds; replaying the existing operation");
			}
			if (waiting.Recovery.InFlight)
			{
				if (!(unscaledTime - waiting.LastPoll >= 2f))
				{
					return;
				}
				waiting.LastPoll = unscaledTime;
				try
				{
					if (waiting.Prepared)
					{
						Transport.Instance.UseQuote(waiting.Op.Id);
					}
					else
					{
						Transport.Instance.Begin(waiting.Op);
					}
					return;
				}
				catch (Exception ex)
				{
					Plugin.Debug(waiting.Op.Id + " waiting for coordinator: " + ex.Message);
					return;
				}
			}
			if (!((double)unscaledTime < waiting.Recovery.Due) && !CraftPreparation.Releasing)
			{
				if (!Intent(waiting, out reason) || waiting.Recovery.Expired(unscaledTime))
				{
					Waiting = null;
					Topology.Dirty();
				}
				else
				{
					Retry(waiting);
				}
			}
		}

		internal static void Cancel()
		{
			if (Waiting != null)
			{
				Waiting.Cancelled = true;
			}
		}

		internal static void Refused(string id, string reason)
		{
			Plugin.Debug(id + " terminal refusal (resources released): " + reason);
			Pending waiting = Waiting;
			if (waiting?.Op.Id != id || !waiting.Recovery.InFlight)
			{
				return;
			}
			waiting.Prepared = false;
			if (waiting.Recovery.Retry(reason, Time.unscaledTime) && Intent(waiting, out var _))
			{
				Topology.Dirty();
				Plugin.Debug(id + " rebuilding supply and retrying automatically");
				return;
			}
			if (!waiting.Cancelled && waiting.Recovery.Expired(Time.unscaledTime) && RecoveryAttempt.Transient(reason) && !reason.Contains("insufficient"))
			{
				Plugin.Critical(id, "Automatic recovery exhausted: " + reason);
			}
			Waiting = null;
			Topology.Dirty();
		}

		private static void Retry(Pending p)
		{
			//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)
			try
			{
				Topology.Dirty();
				Topology.Refresh(force: true);
				Core core = Context(p.Player, !p.Op.Build);
				if (!Object.op_Implicit((Object)(object)core))
				{
					if (!p.Recovery.Retry("network path or coverage changed", Time.unscaledTime))
					{
						Waiting = null;
					}
					return;
				}
				Operation operation = Create(p.Player, core, p.Op.Build, p.Op.Target, p.Op.Quality, p.Op.Multiplier);
				operation.Station = p.Op.Station;
				if (!operation.Validate(out var _, out var _, out var reason))
				{
					if (!p.Recovery.Retry(reason, Time.unscaledTime))
					{
						Waiting = null;
					}
					return;
				}
				Stockroom.Refresh(core, p.Op.Sources);
				List<Stock> stock = Stockroom.Available(p.Player, core, operation.Needs);
				List<Debit> list = (operation.SelectNeeds(stock) ? Planner.Plan(operation.Needs, stock, compact: true) : null);
				if (list == null)
				{
					if (!p.Recovery.Retry("insufficient fresh resources", Time.unscaledTime))
					{
						Waiting = null;
					}
					return;
				}
				Plugin.Debug(p.Op.Id + " retry -> " + operation.Id + " attempt=" + (p.Recovery.Attempts + 1));
				p.Op = operation;
				p.Plan = null;
				p.Delta = null;
				Start(p, list);
			}
			catch (Exception e)
			{
				Plugin.Error(p.Op.Id + " recovery", e);
				Waiting = null;
			}
		}

		private static bool Intent(Pending p, out string reason)
		{
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Invalid comparison between Unknown and O
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Expected O, but got Unknown
			reason = "player context changed";
			if (p.Cancelled || !Object.op_Implicit((Object)(object)p.Player) || (Object)(object)p.Player != (Object)(object)Player.m_localPlayer || ((Character)p.Player).IsDead() || p.Player.NoCostCheat())
			{
				return false;
			}
			if (p.Op.Build)
			{
				reason = "build tool context changed";
				if (!BuildPiece(p.Player, p.Piece) || !((Character)p.Player).InPlaceMode() || (Object)(object)p.Player.GetSelectedPiece() != (Object)(object)p.Piece || (object)(ItemData)R.Call(p.Player, "GetRightItem", Type.EmptyTypes) != p.Tool)
				{
					return false;
				}
				goto IL_021d;
			}
			reason = "recipe changed or disabled";
			if (!RecipeIndex.Matches(p.Recipe, p.Op.Target))
			{
				return false;
			}
			reason = "craft cancelled/changed";
			if (Object.op_Implicit((Object)(object)p.Gui) && InventoryGui.IsVisible())
			{
				CraftingStation currentCraftingStation = p.Player.GetCurrentCraftingStation();
				ZDOID? val = ((currentCraftingStation == null) ? ((ZDOID?)null) : ((Component)currentCraftingStation).GetComponent<ZNetView>().GetZDO()?.m_uid);
				ZDOID station = p.Op.Station;
				if (val.HasValue && (!val.HasValue || !(val.GetValueOrDefault() != station)) && !((Object)(object)R.Get<Recipe>(p.Gui, "m_craftRecipe") != (Object)(object)p.Recipe) && R.Get<ItemData>(p.Gui, "m_craftUpgradeItem") == p.Upgrade)
				{
					object obj = R.Get<object>(p.Gui, "m_selectedRecipe");
					if ((Object)(Recipe)obj.GetType().GetProperty("Recipe").GetValue(obj, null) != (Object)(object)p.Recipe)
					{
						return false;
					}
					if (p.Upgrade != null && (!((Humanoid)p.Player).GetInventory().ContainsItem(p.Upgrade) || p.Upgrade.m_quality != p.UpgradeQuality))
					{
						return false;
					}
					goto IL_021d;
				}
			}
			return false;
			IL_021d:
			reason = "ok";
			return true;
		}

		internal static void Ready(string id, List<Debit> plan)
		{
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Invalid comparison between Unknown and O
			//IL_03c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_030d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_043b: Unknown result type (might be due to invalid IL or missing references)
			//IL_044b: Expected O, but got Unknown
			//IL_032b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0331: Unknown result type (might be due to invalid IL or missing references)
			if (Outcomes.TryGet(id, out var result))
			{
				Transport.Instance.Result(id, result.Success, result.Reason);
				return;
			}
			Pending waiting = Waiting;
			if (waiting == null || waiting.Op.Id != id)
			{
				Transport.Instance.Result(id, success: false, "no pending action");
				return;
			}
			if (waiting.RollbackFailure != null)
			{
				Transport.InternalMutation++;
				try
				{
					waiting.Delta.Restore();
				}
				catch (Exception e)
				{
					Plugin.Error(id + " retrying local rollback", e);
					return;
				}
				finally
				{
					Transport.InternalMutation--;
				}
				Outcomes.Record(id, result: false, waiting.RollbackFailure);
				Transport.Instance.Result(id, success: false, waiting.RollbackFailure);
				return;
			}
			bool flag = false;
			string text = "action not completed";
			try
			{
				if (!Intent(waiting, out var reason))
				{
					throw new InvalidOperationException(reason);
				}
				if (!waiting.Op.Validate(out var _, out var core, out var reason2))
				{
					throw new InvalidOperationException(reason2);
				}
				core.Scan();
				foreach (Debit debit in plan.Where((Debit d) => d.Source != "player"))
				{
					Container val = ((IEnumerable<Container>)core.Pool).FirstOrDefault((Func<Container, bool>)((Container source) => Object.op_Implicit((Object)(object)source) && R.Valid(R.View((Component)(object)source)) && R.Key(R.View((Component)(object)source).GetZDO().m_uid) == debit.Source));
					if (!Object.op_Implicit((Object)(object)val) || !Access.Container(val, waiting.Op.PlayerId, core, out var _, ownLease: true))
					{
						throw new InvalidOperationException("source path/access changed before result: " + debit.Source);
					}
				}
				if (!Object.op_Implicit((Object)(object)waiting.Player) || (Object)(object)waiting.Player != (Object)(object)Player.m_localPlayer || ((Character)waiting.Player).IsDead() || waiting.Player.NoCostCheat())
				{
					throw new InvalidOperationException("player context changed");
				}
				if (waiting.Op.Build)
				{
					if (!BuildPiece(waiting.Player, waiting.Piece) || R.Get<bool>(waiting.Player, "m_noPlacementCost") || !((Character)waiting.Player).InPlaceMode() || (Object)(object)waiting.Player.GetSelectedPiece() != (Object)(object)waiting.Piece || (object)(ItemData)R.Call(waiting.Player, "GetRightItem", Type.EmptyTypes) != waiting.Tool || !BuildToolUsable(waiting.Player, waiting.Tool))
					{
						throw new InvalidOperationException("build tool context changed");
					}
					R.Call(waiting.Player, "UpdatePlacementGhost", new Type[1] { typeof(bool) }, false);
					GameObject val2 = R.Get<GameObject>(waiting.Player, "m_placementGhost");
					if (!Object.op_Implicit((Object)(object)val2) || R.Get<object>(waiting.Player, "m_placementStatus").ToString() != "Valid" || Vector3.Distance(val2.transform.position, waiting.Position) > 0.05f || Quaternion.Angle(val2.transform.rotation, waiting.Rotation) > 0.5f)
					{
						throw new InvalidOperationException("placement moved/cancelled");
					}
					goto IL_0496;
				}
				if (Object.op_Implicit((Object)(object)waiting.Gui) && InventoryGui.IsVisible())
				{
					CraftingStation currentCraftingStation = waiting.Player.GetCurrentCraftingStation();
					ZDOID? val3 = ((currentCraftingStation != null) ? new ZDOID?(((Component)currentCraftingStation).GetComponent<ZNetView>().GetZDO().m_uid) : ((ZDOID?)null));
					ZDOID station = waiting.Op.Station;
					if (val3.HasValue && (!val3.HasValue || !(val3.GetValueOrDefault() != station)) && !((Object)(object)R.Get<Recipe>(waiting.Gui, "m_craftRecipe") != (Object)(object)waiting.Recipe) && R.Get<ItemData>(waiting.Gui, "m_craftUpgradeItem") == waiting.Upgrade)
					{
						object obj = R.Get<object>(waiting.Gui, "m_selectedRecipe");
						if ((Object)(Recipe)obj.GetType().GetProperty("Recipe").GetValue(obj, null) != (Object)(object)waiting.Recipe)
						{
							throw new InvalidOperationException("recipe selection changed");
						}
						if (waiting.Upgrade != null && (!((Humanoid)waiting.Player).GetInventory().ContainsItem(waiting.Upgrade) || waiting.Upgrade.m_quality != waiting.UpgradeQuality))
						{
							throw new InvalidOperationException("upgrade item changed");
						}
						goto IL_0496;
					}
				}
				throw new InvalidOperationException("craft cancelled/changed");
				IL_0496:
				waiting.Plan = plan;
				waiting.Delta = new InventoryDelta(((Humanoid)waiting.Player).GetInventory(), plan.Where((Debit d) => d.Source == "player"), chest: false);
				Active = waiting;
				Transport.InternalMutation++;
				try
				{
					waiting.Delta.Apply();
					Plugin.Debug(id + " player debit confirmed");
					if (waiting.Op.Build)
					{
						if (waiting.Player.TryPlacePiece(waiting.Piece))
						{
							waiting.Output = true;
							FinishBuild(waiting);
						}
					}
					else
					{
						R.Set(waiting.Gui, "m_craftVariant", waiting.Variant);
						R.Set(waiting.Gui, "m_multiCrafting", waiting.Multi);
						waiting.Gui.m_multiCraftAmount = waiting.Op.Multiplier;
						R.Call(waiting.Gui, "DoCrafting", new Type[1] { typeof(Player) }, waiting.Player);
					}
					flag = waiting.Output;
					if (!flag)
					{
						waiting.Delta.Restore();
						Plugin.Debug(id + " player delta restored: no result");
					}
				}
				finally
				{
					Transport.InternalMutation--;
					Active = null;
				}
			}
			catch (Exception ex)
			{
				text = ex.Message;
				if (ex is InvalidOperationException)
				{
					Plugin.Debug(id + " action postponed: " + ex.Message);
				}
				else
				{
					Plugin.Error(id + " action", ex);
				}
				flag = waiting.Output;
				if (!flag)
				{
					InventoryDelta delta = waiting.Delta;
					if (delta != null && delta.Applied)
					{
						Transport.InternalMutation++;
						try
						{
							waiting.Delta.Restore();
						}
						catch (Exception e2)
						{
							waiting.RollbackFailure = text;
							Plugin.Error(id + " local rollback unresolved", e2);
							return;
						}
						finally
						{
							Transport.InternalMutation--;
						}
					}
				}
			}
			Outcomes.Record(id, flag, flag ? "result observed" : text);
			if (flag)
			{
				Waiting = null;
				Stockroom.ClearObservations();
				CraftInspection.Clear();
				CraftOverview.Rescan();
				Topology.Dirty();
			}
			Transport.Instance.Result(id, flag, flag ? "result observed" : text);
		}

		private static void FinishBuild(Pending p)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			R.Set(p.Player, "m_lastToolUseTime", Time.time);
			((Character)p.Player).UseStamina((float)R.Call(p.Player, "GetBuildStamina", Type.EmptyTypes));
			PieceTable val = R.Get<PieceTable>(p.Player, "m_buildPieces");
			if ((int)val.m_skill != 0)
			{
				int num = R.Get<int>(p.Player, "m_buildRemoveDebt");
				if (num > 0)
				{
					R.Set(p.Player, "m_buildRemoveDebt", num - 1);
				}
				else
				{
					((Character)p.Player).RaiseSkill(val.m_skill, 1f);
				}
			}
			if (p.Tool.m_shared.m_useDurability)
			{
				ItemData tool = p.Tool;
				tool.m_durability -= (float)R.Call(p.Player, "GetPlaceDurability", new Type[1] { typeof(ItemData) }, p.Tool) * Game.m_durabilityRate;
			}
			p.Tool.m_shared.m_buildEffect.Create(((Component)p.Player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, ((Character)p.Player).GetZDOID());
		}

		internal static bool PaidRequirements(Player p, Recipe recipe)
		{
			if (Active != null && (Object)(object)Active.Player == (Object)(object)p)
			{
				return (Object)(object)Active.Recipe == (Object)(object)recipe;
			}
			return false;
		}

		internal static bool HaveCraft(Player player, Recipe recipe, int quality, int amount, out bool result)
		{
			result = false;
			if (PaidRequirements(player, recipe))
			{
				result = true;
				return true;
			}
			if (!Object.op_Implicit((Object)(object)Context(player, craft: true)))
			{
				return false;
			}
			result = CraftPreparation.Available(player, recipe, quality, amount);
			return true;
		}

		internal static bool FirstIngredient(Player p, Recipe recipe, int quality, int multiplier, out ItemData item, out int amount, out int extra)
		{
			item = null;
			amount = (extra = 0);
			if (!Object.op_Implicit((Object)(object)Context(p, craft: true)))
			{
				return false;
			}
			List<Need> list = Stockroom.Requirements(recipe.m_resources, quality, multiplier);
			List<Stock> stock = ((!((Object)(object)Active?.Recipe == (Object)(object)recipe)) ? CraftPreparation.IngredientStock(p, recipe, quality, multiplier, list) : Active.Plan.Select((Debit d) => new Stock(d.Source, d.Item, d.Quality, d.Amount)).ToList());
			foreach (Need n in list)
			{
				if (Stockroom.Qualities(new List<Need> { n }, stock, craft: true))
				{
					Requirement val = recipe.m_resources.First((Requirement r) => Object.op_Implicit((Object)(object)r.m_resItem) && ((Object)r.m_resItem).name == n.Item);
					item = val.m_resItem.m_itemData.Clone();
					item.m_quality = n.Quality;
					amount = n.Amount;
					extra = val.m_extraAmountOnlyOneIngredient;
					return true;
				}
			}
			return false;
		}
	}
	internal static class BuildToolPolicy
	{
		private static readonly Dictionary<GameObject, string> components = new Dictionary<GameObject, string>();

		private static BuildToolRules rules;

		private static ObjectDB database;

		internal static BuildToolRules Rules
		{
			get
			{
				if ((Object)(object)database != (Object)(object)ObjectDB.instance)
				{
					Invalidate();
					database = ObjectDB.instance;
				}
				if (rules == null)
				{
					rules = new BuildToolRules(Text(Plugin.AllowedBuildTools), Text(Plugin.DeniedBuildTools), Text(Plugin.DeniedPieceComponents));
				}
				return rules;
			}
		}

		internal static void Invalidate()
		{
			database = null;
			rules = null;
			components.Clear();
		}

		private static string Text(ConfigEntry<string> entry)
		{
			object obj;
			if (entry != null)
			{
				obj = entry.Value;
				if (obj == null)
				{
					return "";
				}
			}
			else
			{
				obj = "";
			}
			return (string)obj;
		}

		internal static bool Table(PieceTable table)
		{
			return Object.op_Implicit((Object)(object)table);
		}

		internal static bool Tool(ItemData tool)
		{
			if (tool != null && tool.m_shared != null && Object.op_Implicit((Object)(object)tool.m_dropPrefab) && Table(tool.m_shared.m_buildPieces))
			{
				return Rules.Tool(R.Id(tool.m_dropPrefab));
			}
			return false;
		}

		internal static string PieceReason(GameObject prefab)
		{
			if (!Object.op_Implicit((Object)(object)prefab))
			{
				return "invalid build piece";
			}
			Piece component = prefab.GetComponent<Piece>();
			if (!Object.op_Implicit((Object)(object)component) || !component.m_enabled || component.m_repairPiece || component.m_removePiece)
			{
				return "invalid build piece";
			}
			BuildToolRules buildToolRules = Rules;
			if (!components.TryGetValue(prefab, out var value))
			{
				value = (buildToolRules.Piece(R.Components(prefab)) ? null : "excluded build piece");
				components[prefab] = value;
			}
			return value;
		}

		internal static string Reason(GameObject prefab)
		{
			string text = PieceReason(prefab);
			if (text != null)
			{
				return text;
			}
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance) || ObjectDB.instance.m_items == null)
			{
				return "supply unavailable";
			}
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				ItemDrop val = (Object.op_Implicit((Object)(object)item) ? item.GetComponent<ItemDrop>() : null);
				PieceTable val2 = ((!Object.op_Implicit((Object)(object)val)) ? null : val.m_itemData?.m_shared?.m_buildPieces);
				if (Table(val2) && Rules.Tool(((Object)item).name) && val2.m_pieces != null && val2.m_pieces.Contains(prefab))
				{
					return null;
				}
			}
			return "excluded build tool";
		}

		internal static bool Eligible(GameObject prefab)
		{
			return Reason(prefab) == null;
		}
	}
	internal static class ContainerHover
	{
		private static Container target;

		private static Player player;

		private static int frame;

		private static float next;

		private static string text = "";

		private static readonly DisplayAccessCache access = new DisplayAccessCache();

		internal static string Text(Container container)
		{
			if (!Plugin.Enabled || !Object.op_Implicit((Object)(object)container) || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return "";
			}
			if ((Object)(object)target != (Object)(object)container || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				Clear();
				target = container;
				player = Player.m_localPlayer;
			}
			frame = Time.frameCount;
			return text;
		}

		internal static void Clear()
		{
			target = null;
			player = null;
			text = "";
			next = 0f;
			access.Clear();
		}

		internal static void Tick()
		{
			if (!Plugin.Enabled || !Object.op_Implicit((Object)(object)target) || !Object.op_Implicit((Object)(object)player) || (Object)(object)player != (Object)(object)Player.m_localPlayer || frame < Time.frameCount - 1)
			{
				Clear();
			}
			else
			{
				if (Time.unscaledTime < next)
				{
					return;
				}
				next = Time.unscaledTime + 0.25f;
				access.Clear();
				List<string> list = new List<string>();
				foreach (string item2 in Topology.ContainerNetworks(target))
				{
					Core core = Topology.RootSnapshot(item2);
					if (Object.op_Implicit((Object)(object)core) && Access.Container(target, player.GetPlayerID(), core, out var _, ownLease: true, null, access))
					{
						string text = NetworkName.Read(Topology.LabelRootSnapshot(item2));
						string item = ((text.Length == 0) ? RsnLocalization.Text("chest_connected") : RsnLocalization.Text("chest_connected_named", text));
						if (!list.Contains(item))
						{
							list.Add(item);
						}
					}
				}
				ContainerHover.text = string.Join("\n", list);
			}
		}
	}
	internal static class ContainerPolicy
	{
		private static readonly Dictionary<string, string> verdicts = new Dictionary<string, string>(StringComparer.Ordinal);

		private static ContainerRules rules;

		private static ZNetScene scene;

		internal static readonly List<string> Excluded = new List<string>();

		internal static int Supported { get; private set; }

		internal static ContainerRules Rules
		{
			get
			{
				if (rules == null)
				{
					rules = new ContainerRules(Text(Plugin.AllowedContainers), Text(Plugin.DeniedContainers), Text(Plugin.DeniedComponents));
				}
				return rules;
			}
		}

		internal static void Invalidate()
		{
			scene = null;
			rules = null;
		}

		private static string Text(ConfigEntry<string> entry)
		{
			object obj;
			if (entry != null)
			{
				obj = entry.Value;
				if (obj == null)
				{
					return "";
				}
			}
			else
			{
				obj = "";
			}
			return (string)obj;
		}

		internal static string Reason(string prefab)
		{
			if (!Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				return "unloaded";
			}
			if ((Object)(object)scene != (Object)(object)ZNetScene.instance)
			{
				Catalog();
			}
			if (string.IsNullOrEmpty(prefab))
			{
				return "unsupported prefab";
			}
			if (!verdicts.TryGetValue(prefab, out var value))
			{
				return "unsupported prefab";
			}
			return value;
		}

		internal static bool Eligible(string prefab)
		{
			return Reason(prefab) == null;
		}

		internal static void Ensure()
		{
			Reason("");
		}

		private static void Catalog()
		{
			scene = ZNetScene.instance;
			rules = null;
			verdicts.Clear();
			Excluded.Clear();
			int num = 0;
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if (!Object.op_Implicit((Object)(object)prefab) || verdicts.ContainsKey(((Object)prefab).name))
				{
					continue;
				}
				string text = Evaluate(prefab);
				if (!(text == "unsupported prefab"))
				{
					num++;
					verdicts[((Object)prefab).name] = text;
					if (text != null)
					{
						Excluded.Add(((Object)prefab).name);
					}
				}
			}
			Supported = num - Excluded.Count;
			Excluded.Sort(StringComparer.Ordinal);
			Plugin.Info("Container policy: " + Supported + " of " + num + " container prefabs supported; " + Rules.Summary);
			if (Excluded.Count > 0)
			{
				Plugin.Info("Containers excluded: " + string.Join(", ", Excluded.ToArray()));
			}
			Plugin.Debug("Containers supported: " + string.Join(", ", (from p in verdicts
				where p.Value == null
				select p.Key).OrderBy<string, string>((string n) => n, StringComparer.Ordinal).ToArray()));
		}

		private static string Evaluate(GameObject prefab)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Invalid comparison between Unknown and I4
			Container component = prefab.GetComponent<Container>();
			if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)prefab.GetComponent<Piece>()) || !Object.op_Implicit((Object)(object)prefab.GetComponent<ZNetView>()))
			{
				return "unsupported prefab";
			}
			if ((int)component.m_privacy != 2 || Object.op_Implicit((Object)(object)component.m_wagon) || Object.op_Implicit((Object)(object)component.m_rootObjectOverride) || Object.op_Implicit((Object)(object)prefab.GetComponent<Ship>()) || Object.op_Implicit((Object)(object)prefab.GetComponent<Rigidbody>()))
			{
				return "moving/private";
			}
			return Rules.Verdict(((Object)prefab).name, R.Components(prefab));
		}
	}
	public sealed class Core : MonoBehaviour, Hoverable
	{
		private sealed class Cached
		{
			internal float Until;

			internal List<Stock> Items;
		}

		internal static readonly HashSet<Core> Live = new HashSet<Core>();

		internal readonly List<Container> Pool = new List<Container>();

		private readonly HashSet<Inventory> subscribed = new HashSet<Inventory>();

		private readonly Dictionary<long, Cached> cached = new Dictionary<long, Cached>();

		private ZNetView view;

		private bool registered;

		internal bool Valid
		{
			get
			{
				if (R.Valid(view))
				{
					return ((Component)this).GetComponent<Piece>().IsPlacedByPlayer();
				}
				return false;
			}
		}

		internal ZDOID Id => view.GetZDO().m_uid;

		private void Awake()
		{
			view = ((Component)this).GetComponent<ZNetView>();
		}

		private void Update()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if (!Valid)
			{
				if (registered)
				{
					Live.Remove(this);
					registered = false;
					Pool.Clear();
				}
			}
			else if (!registered)
			{
				Live.Add(this);
				registered = true;
				Plugin.Debug("Core active " + R.Key(Id));
			}
		}

		internal void Scan()
		{
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			int count = Pool.Count;
			Pool.Clear();
			if (!Valid)
			{
				return;
			}
			Pool.AddRange(from c in Topology.Pool(this)
				where Object.op_Implicit((Object)(object)c) && R.Valid(R.View((Component)(object)c))
				select c);
			Pool.Sort((Container a, Container b) => string.CompareOrdinal(R.Key(R.View((Component)(object)a).GetZDO().m_uid), R.Key(R.View((Component)(object)b).GetZDO().m_uid)));
			HashSet<Inventory> inventories = new HashSet<Inventory>(from c in Pool
				select c.GetInventory() into i
				where i != null
				select i);
			Inventory[] array = subscribed.Where((Inventory i) => !inventories.Contains(i)).ToArray();
			foreach (Inventory val in array)
			{
				val.m_onChanged = (Action)Delegate.Remove(val.m_onChanged, new Action(Invalidate));
				subscribed.Remove(val);
			}
			foreach (Inventory item in inventories)
			{
				if (subscribed.Add(item))
				{
					item.m_onChanged = (Action)Delegate.Combine(item.m_onChanged, new Action(Invalidate));
				}
			}
			Invalidate();
			if (count != Pool.Count)
			{
				Plugin.Debug("Core " + R.Key(Id) + " candidate containers=" + Pool.Count);
			}
		}

		internal void Invalidate()
		{
			cached.Clear();
		}

		internal List<Stock> Stock(long player)
		{
			if (cached.TryGetValue(player, out var value) && Time.unscaledTime < value.Until)
			{
				return value.Items;
			}
			List<Stock> list = new List<Stock>();
			foreach (Container item in Pool)
			{
				if (Access.Container(item, player, this, out var _, ownLease: false, null, null, preview: true))
				{
					list.AddRange(Stockroom.Preview(item));
				}
			}
			cached[player] = new Cached
			{
				Until = Time.unscaledTime + 0.25f,
				Items = list
			};
			return list;
		}

		private void OnDestroy()
		{
			Live.Remove(this);
			Pool.Clear();
			foreach (Inventory item in subscribed)
			{
				item.m_onChanged = (Action)Delegate.Remove(item.m_onChanged, new Action(Invalidate));
			}
			subscribed.Clear();
			cached.Clear();
			if (registered)
			{
				Plugin.Debug("Core removed");
			}
		}

		internal static Core Choose(Vector3 point, long player)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Enabled)
			{
				return null;
			}
			return Topology.Choose(point, player);
		}

		public string GetHoverName()
		{
			return Localization.instance.Localize("$rsn_name");
		}

		public float GetHoverOffset()
		{
			return 1.5f;
		}

		public string GetHoverText()
		{
			return HoverInfo.Text(((Component)this).GetComponent<NetworkMember>());
		}

		internal static void Diagnose(Terminal terminal)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer))
			{
				terminal.AddString("[RSN] " + RsnLocalization.Text("diag_no_player"));
				return;
			}
			Core core = Choose(((Component)localPlayer).transform.position, localPlayer.GetPlayerID());
			string text = "[RSN] supply=" + Plugin.Enabled + " core=" + (Object.op_Implicit((Object)(object)core) ? R.Key(core.Id) : "none");
			terminal.AddString("[RSN] " + RsnLocalization.Text("diag_supply", RsnLocalization.Text(Plugin.Enabled ? "enabled" : "disabled"), Object.op_Implicit((Object)(object)core) ? R.Key(core.Id) : RsnLocalization.Text("none")));
			Plugin.Info(text);
			if (!Object.op_Implicit((Object)(object)core))
			{
				return;
			}
			core.Scan();
			foreach (Container item in core.Pool)
			{
				string reason;
				bool flag = Access.Container(item, localPlayer.GetPlayerID(), core, out reason);
				string text2 = R.Key(R.View((Component)(object)item).GetZDO().m_uid);
				text = text2 + " " + (flag ? "available" : reason);
				terminal.AddString(RsnLocalization.Text("diag_chest", text2, RsnLocalization.Reason(flag ? "available" : reason)));
				Plugin.Info(text);
			}
		}
	}
	internal static class Access
	{
		internal static bool Ward(Vector3 point, long player)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			bool flag2 = false;
			foreach (PrivateArea item in R.Get<List<PrivateArea>>(typeof(PrivateArea), "m_allAreas"))
			{
				if (Object.op_Implicit((Object)(object)item) && (bool)R.Call(item, "IsEnabled", Type.EmptyTypes) && (bool)R.Call(item, "IsInside", new Type[2]
				{
					typeof(Vector3),
					typeof(float)
				}, point, 0f))
				{
					if (((Component)item).GetComponent<Piece>().GetCreator() == player || (bool)R.Call(item, "IsPermitted", new Type[1] { typeof(long) }, player))
					{
						flag2 = true;
					}
					else
					{
						flag = true;
					}
				}
			}
			if (!flag2)
			{
				return !flag;
			}
			return true;
		}

		internal static bool Container(Container c, long player, Core core, out string reason, bool ownLease = false, string reservation = null, DisplayAccessCache display = null, bool preview = false)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Invalid comparison between Unknown and I4
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			reason = "unloaded";
			ZNetView val = R.View((Component)(object)c);
			if (!Object.op_Implicit((Object)(object)c) || !R.Valid(val) || !Object.op_Implicit((Object)(object)core) || !core.Valid)
			{
				return false;
			}
			reason = ContainerPolicy.Reason(R.Id(((Component)c).gameObject));
			if (reason != null)
			{
				return false;
			}
			Piece component = ((Component)c).GetComponent<Piece>();
			reason = "not player built";
			if (!Object.op_Implicit((Object)(object)component) || !component.IsPlacedByPlayer())
			{
				return false;
			}
			reason = "moving/private";
			if ((int)c.m_privacy != 2 || Object.op_Implicit((Object)(object)c.m_wagon) || Object.op_Implicit((Object)(object)c.m_rootObjectOverride) || Object.op_Implicit((Object)(object)((Component)c).GetComponentInParent<Ship>()) || Object.op_Implicit((Object)(object)((Component)c).GetComponentInParent<Rigidbody>()))
			{
				return false;
			}
			reason = "network path/storage coverage unavailable";
			if (!(display?.Covers(core, ((Component)c).transform.position, player) ?? Topology.Covers(core, ((Component)c).transform.position, player)))
			{
				return false;
			}
			reason = "unconfirmed loaded area";
			if (!(display?.Ready(((Component)c).transform.position) ?? ZNetScene.instance.IsAreaReady(((Component)c).transform.position)))
			{
				return false;
			}
			reason = "access denied";
			if (!(bool)R.Call(c, "CheckAccess", new Type[1] { typeof(long) }, player) || !(display?.Ward(((Component)c).transform.position, player) ?? Ward(((Component)c).transform.position, player)) || !(display?.Ward(((Component)core).transform.position, player) ?? Ward(((Component)core).transform.position, player)))
			{
				return false;
			}
			reason = "inventory unavailable";
			if (c.GetInventory() == null)
			{
				return false;
			}
			bool flag = preview && (val.GetZDO().GetString("rsn_lease", "") != "" || Transport.Reserved(val.GetZDO()));
			reason = "busy/reserved";
			if (!ownLease && !flag && (c.IsInUse() || val.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0 || Transport.Reserved(val.GetZDO(), reservation) || Integrations.IsBusy(c.GetInventory())))
			{
				return false;
			}
			reason = "available";
			return true;
		}
	}
	internal static class Stockroom
	{
		private sealed class Observation
		{
			internal byte[] Data;

			internal float Until;

			internal List<Stock> Items;
		}

		private static readonly Dictionary<string, Observation> observed = new Dictionary<string, Observation>();

		internal static void ClearObservations()
		{
			observed.Clear();
		}

		internal static void Observe(string key, List<Stock> items)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			ZDO val = RemoteContext.Source(key);
			if (val == null)
			{
				return;
			}
			observed[key] = new Observation
			{
				Data = (byte[])(val.GetByteArray(ZDOVars.s_items, (byte[])null) ?? Array.Empty<byte>()).Clone(),
				Until = Time.unscaledTime + 10f,
				Items = items
			};
			foreach (Core item in Core.Live)
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					item.Invalidate();
				}
			}
			CraftOverview.Fresh(key, items);
			if (val.GetOwner() != ZNet.GetUID())
			{
				ZDOMan.instance.RequestZDO(val.m_uid);
			}
		}

		internal static List<Stock> Preview(Container c)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = R.View((Component)(object)c).GetZDO();
			string text = R.Key(zDO.m_uid);
			List<Stock> list = Snapshot(c.GetInventory(), text, null, chest: true);
			if (observed.TryGetValue(text, out var value))
			{
				if (!value.Data.SequenceEqual(zDO.GetByteArray(ZDOVars.s_items, (byte[])null) ?? Array.Empty<byte>()) || Time.unscaledTime >= value.Until)
				{
					observed.Remove(text);
				}
				else
				{
					HashSet<string> names = new HashSet<string>(value.Items.Select((Stock s) => s.Item));
					list.RemoveAll((Stock s) => names.Contains(s.Item));
					list.AddRange(value.Items);
				}
			}
			return list;
		}

		internal static void Refresh(Core core, IEnumerable<string> sources)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = new HashSet<string>(sources);
			int num = 0;
			core.Scan();
			foreach (Container item in core.Pool)
			{
				ZNetView val = R.View((Component)(object)item);
				if (R.Valid(val) && hashSet.Contains(R.Key(val.GetZDO().m_uid)) && !Transport.Reserved(val.GetZDO()) && !Transport.Locked(item.GetInventory()))
				{
					R.Call(item, "Load");
					if (!val.IsOwner() && num++ < 32)
					{
						ZDOMan.instance.RequestZDO(val.GetZDO().m_uid);
					}
				}
			}
			core.Invalidate();
		}

		internal static bool Ingredient(ItemData item, bool chest)
		{
			if (item == null || !Object.op_Implicit((Object)(object)item.m_dropPrefab) || item.m_stack <= 0 || item.m_worldLevel < Game.m_worldLevel)
			{
				return false;
			}
			if (chest)
			{
				return !item.m_equipped;
			}
			return true;
		}

		internal static List<Stock> Snapshot(Inventory inventory, string source, IEnumerable<Need> needs, bool chest)
		{
			HashSet<string> ids = ((needs == null) ? null : new HashSet<string>(needs.Select((Need n) => n.Item)));
			return (from i in inventory.GetAllItems()
				where Ingredient(i, chest) && (ids == null || ids.Contains(((Object)i.m_dropPrefab).name))
				group i by new
				{
					Id = ((Object)i.m_dropPrefab).name,
					m_quality = i.m_quality
				} into g
				select new Stock(source, g.Key.Id, g.Key.m_quality, g.Sum((ItemData i) => i.m_stack))).ToList();
		}

		internal static List<Need> Requirements(Requirement[] requirements, int quality, int multiplier)
		{
			return (from r in requirements
				where Object.op_Implicit((Object)(object)r.m_resItem) && !r.m_upgraderResource && r.GetAmount(quality) > 0
				select new Need(((Object)r.m_resItem).name, checked(r.GetAmount(quality) * multiplier))).ToList();
		}

		internal static List<Stock> Available(Player player, Core core, List<Need> needs)
		{
			List<Stock> list = Snapshot(((Humanoid)player).GetInventory(), "player", needs, chest: false);
			if (Object.op_Implicit((Object)(object)core))
			{
				HashSet<string> ids = new HashSet<string>(needs.Select((Need n) => n.Item));
				list.AddRange(from s in core.Stock(player.GetPlayerID())
					where ids.Contains(s.Item)
					select s);
			}
			return list;
		}

		internal static bool Qualities(List<Need> needs, List<Stock> stock, bool craft)
		{
			if (!craft)
			{
				return true;
			}
			foreach (Need n in needs)
			{
				IGrouping<int, Stock> grouping = (from s in stock
					where s.Item == n.Item && s.Allowed
					group s by s.Quality into g
					orderby g.Key
					select g).FirstOrDefault((IGrouping<int, Stock> g) => g.Sum((Stock s) => s.Amount) >= n.Amount);
				if (grouping == null)
				{
					return false;
				}
				n.Quality = grouping.Key;
			}
			return true;
		}
	}
	internal sealed class InventoryDelta
	{
		internal sealed class Part
		{
			internal ItemData Item;

			internal int Amount;

			internal int Removed;

			internal Vector2i Position;
		}

		internal readonly Inventory Inventory;

		internal readonly List<Part> Parts = new List<Part>();

		internal bool Applied;

		internal InventoryDelta(Inventory inventory, IEnumerable<Debit> debits, bool chest)
		{
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			Inventory = inventory;
			Dictionary<ItemData, int> dictionary = new Dictionary<ItemData, int>();
			foreach (Debit d in debits)
			{
				int num = d.Amount;
				foreach (ItemData item in from i in inventory.GetAllItems()
					where Stockroom.Ingredient(i, chest) && ((Object)i.m_dropPrefab).name == d.Item && i.m_quality == d.Quality
					select i)
				{
					dictionary.TryGetValue(item, out var value);
					int num2 = Math.Min(num, item.m_stack - value);
					if (num2 > 0)
					{
						Part part = Parts.FirstOrDefault((Part x) => x.Item == item);
						if (part == null)
						{
							part = new Part
							{
								Item = item,
								Position = item.m_gridPos
							};
							Parts.Add(part);
						}
						part.Amount += num2;
						dictionary[item] = value + num2;
						num -= num2;
						if (num == 0)
						{
							break;
						}
					}
				}
				if (num != 0)
				{
					throw new InvalidOperationException("Fresh stock insufficient: " + d.Item);
				}
			}
		}

		internal void Apply()
		{
			if (Applied)
			{
				return;
			}
			foreach (Part part in Parts)
			{
				if (!Inventory.ContainsItem(part.Item) || part.Item.m_stack < part.Amount)
				{
					throw new InvalidOperationException("Stale inventory");
				}
			}
			Applied = true;
			try
			{
				foreach (Part part2 in Parts)
				{
					int stack = part2.Item.m_stack;
					try
					{
						if (!Inventory.RemoveItem(part2.Item, part2.Amount))
						{
							throw new InvalidOperationException("RemoveItem refused");
						}
					}
					finally
					{
						part2.Removed = stack - (Inventory.ContainsItem(part2.Item) ? part2.Item.m_stack : 0);
					}
					if (part2.Removed != part2.Amount)
					{
						throw new InvalidOperationException("Unexpected debit amount");
					}
				}
			}
			catch
			{
				Restore();
				throw;
			}
		}

		internal void Restore()
		{
			if (!Applied)
			{
				return;
			}
			foreach (Part item2 in Parts.Where((Part x) => x.Removed > 0))
			{
				int num = (Inventory.ContainsItem(item2.Item) ? item2.Item.m_stack : 0);
				int removed = item2.Removed;
				try
				{
					if (Inventory.ContainsItem(item2.Item))
					{
						ItemData item = item2.Item;
						item.m_stack += removed;
						continue;
					}
					if (Inventory.GetItemAt(item2.Position.x, item2.Position.y) != null)
					{
						throw new InvalidOperationException("Reserved rollback slot occupied");
					}
					ItemData val = item2.Item.Clone();
					val.m_stack = removed;
					try
					{
						if (!(bool)R.Call(Inventory, "AddItem", new Type[5]
						{
							typeof(ItemData),
							typeof(int),
							typeof(int),
							typeof(int),
							typeof(bool)
						}, val, removed, item2.Position.x, item2.Position.y, false))
						{
							throw new InvalidOperationException("Delta restoration refused");
						}
					}
					finally
					{
						ItemData itemAt = Inventory.GetItemAt(item2.Position.x, item2.Position.y);
						if (itemAt != null && (Object)(object)itemAt.m_dropPrefab == (Object)(object)item2.Item.m_dropPrefab && itemAt.m_quality == item2.Item.m_quality)
						{
							item2.Item = itemAt;
						}
					}
				}
				finally
				{
					int val2 = (Inventory.ContainsItem(item2.Item) ? item2.Item.m_stack : 0) - num;
					item2.Removed -= Math.Max(0, Math.Min(removed, val2));
				}
			}
			Applied = Parts.Any((Part p) => p.Removed > 0);
			R.Call(Inventory, "Changed", new Type[2]
			{
				typeof(bool),
				typeof(bool)
			}, false, false);
		}
	}
	internal static class CoreMaterials
	{
		private static readonly string[] Slots = new string[4] { "SNC_Stone", "SNC_Timber", "SNC_Iron", "SNC_Plinth" };

		private static readonly string[] Donors = new string[4] { "stone_wall_2x1", "wood_door", "iron_floor_1x1", "stone_wall_2x1" };

		private static readonly string[] Names = new string[4] { "stone_mat", "door_wood", "metalwall", "stone_mat" };

		private const string Prefix = "RSN_Vanilla_";

		internal static void Apply(GameObject core, bool relay = false)
		{
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Expected O, but got Unknown
			//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0400: Unknown result type (might be due to invalid IL or missing references)
			//IL_0433: Unknown result type (might be due to invalid IL or missing references)
			string[] array = ((!relay) ? Slots : new string[3] { "RR_Stone", "RR_Timber", "RR_Iron" });
			if (!Object.op_Implicit((Object)(object)core))
			{
				throw new InvalidOperationException("Core prefab unavailable");
			}
			MeshRenderer[] componentsInChildren = core.GetComponentsInChildren<MeshRenderer>(true);
			MeshRenderer[] array2 = (MeshRenderer[])(object)new MeshRenderer[array.Length];
			int num = 0;
			MeshRenderer[] array3 = componentsInChildren;
			foreach (MeshRenderer val in array3)
			{
				Material sharedMaterial = ((Renderer)val).sharedMaterial;
				if (!Object.op_Implicit((Object)(object)sharedMaterial))
				{
					throw new InvalidOperationException("Missing core material");
				}
				for (int j = 0; j < array.Length; j++)
				{
					if (((Object)sharedMaterial).name == "RSN_Vanilla_" + array[j])
					{
						num++;
					}
					if (((Object)sharedMaterial).name == array[j])
					{
						if (Object.op_Implicit((Object)(object)array2[j]))
						{
							throw new InvalidOperationException("Duplicate core material slot " + array[j]);
						}
						array2[j] = val;
					}
				}
			}
			if (num == array.Length)
			{
				return;
			}
			if (num != 0)
			{
				throw new InvalidOperationException("Partial native material assignment");
			}
			List<Material> list = new List<Material>();
			try
			{
				for (int k = 0; k < array.Length; k++)
				{
					if (!Object.op_Implicit((Object)(object)array2[k]))
					{
						throw new InvalidOperationException("Core material slot missing: " + array[k]);
					}
					GameObject prefab = PrefabManager.Instance.GetPrefab(Donors[k]);
					if (!Object.op_Implicit((Object)(object)prefab))
					{
						throw new InvalidOperationException("Material donor missing: " + Donors[k]);
					}
					Material val2 = null;
					Renderer[] componentsInChildren2 = prefab.GetComponentsInChildren<Renderer>(true);
					for (int i = 0; i < componentsInChildren2.Length; i++)
					{
						Material[] sharedMaterials = componentsInChildren2[i].sharedMaterials;
						foreach (Material val3 in sharedMaterials)
						{
							if (Object.op_Implicit((Object)(object)val3) && ((Object)val3).name == Names[k] && Object.op_Implicit((Object)(object)val3.shader) && ((Object)val3.shader).name == "Custom/Piece")
							{
								val2 = val3;
								break;
							}
						}
					}
					if (!Object.op_Implicit((Object)(object)val2))
					{
						throw new InvalidOperationException("Native material missing: " + Donors[k] + "/" + Names[k]);
					}
					Material val4 = new Material(val2)
					{
						name = "RSN_Vanilla_" + array[k]
					};
					list.Add(val4);
					string[] array4 = new string[6] { "_TriplanarMap", "_TriplanarLocalPos", "_TriplanarScale", "_Cull", "_Cutoff", "_TwoSidedNormals" };
					foreach (string text in array4)
					{
						if (!val4.HasProperty(text))
						{
							throw new InvalidOperationException("Native shader property missing: " + text);
						}
					}
					val4.SetFloat("_TriplanarMap", 1f);
					val4.SetFloat("_TriplanarLocalPos", 1f);
					val4.SetFloat("_TriplanarScale", 1f);
					val4.SetFloat("_Cull", 0f);
					val4.SetFloat("_TwoSidedNormals", 1f);
					val4.SetFloat("_Cutoff", 0f);
					if (k != 2)
					{
						val4.SetFloat("_RippleDistance", 0f);
						val4.SetFloat("_MoveableObject", 1f);
					}
					if (k == 3)
					{
						val4.SetTexture("_BumpMap", (Texture)null);
						val4.SetFloat("_BumpScale", 0f);
						val4.SetFloat("_ValueNoise", 0f);
					}
					if (k < 2 || k == 3)
					{
						if (k < 2 && !Object.op_Implicit((Object)(object)val4.GetTexture("_BumpMap")))
						{
							throw new InvalidOperationException("Native normal map missing: " + Names[k]);
						}
						val4.SetFloat("_TriplanarMap", 0f);
						array4 = new string[2] { "_MainTex", "_BumpMap" };
						foreach (string text2 in array4)
						{
							val4.SetTextureScale(text2, Vector2.one);
							val4.SetTextureOffset(text2, Vector2.zero);
						}
						val4.SetVector("_MainTex_ST", new Vector4(1f, 1f, 0f, 0f));
						val4.SetFloat("_BumpScale", (k == 3) ? 0f : ((!relay) ? ((k == 0) ? 1.15f : 1.25f) : ((k == 0) ? 0.55f : 0.65f)));
					}
				}
			}
			catch
			{
				foreach (Material item in list)
				{
					Object.Destroy((Object)(object)item);
				}
				throw;
			}
			for (int m = 0; m < array.Length; m++)
			{
				((Renderer)array2[m]).sharedMaterial = list[m];
				Plugin.Info((relay ? "Relay" : "Core") + " material " + array[m] + " <- " + Donors[m] + "/" + Names[m] + "; shader=Custom/Piece; " + ((m < 2) ? ("engraved surface UV; normal=" + ((Object)list[m].GetTexture("_BumpMap")).name + " strength=" + list[m].GetFloat("_BumpScale")) : ((m == 2) ? "local triplanar (unchanged since 0.1.2)" : "textured flat stone; surface UV; normal/noise disabled")));
			}
		}
	}
	internal static class CraftInspection
	{
		private sealed class Source
		{
			internal string Key;

			internal long Owner;

			internal ZDOID Id;

			internal float Sent;

			internal float FirstSent = -1f;

			internal bool Delivered;
		}

		private static Operation operation;

		private static Recipe recipe;

		private static ItemData upgrade;

		private static readonly Dictionary<string, Source> sources = new Dictionary<string, Source>();

		private static float nextRefresh;

		private static string rejectedTarget;

		private static float retryAt;

		internal static bool Ready { get; private set; }

		internal static void Clear()
		{
			operation = null;
			recipe = null;
			upgrade = null;
			sources.Clear();
			Ready = false;
			nextRefresh = 0f;
			rejectedTarget = null;
			retryAt = 0f;
		}

		internal static void Ensure(Actions.Pending selected, Core core)
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			if ((rejectedTarget == selected.Op.Target && Time.unscaledTime < retryAt) || (CraftInspection.operation != null && CraftInspection.operation.Target == selected.Op.Target && (Object)(object)recipe == (Object)(object)selected.Recipe && upgrade == selected.Upgrade && CraftInspection.operation.Core == core.Id && CraftInspection.operation.Station == selected.Op.Station && CraftInspection.operation.Quality == selected.Op.Quality && CraftInspection.operation.Multiplier == selected.Op.Multiplier && (!Ready || Time.unscaledTime < nextRefresh)))
			{
				return;
			}
			Clear();
			recipe = selected.Recipe;
			upgrade = selected.Upgrade;
			Operation operation = Actions.Create(selected.Player, core, build: false, selected.Op.Target, selected.Op.Quality, selected.Op.Multiplier);
			if (!operation.ReadRequirements(out var reason))
			{
				rejectedTarget = selected.Op.Target;
				retryAt = Time.unscaledTime + 1f;
			}
			else
			{
				if (!Actions.Propose(new Actions.Pending
				{
					Op = operation,
					Player = selected.Player
				}, new List<Debit>()))
				{
					return;
				}
				CraftInspection.operation = operation;
				foreach (Container item in core.Pool)
				{
					if (Access.Container(item, operation.PlayerId, core, out reason, ownLease: true))
					{
						ZDO zDO = R.View((Component)(object)item).GetZDO();
						string key = R.Key(zDO.m_uid);
						sources[key] = new Source
						{
							Key = key,
							Owner = zDO.GetOwner(),
							Id = zDO.m_uid,
							Sent = -100f
						};
					}
				}
				if (sources.Count == 0)
				{
					Ready = true;
					nextRefresh = Time.unscaledTime + 2f;
				}
			}
		}

		internal static void Tick()
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			if (operation == null || Ready)
			{
				return;
			}
			float now = Time.unscaledTime;
			foreach (Source item in sources.Values.Where((Source s) => !s.Delivered))
			{
				ZDO val = RemoteContext.Data(item.Id);
				if (val != null && val.GetOwner() != item.Owner)
				{
					item.Owner = val.GetOwner();
					item.Sent = -100f;
					item.FirstSent = -1f;
				}
			}
			Source[] array = sources.Values.Where((Source s) => !s.Delivered && s.FirstSent >= 0f && now - s.FirstSent > 6f).ToArray();
			foreach (Source source in array)
			{
				Apply(source, new List<Stock>());
				Plugin.Debug("Selected ingredient inspection timed out; unconfirmed source counts cleared: " + source.Key);
			}
			if (sources.Values.All((Source s) => s.Delivered))
			{
				Ready = true;
				nextRefresh = now + 2f;
				return;
			}
			Source first = sources.Values.FirstOrDefault((Source s) => !s.Delivered && now - s.Sent >= 2f);
			if (first == null)
			{
				return;
			}
			Source[] array2 = sources.Values.Where((Source s) => !s.Delivered && s.Owner == first.Owner && now - s.Sent >= 2f).Take(16).ToArray();
			ZPackage val2 = operation.Write();
			val2.Write(array2.Length);
			array = array2;
			foreach (Source source2 in array)
			{
				source2.Sent = now;
				if (source2.FirstSent < 0f)
				{
					source2.FirstSent = now;
				}
				val2.Write(source2.Id);
			}
			try
			{
				Transport.Send(first.Owner, "inspect", val2);
			}
			catch (Exception ex)
			{
				Plugin.Debug("Ingredient inspection retry: " + ex.Message);
			}
		}

		internal static void Request(long sender, ZPackage p)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Expected O, but got Unknown
			Operation operation = Operation.Read(p);
			int num = p.ReadInt();
			if (num < 1 || num > 16 || operation.Build || operation.Quote || operation.Peer != sender || !operation.ReadRequirements(out var reason) || !RemoteContext.Actor(operation.Actor, sender, operation.PlayerId, out var _, out reason))
			{
				return;
			}
			RemoteContext remoteContext = new RemoteContext(operation);
			for (int i = 0; i < num; i++)
			{
				ZDOID val = p.ReadZDOID();
				string text = R.Key(val);
				GameObject obj = ZNetScene.instance.FindInstance(val);
				Container val2 = ((obj != null) ? obj.GetComponent<Container>() : null);
				List<Stock> items = new List<Stock>();
				if (remoteContext.OwnerSource(val2, out reason, ownLease: true))
				{
					Inventory inventory = val2.GetInventory();
					ZNetView val3 = R.View((Component)(object)val2);
					if (!Transport.Locked(inventory) && !Integrations.IsBusy(inventory) && !val2.IsInUse() && val3.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 0)
					{
						R.Call(val2, "Load");
					}
					items = Stockroom.Snapshot(inventory, text, operation.Needs, chest: true);
				}
				ZPackage val4 = new ZPackage();
				val4.Write(operation.Id);
				val4.Write(text);
				Wire.Stocks(val4, items);
				Transport.Send(sender, "inspected", val4);
			}
		}

		internal static void Response(long sender, ZPackage p)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			string text = p.ReadString();
			string key = p.ReadString();
			List<Stock> list = Wire.Stocks(p);
			if (operation?.Id != text || !sources.TryGetValue(key, out var value) || value.Owner != sender || value.Delivered)
			{
				return;
			}
			ZDO val = RemoteContext.Data(value.Id);
			if (val != null && val.GetOwner() == sender && !list.Any((Stock s) => s.Source != key || !operation.Needs.Any((Need n) => n.Item == s.Item)))
			{
				Apply(value, list);
				if (sources.Values.All((Source s) => s.Delivered))
				{
					Ready = true;
					nextRefresh = Time.unscaledTime + 2f;
				}
			}
		}

		private static void Apply(Source source, List<Stock> items)
		{
			foreach (Need need in operation.Needs)
			{
				if (!items.Any((Stock s) => s.Item == need.Item))
				{
					items.Add(new Stock(source.Key, need.Item, 1, 0));
				}
			}
			source.Delivered = true;
			Stockroom.Observe(source.Key, items);
		}
	}
	internal static class CraftOverview
	{
		private static readonly StockCatalog<Container> catalog = new StockCatalog<Container>();

		private static InventoryGui gui;

		private static Player player;

		private static CraftingStation station;

		private static Core core;

		private static float nextScan;

		private static bool requestRecords;

		private static bool rowsDirty;

		private static object[] rowQueue;

		private static int rowIndex;

		internal static void Clear()
		{
			catalog.Clear();
			gui = null;
			player = null;
			station = null;
			core = null;
			nextScan = 0f;
			requestRecords = (rowsDirty = false);
			rowQueue = null;
		}

		internal static void Rescan()
		{
			catalog.CancelScan();
			nextScan = 0f;
		}

		internal static void Open(InventoryGui current, Player actor)
		{
			CraftingStation val = (Object.op_Implicit((Object)(object)actor) ? actor.GetCurrentCraftingStation() : null);
			if (!Object.op_Implicit((Object)(object)actor) || !Object.op_Implicit((Object)(object)current) || !Object.op_Implicit((Object)(object)val) || val.m_upgrader || !Plugin.Enabled)
			{
				Clear();
			}
			else if (!((Object)(object)gui == (Object)(object)current) || !((Object)(object)player == (Object)(object)actor) || !((Object)(object)station == (Object)(object)val))
			{
				Clear();
				gui = current;
				player = actor;
				station = val;
				requestRecords = true;
				RecipeIndex.Ensure();
			}
		}

		internal static void Tick()
		{
			if (!Object.op_Implicit((Object)(object)gui))
			{
				return;
			}
			if (!InventoryGui.IsVisible() || !Object.op_Implicit((Object)(object)player) || ((Character)player).IsDead() || (Object)(object)player.GetCurrentCraftingStation() != (Object)(object)station || !Plugin.Enabled)
			{
				Clear();
				return;
			}
			if (!catalog.Running)
			{
				if (Time.unscaledTime < nextScan)
				{
					return;
				}
				Core core = Actions.Context(player, craft: true);
				if ((Object)(object)CraftOverview.core != (Object)(object)core)
				{
					CraftOverview.core = core;
					catalog.Clear();
					requestRecords = true;
					rowsDirty = true;
				}
				if (!Object.op_Implicit((Object)(object)CraftOverview.core))
				{
					nextScan = Time.unscaledTime + 0.5f;
					return;
				}
				CraftOverview.core.Scan();
				catalog.Begin(CraftOverview.core.Pool.ToArray());
			}
			Stopwatch clock = Stopwatch.StartNew();
			int revision = catalog.Revision;
			try
			{
				catalog.Step(Read, 16, () => clock.Elapsed.TotalMilliseconds >= 0.75);
			}
			catch (Exception e)
			{
				catalog.Clear();
				nextScan = Time.unscaledTime + 2f;
				rowsDirty = true;
				Plugin.Error("craft-overview", e);
				return;
			}
			if (!catalog.Running)
			{
				rowsDirty |= revision != catalog.Revision;
				requestRecords = false;
				nextScan = Time.unscaledTime + 1f;
				Plugin.Debug("Craft overview refreshed; containers=" + CraftOverview.core.Pool.Count + "; revision=" + catalog.Revision + "; no reservations acquired");
			}
		}

		private static IEnumerable<Stock> Read(Container container)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)core) || !Object.op_Implicit((Object)(object)player) || !Access.Container(container, player.GetPlayerID(), core, out var _, ownLease: true))
			{
				return Array.Empty<Stock>();
			}
			ZNetView val = R.View((Component)(object)container);
			Inventory inventory = container.GetInventory();
			if (requestRecords && !val.IsOwner())
			{
				ZDOMan.instance.RequestZDO(val.GetZDO().m_uid);
			}
			if (!Transport.Locked(inventory) && !Integrations.IsBusy(inventory) && !container.IsInUse() && val.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 0)
			{
				R.Call(container, "Load");
			}
			return Stockroom.Preview(container);
		}

		internal static List<Stock> Stock(Player actor, IEnumerable<Need> requirements)
		{
			List<Need> list = requirements.ToList();
			List<Stock> list2 = Stockroom.Snapshot(((Humanoid)actor).GetInventory(), "player", list, chest: false);
			if ((Object)(object)actor == (Object)(object)player && Object.op_Implicit((Object)(object)core) && catalog.Ready)
			{
				list2.AddRange(catalog.ForItems(list.Select((Need n) => n.Item)));
			}
			return list2;
		}

		internal static bool Ready(Player actor)
		{
			if ((Object)(object)actor == (Object)(object)player && Object.op_Implicit((Object)(object)core))
			{
				return catalog.Ready;
			}
			return false;
		}

		internal static void Fresh(string key, List<Stock> items)
		{
			if (Object.op_Implicit((Object)(object)core))
			{
				int revision = catalog.Revision;
				catalog.Replace(key, items.Select((Stock s) => s.Item), items);
				rowsDirty |= catalog.Revision != revision;
			}
		}

		internal static void RefreshRows(InventoryGui current)
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Expected O, but got Unknown
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)gui != (Object)(object)current || !Object.op_Implicit((Object)(object)player) || Actions.Active != null)
			{
				return;
			}
			if (rowQueue == null)
			{
				if (!rowsDirty)
				{
					return;
				}
				rowsDirty = false;
				rowQueue = ((IList)R.Get<object>(gui, "m_availableRecipes")).Cast<object>().ToArray();
				rowIndex = 0;
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			int num = 32;
			while (rowIndex < rowQueue.Length && num-- > 0)
			{
				object obj = rowQueue[rowIndex++];
				Type type = obj.GetType();
				Recipe val = (Recipe)type.GetProperty("Recipe").GetValue(obj, null);
				ItemData val2 = (ItemData)type.GetProperty("ItemData").GetValue(obj, null);
				GameObject val3 = (GameObject)type.GetProperty("InterfaceElement").GetValue(obj, null);
				if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val3))
				{
					int num2 = ((val2 == null) ? 1 : (val2.m_quality + 1));
					bool flag = num2 <= val.m_item.m_itemData.m_shared.m_maxQuality && (player.HaveRequirements(val, false, num2, 1) || player.NoCostCheat() || ZoneSystem.instance.GetGlobalKey((GlobalKeys)25));
					Transform obj2 = val3.transform.Find("icon");
					Image val4 = ((obj2 != null) ? ((Component)obj2).GetComponent<Image>() : null);
					Transform obj3 = val3.transform.Find("name");
					TMP_Text val5 = ((obj3 != null) ? ((Component)obj3).GetComponent<TMP_Text>() : null);
					if (Object.op_Implicit((Object)(object)val4))
					{
						((Graphic)val4).color = (Color)(flag ? Color.white : new Color(1f, 0f, 1f, 0f));
					}
					if (Object.op_Implicit((Object)(object)val5))
					{
						((Graphic)val5).color = (Color)(flag ? Color.white : new Color(0.66f, 0.66f, 0.66f, 1f));
					}
					if (stopwatch.Elapsed.TotalMilliseconds >= 0.75)
					{
						break;
					}
				}
			}
			if (rowIndex == rowQueue.Length)
			{
				rowQueue = null;
			}
		}
	}
	internal static class CraftPreparation
	{
		private sealed class Release
		{
			internal float Since;

			internal float Sent;
		}

		private static Actions.Pending desired;

		private static Actions.Pending offer;

		private static Actions.Pending lostClaim;

		private static OfferWindow window;

		private static float nextProbe;

		private static float lastSend;

		private static float nextValidation;

		private static float failureSince = -1f;

		private static string failure;

		private static bool queued;

		private static readonly Dictionary<string, Release> retiring = new Dictionary<string, Release>();

		internal static bool Releasing => retiring.Count > 0;

		internal static bool Claimed
		{
			get
			{
				OfferWindow offerWindow = window;
				if (offerWindow == null || !offerWindow.Claimed)
				{
					return lostClaim != null;
				}
				return true;
			}
		}

		internal static void Clear()
		{
			desired = (offer = (lostClaim = null));
			CraftOverview.Clear();
			CraftInspection.Clear();
			window = null;
			retiring.Clear();
			nextProbe = (lastSend = 0f);
			failureSince = -1f;
			failure = null;
		}

		private static bool Free(Player p)
		{
			if (!p.NoCostCheat())
			{
				return ZoneSystem.instance.GetGlobalKey((GlobalKeys)25);
			}
			return true;
		}

		private static bool Same(Actions.Pending a, Actions.Pending b)
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			if (a != null && b != null && (Object)(object)a.Player == (Object)(object)b.Player && (Object)(object)a.Gui == (Object)(object)b.Gui && (Object)(object)a.Recipe == (Object)(object)b.Recipe && a.Op.Target == b.Op.Target && a.Upgrade == b.Upgrade && a.UpgradeQuality == b.UpgradeQuality && a.Op.Station == b.Op.Station && a.Op.Quality == b.Op.Quality && a.Op.Multiplier == b.Op.Multiplier)
			{
				return a.Variant == b.Variant;
			}
			return false;
		}

		internal static void Selection(InventoryGui gui, Player player)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: 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_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			if (Actions.Active != null)
			{
				return;
			}
			CraftOverview.Open(gui, player);
			if (!Object.op_Implicit((Object)(object)player) || !Object.op_Implicit((Object)(object)gui) || !InventoryGui.IsVisible() || ((Character)player).IsDead() || Free(player))
			{
				Cancel();
				return;
			}
			object obj = R.Get<object>(gui, "m_selectedRecipe");
			Type type = obj.GetType();
			Recipe val = (Recipe)type.GetProperty("Recipe").GetValue(obj, null);
			ItemData val2 = (ItemData)type.GetProperty("ItemData").GetValue(obj, null);
			CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)currentCraftingStation) || currentCraftingStation.m_upgrader || !Plugin.Enabled)
			{
				Cancel();
				return;
			}
			OfferWindow offerWindow = window;
			Actions.Pending pending = ((offerWindow != null && offerWindow.Claimed) ? offer : lostClaim);
			if (pending != null && (Object)(object)pending.Recipe == (Object)(object)val && pending.Upgrade == val2)
			{
				ZDOID station = pending.Op.Station;
				ZNetView obj2 = R.View((Component)(object)currentCraftingStation);
				ZDOID? val3 = ((obj2 == null) ? ((ZDOID?)null) : obj2.GetZDO()?.m_uid);
				if (val3.HasValue && station == val3.GetValueOrDefault())
				{
					return;
				}
			}
			bool flag = val2 == null && (ZInput.GetButton("AltPlace") || ZInput.GetButton("JoyLStick") || R.Get<bool>(gui, "m_touchMultiCrafting"));
			Actions.Pending obj3 = new Actions.Pending
			{
				Player = player,
				Gui = gui,
				Recipe = val,
				Upgrade = val2,
				UpgradeQuality = (val2?.m_quality ?? 0),
				Variant = R.Get<int>(gui, "m_selectedVariant"),
				Multi = flag
			};
			Operation obj4 = new Operation
			{
				Target = RecipeIndex.Key(val),
				Quality = ((val2 == null) ? 1 : (val2.m_quality + 1)),
				Multiplier = ((!flag) ? 1 : gui.m_multiCraftAmount)
			};
			ZNetView obj5 = R.View((Component)(object)currentCraftingStation);
			obj4.Station = (ZDOID)(((??)((obj5 == null) ? ((ZDOID?)null) : obj5.GetZDO()?.m_uid)) ?? ZDOID.None);
			obj3.Op = obj4;
			Actions.Pending b = obj3;
			if (!Same(desired, b))
			{
				Cancel();
				desired = b;
				nextProbe = Time.unscaledTime;
				failureSince = -1f;
			}
		}

		private static bool ContextValid(Actions.Pending p)
		{
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			if (p != null && Object.op_Implicit((Object)(object)p.Player) && (Object)(object)p.Player == (Object)(object)Player.m_localPlayer && !((Character)p.Player).IsDead() && Object.op_Implicit((Object)(object)p.Gui) && InventoryGui.IsVisible())
			{
				CraftingStation currentCraftingStation = p.Player.GetCurrentCraftingStation();
				ZDOID? val = ((currentCraftingStation == null) ? ((ZDOID?)null) : ((Component)currentCraftingStation).GetComponent<ZNetView>().GetZDO()?.m_uid);
				ZDOID station = p.Op.Station;
				if (val.HasValue && (!val.HasValue || val.GetValueOrDefault() == station))
				{
					if (p.Upgrade != null)
					{
						if (((Humanoid)p.Player).GetInventory().ContainsItem(p.Upgrade))
						{
							return p.Upgrade.m_quality == p.UpgradeQuality;
						}
						return false;
					}
					return true;
				}
			}
			return false;
		}

		internal static bool Personal(Player p, Recipe recipe, int quality, int amount)
		{
			List<Need> needs = Stockroom.Requirements(recipe.m_resources, quality, amount);
			List<Stock> stock = Stockroom.Snapshot(((Humanoid)p).GetInventory(), "player", needs, chest: false);
			return Satisfies(recipe, needs, stock);
		}

		private static bool Satisfies(Recipe recipe, List<Need> needs, List<Stock> stock)
		{
			if (recipe.m_requireOnlyOneIngredient)
			{
				return needs.Any(delegate(Need n)
				{
					List<Need> needs2 = new List<Need>
					{
						new Need(n.Item, n.Amount)
					};
					return Stockroom.Qualities(needs2, stock, craft: true) && Planner.Plan(needs2, stock) != null;
				});
			}
			if (Stockroom.Qualities(needs, stock, craft: true))
			{
				return Planner.Plan(needs, stock) != null;
			}
			return false;
		}

		private static bool PersonalShare(Actions.Pending p)
		{
			if (p?.Plan == null || !Object.op_Implicit((Object)(object)p.Player))
			{
				return false;
			}
			List<Stock> stock = Stockroom.Snapshot(((Humanoid)p.Player).GetInventory(), "player", p.Op.Needs, chest: false);
			return Planner.Plan(from d in p.Plan
				where d.Source == "player"
				select new Need(d.Item, d.Amount, d.Quality), stock) != null;
		}

		private static bool Ready()
		{
			if (offer != null && window != null && window.Ready(Time.unscaledTime) && Same(desired, offer) && RecipeIndex.Matches(offer.Recipe, offer.Op.Target) && ContextValid(offer))
			{
				return PersonalShare(offer);
			}
			return false;
		}

		internal static List<Stock> Stock(Player p, IEnumerable<Need> needs, Recipe recipe = null, int quality = 0, int multiplier = 0)
		{
			return CraftOverview.Stock(p, needs);
		}

		internal static List<Stock> IngredientStock(Player p, Recipe recipe, int quality, int amount, List<Need> needs)
		{
			if (Personal(p, recipe, quality, amount))
			{
				return Stockroom.Snapshot(((Humanoid)p).GetInventory(), "player", needs, chest: false);
			}
			if (Ready() && (Object)(object)offer.Player == (Object)(object)p && (Object)(object)offer.Recipe == (Object)(object)recipe && offer.Op.Quality == quality && offer.Op.Multiplier == amount)
			{
				return offer.Plan.Select((Debit d) => new Stock(d.Source, d.Item, d.Quality, d.Amount)).ToList();
			}
			return CraftOverview.Stock(p, needs);
		}

		internal static bool Available(Player p, Recipe r, int quality, int amount)
		{
			List<Need> list = Stockroom.Requirements(r.m_resources, quality, amount);
			return Satisfies(r, list, CraftOverview.Stock(p, list));
		}

		private static bool CanStart(Actions.Pending p)
		{
			if (!Personal(p.Player, p.Recipe, p.Op.Quality, p.Op.Multiplier))
			{
				if (Ready())
				{
					return Same(desired, p);
				}
				return false;
			}
			return true;
		}

		internal static bool Contains(string id, string key)
		{
			if (offer?.Op.Id == id)
			{
				return offer.Op.Sources.Contains(key);
			}
			return false;
		}

		internal static bool Locked(Inventory inv)
		{
			OfferWindow offerWindow = window;
			if (offerWindow != null && offerWindow.Claimed && offer != null && Object.op_Implicit((Object)(object)offer.Player))
			{
				return ((Humanoid)offer.Player).GetInventory() == inv;
			}
			return false;
		}

		internal static bool LockedItem(ItemData item)
		{
			OfferWindow offerWindow = window;
			if (offerWindow != null && offerWindow.Claimed && offer != null && Object.op_Implicit((Object)(object)offer.Player))
			{
				return ((Humanoid)offer.Player).GetInventory().ContainsItem(item);
			}
			return false;
		}

		private static void Send(Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				Plugin.Debug("preflight transport: " + ex.Message);
			}
		}

		internal static void Cancel()
		{
			desired = null;
			lostClaim = null;
			CraftInspection.Clear();
			if (offer != null)
			{
				string id = offer.Op.Id;
				offer = null;
				window?.Cancel();
				window = null;
				retiring[id] = new Release
				{
					Since = Time.unscaledTime,
					Sent = Time.unscaledTime
				};
				Send(delegate
				{
					Transport.Instance.DropQuote(id);
				});
			}
		}

		private static void Retire()
		{
			Actions.Pending pending = desired;
			OfferWindow offerWindow = window;
			Actions.Pending obj = ((offerWindow != null && offerWindow.Claimed) ? offer : null);
			Cancel();
			desired = pending;
			lostClaim = obj;
			nextProbe = Time.unscaledTime + 0.5f;
		}

		internal static void Tick()
		{
			float unscaledTime = Time.unscaledTime;
			KeyValuePair<string, Release>[] array = retiring.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<string, Release> entry = array[i];
				if (unscaledTime - entry.Value.Since > 10f)
				{
					Plugin.Critical(entry.Key, "Unused craft reservation release unconfirmed after 10 seconds; retrying cancellation");
				}
				if (unscaledTime - entry.Value.Sent >= 2f)
				{
					entry.Value.Sent = unscaledTime;
					Send(delegate
					{
						Transport.Instance.DropQuote(entry.Key);
					});
				}
			}
			if (Actions.Waiting != null)
			{
				return;
			}
			if (!ContextValid(desired))
			{
				if (desired != null || offer != null)
				{
					Cancel();
				}
			}
			else
			{
				if (lostClaim != null)
				{
					return;
				}
				Core core = Actions.Context(desired.Player, craft: true);
				if (Object.op_Implicit((Object)(object)core))
				{
					CraftInspection.Ensure(desired, core);
				}
				Player player;
				Core core2;
				if (offer != null)
				{
					if (window.Confirmed && !Ready())
					{
						Retire();
						return;
					}
					if (!window.Confirmed && unscaledTime - offer.Started >= 8f)
					{
						if (!queued)
						{
							Problem("preflight confirmation timeout");
						}
						Retire();
						return;
					}
					if (window.Confirmed && unscaledTime >= nextValidation)
					{
						nextValidation = unscaledTime + 0.5f;
						if (!offer.Op.Validate(out player, out core2, out var _, forceTopology: false))
						{
							Retire();
							return;
						}
					}
					if (window.Claimed && R.Get<float>(offer.Gui, "m_craftTimer") < 0f)
					{
						Retire();
						lostClaim = null;
					}
					else
					{
						if (!(unscaledTime - lastSend >= 1f))
						{
							return;
						}
						lastSend = unscaledTime;
						Actions.Pending current = offer;
						Send(delegate
						{
							if (window.Claimed)
							{
								Transport.Instance.HoldQuote(current.Op.Id);
							}
							else if (!window.Confirmed)
							{
								Transport.Instance.Begin(current.Op);
							}
						});
					}
				}
				else
				{
					if (retiring.Count > 0 || unscaledTime < nextProbe || !CraftOverview.Ready(desired.Player) || !CraftInspection.Ready)
					{
						return;
					}
					nextProbe = unscaledTime + 1f;
					if (!Personal(desired.Player, desired.Recipe, desired.Op.Quality, desired.Op.Multiplier))
					{
						try
						{
							Core core3 = core;
							if (Object.op_Implicit((Object)(object)core3))
							{
								Operation op = Actions.Create(desired.Player, core3, build: false, desired.Op.Target, desired.Op.Quality, desired.Op.Multiplier);
								op.Quote = true;
								if (!op.Validate(out player, out core2, out var reason2, forceTopology: false))
								{
									Problem(reason2);
								}
								else
								{
									List<Stock> stock = CraftOverview.Stock(desired.Player, op.Needs);
									List<Debit> list = (op.SelectNeeds(stock) ? Planner.Plan(op.Needs, stock, compact: true) : null);
									if (list == null)
									{
										failureSince = -1f;
									}
									else
									{
										Actions.Pending pending = new Actions.Pending
										{
											Player = desired.Player,
											Gui = desired.Gui,
											Recipe = desired.Recipe,
											Upgrade = desired.Upgrade,
											UpgradeQuality = desired.UpgradeQuality,
											Variant = desired.Variant,
											Multi = desired.Multi,
											Op = op,
											Started = unscaledTime
										};
										if (!Actions.Propose(pending, list))
										{
											Problem("invalid source proposal");
										}
										else
										{
											offer = pending;
											queued = false;
											window = new OfferWindow();
											lastSend = unscaledTime;
											Send(delegate
											{
												Transport.Instance.Begin(op);
											});
										}
									}
								}
							}
							return;
						}
						catch (Exception ex)
						{
							Problem(ex.Message);
							return;
						}
					}
					failureSince = -1f;
				}
			}
		}

		internal static void Progress(string id, bool inQueue)
		{
			if (offer?.Op.Id == id)
			{
				queued = inQueue;
			}
		}

		internal static void Offered(string id, List<Debit> plan, float remaining)
		{
			if (offer?.Op.Id != id || window == null || window.Claimed || window.Confirmed)
			{
				return;
			}
			if (!ContextValid(offer) || !Same(desired, offer) || !RecipeIndex.Matches(offer.Recipe, offer.Op.Target) || remaining <= 0.5f)
			{
				Retire();
				return;
			}
			List<Need> needs = Stockroom.Requirements(offer.Recipe.m_resources, offer.Op.Quality, offer.Op.Multiplier);
			bool num = !float.IsNaN(remaining) && !float.IsInfinity(remaining) && plan.Count > 0 && plan.All((Debit d) => d.Amount > 0 && d.Quality >= 1 && (d.Source == "player" || offer.Op.Sources.Contains(d.Source)) && needs.Any((Need n) => n.Item == d.Item)) && Satisfies(offer.Recipe, needs, plan.Select((Debit d) => new Stock(d.Source, d.Item, d.Quality, d.Amount)).ToList());
			offer.Plan = plan;
			if (!num || !PersonalShare(offer))
			{
				Problem("preflight plan changed");
				Retire();
				return;
			}
			window.Confirm(Math.Min(offer.Started + 8f, (double)(Time.unscaledTime + remaining) - 0.5));
			if (!window.Ready(Time.unscaledTime))
			{
				Retire();
				return;
			}
			failureSince = -1f;
			failure = null;
		}

		internal static void Refused(string id, string reason)
		{
			if (!retiring.Remove(id) && !(offer?.Op.Id != id))
			{
				CraftInspection.Clear();
				OfferWindow offerWindow = window;
				if (offerWindow != null && offerWindow.Claimed)
				{
					lostClaim = offer;
				}
				offer = null;
				window = null;
				nextProbe = Time.unscaledTime + 1f;
				Topology.Dirty();
				Problem(reason);
			}
		}

		private static void Problem(string reason)
		{
			string[] obj = new string[6] { "preflight: ", reason, " recipe=", null, null, null };
			Actions.Pending pending = desired;
			object obj2;
			if (pending == null)
			{
				obj2 = null;
			}
			else
			{
				Recipe recipe = pending.Recipe;
				obj2 = ((recipe != null) ? ((Object)recipe).name : null);
			}
			if (obj2 == null)
			{
				obj2 = "none";
			}
			obj[3] = (string)obj2;
			obj[4] = " target=";
			obj[5] = desired?.Op.Target ?? "none";
			Plugin.Debug(string.Concat(obj));
			if (reason.StartsWith("recipe ", StringComparison.Ordinal))
			{
				RecipeIndex.Report(desired?.Op.Target, reason);
			}
			if (!(reason == "offer expired") && !(reason == "offer cancelled") && !reason.Contains("insufficient") && !reason.Contains("busy/reserved"))
			{
				switch (reason)
				{
				default:
					if (!reason.Contains("access denied"))
					{
						failure = reason;
						if (failureSince < 0f)
						{
							failureSince = Time.unscaledTime;
						}
						if (Time.unscaledTime - failureSince >= 8f)
						{
							Plugin.Critical("preflight-" + desired?.Op.Target, "Cannot confirm selected recipe after background recovery: " + failure);
						}
						Topology.Dirty();
						return;
					}
					break;
				case "queue timeout":
				case "station unavailable":
				case "recipe unavailable":
					break;
				}
			}
			failureSince = -1f;
		}

		private static bool Capacity(Actions.Pending p)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			string dlc = p.Recipe.m_item.m_itemData.m_shared.m_dlc;
			if (dlc.Length > 0 && !DLCMan.instance.IsDLCInstalled(dlc))
			{
				return false;
			}
			if (p.Upgrade != null)
			{
				return true;
			}
			int num2 = default(int);
			ItemData val = default(ItemData);
			int num = p.Recipe.GetAmount(p.Op.Quality, ref num2, ref val, p.Op.Multiplier);
			CraftingStation currentCraftingStation = p.Player.GetCurrentCraftingStation();
			if (Object.op_Implicit((Object)(object)currentCraftingStation) && (int)currentCraftingStation.m_craftingSkill != 0 && ((Character)p.Player).GetSkillFactor(currentCraftingStation.m_craftingSkill) > 0f && p.Gui.m_craftBonusChance > 0f && p.Recipe.m_item.m_itemData.m_shared.m_maxStackSize > 1)
			{
				num = CraftCapacity.Maximum(num, p.Op.Multiplier, p.Gui.m_craftBonusAmount);
			}
			if (num > 0)
			{
				return ((Humanoid)p.Player).GetInventory().CanAddItem(((Component)p.Recipe.m_item).gameObject, num);
			}
			return false;
		}

		internal static void Button(InventoryGui gui)
		{
			if (Actions.Active == null)
			{
				if (Actions.Waiting != null)
				{
					((Selectable)gui.m_craftButton).interactable = false;
				}
				if ((Object)(object)desired?.Gui == (Object)(object)gui && ((Selectable)gui.m_craftButton).interactable && (!CanStart(desired) || !Capacity(desired)))
				{
					((Selectable)gui.m_craftButton).interactable = false;
				}
				CraftOverview.RefreshRows(gui);
			}
		}

		internal static bool Press(InventoryGui gui)
		{
			if (Actions.Waiting == null)
			{
				OfferWindow offerWindow = window;
				if ((offerWindow == null || !offerWindow.Claimed) && lostClaim == null)
				{
					Selection(gui, Player.m_localPlayer);
					Actions.Pending pending = desired;
					if (pending == null)
					{
						return true;
					}
					if (!((Selectable)gui.m_craftButton).interactable || !Capacity(pending))
					{
						return false;
					}
					if (Personal(pending.Player, pending.Recipe, pending.Op.Quality, pending.Op.Multiplier))
					{
						Cancel();
						return true;
					}
					if (!Ready() || !offer.Op.Validate(out var _, out var _, out var _, forceTopology: false))
					{
						return false;
					}
					if (!window.Claim(Time.unscaledTime))
					{
						return false;
					}
					lastSend = Time.unscaledTime;
					Send(delegate
					{
						Transport.Instance.HoldQuote(offer.Op.Id);
					});
					return true;
				}
			}
			return false;
		}

		internal static void Pressed(InventoryGui gui)
		{
			OfferWindow offerWindow = window;
			if (offerWindow != null && offerWindow.Claimed && R.Get<float>(gui, "m_craftTimer") < 0f)
			{
				Retire();
				lostClaim = null;
			}
		}

		internal static bool Execute(InventoryGui gui, Player player)
		{
			Recipe val = R.Get<Recipe>(gui, "m_craftRecipe");
			ItemData val2 = R.Get<ItemData>(gui, "m_craftUpgradeItem");
			int num = ((val2 == null) ? 1 : (val2.m_quality + 1));
			int num2 = ((!R.Get<bool>(gui, "m_multiCrafting")) ? 1 : gui.m_multiCraftAmount);
			if (offer != null)
			{
				OfferWindow offerWindow = window;
				if (offerWindow != null && offerWindow.Claimed)
				{
					if (!Ready() || (Object)(object)offer.Gui != (Object)(object)gui || (Object)(object)offer.Player != (Object)(object)player || (Object)(object)offer.Recipe != (Object)(object)val || offer.Upgrade != val2 || offer.Op.Quality != num || offer.Op.Multiplier != num2 || !window.Consume(Time.unscaledTime))
					{
						Actions.Pending pending = offer;
						Retire();
						lostClaim = null;
						Actions.RecoverCraft(pending);
						return false;
					}
					Actions.Pending pending2 = offer;
					offer = null;
					window = null;
					pending2.Prepared = true;
					pending2.Started = Time.unscaledTime;
					pending2.LastPoll = pending2.Started;
					pending2.Recovery = new RecoveryAttempt(pending2.Started);
					pending2.Recovery.Sent();
					Actions.Waiting = pending2;
					Send(delegate
					{
						Transport.Instance.UseQuote(pending2.Op.Id);
					});
					return false;
				}
			}
			Actions.Pending pending3 = lostClaim;
			lostClaim = null;
			if (pending3 != null && (Object)(object)pending3.Gui == (Object)(object)gui && (Object)(object)pending3.Player == (Object)(object)player && (Object)(object)pending3.Recipe == (Object)(object)val && pending3.Upgrade == val2)
			{
				Actions.RecoverCraft(pending3);
				return false;
			}
			return Personal(player, val, num, num2);
		}
	}
	internal static class HoverInfo
	{
		private static readonly IncrementalCount<Container> count = new IncrementalCount<Container>();

		private static readonly DisplayAccessCache access = new DisplayAccessCache();

		private static readonly Func<Container, bool> include = Include;

		private static readonly Func<bool> expired = Expired;

		private static NetworkMember target;

		private static Core core;

		private static Player player;

		private static long actor;

		private static long started;

		private static int requestedFrame;

		private static int revision;

		private static float nextCount;

		private static float nextText;

		private static bool local;

		private static string text;

		internal static string Text(NetworkMember node)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)node) || !node.Valid || !Object.op_Implicit((Object)(object)localPlayer))
			{
				return Localization.instance.Localize((node is Relay) ? "$rsn_relay_name" : "$rsn_name");
			}
			long playerID = localPlayer.GetPlayerID();
			if ((Object)(object)target != (Object)(object)node || (Object)(object)player != (Object)(object)localPlayer || actor != playerID)
			{
				Clear();
				target = node;
				player = localPlayer;
				actor = playerID;
				local = node is Relay;
				revision = Topology.DisplayRevision;
				Render();
			}
			requestedFrame = Time.frameCount;
			return text;
		}

		internal static void Clear()
		{
			target = null;
			core = null;
			player = null;
			count.Reset();
			access.Clear();
			text = null;
			nextCount = (nextText = 0f);
		}

		internal static void Tick()
		{
			if (!Object.op_Implicit((Object)(object)target) || !target.Valid || !Object.op_Implicit((Object)(object)player) || (Object)(object)player != (Object)(object)Player.m_localPlayer || requestedFrame < Time.frameCount - 1)
			{
				Clear();
				return;
			}
			try
			{
				float unscaledTime = Time.unscaledTime;
				NetworkGraph networkGraph = Topology.ForActor(actor);
				object obj;
				if (!networkGraph.Nodes.TryGetValue(target.Id, out var value) || !networkGraph.Roots.TryGetValue(value.Network, out var value2))
				{
					obj = null;
				}
				else
				{
					NetworkMember networkMember = Topology.Member(value2);
					obj = ((networkMember != null) ? ((Component)networkMember).GetComponent<Core>() : null);
				}
				Core core = (Core)obj;
				if ((Object)(object)HoverInfo.core != (Object)(object)core || revision != Topology.DisplayRevision)
				{
					HoverInfo.core = core;
					revision = Topology.DisplayRevision;
					count.Reset();
					nextCount = (nextText = 0f);
				}
				if (!count.Running && unscaledTime >= nextCount)
				{
					count.Begin((Object.op_Implicit((Object)(object)HoverInfo.core) && HoverInfo.core.Valid) ? HoverInfo.core.Pool.ToArray() : Array.Empty<Container>());
				}
				if (count.Running)
				{
					access.Clear();
					started = Stopwatch.GetTimestamp();
					count.Step(include, 32, expired);
					if (!count.Running)
					{
						nextCount = unscaledTime + 1f;
						nextText = 0f;
					}
				}
				if (unscaledTime >= nextText)
				{
					Render();
				}
			}
			catch (Exception e)
			{
				count.Rese