Decompiled source of Forge Pipes v1.1.4

plugins/ForgePipes.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyTitle("Forge Pipes")]
[assembly: AssemblyFileVersion("1.1.4.0")]
[assembly: AssemblyInformationalVersion("1.1.4")]
[assembly: AssemblyDescription("Forge Pipes for Valheim")]
[assembly: AssemblyVersion("1.1.4.0")]
namespace ForgePipes;

internal static class AutomationScheduler
{
	private static readonly List<Component> work = new List<Component>();

	private static int cursor;

	private static float nextCycle;

	private static float nextMaintenance;

	private static ZNetScene scene;

	internal static void Tick()
	{
		//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02fa: Expected O, but got Unknown
		//IL_0333: Unknown result type (might be due to invalid IL or missing references)
		//IL_033d: Expected O, but got Unknown
		if ((Object)(object)scene != (Object)(object)ZNetScene.instance)
		{
			scene = ZNetScene.instance;
			work.Clear();
			cursor = 0;
			nextCycle = (nextMaintenance = 0f);
			InventoryWake.Clear();
			Routing.Reset();
		}
		long timestamp = Stopwatch.GetTimestamp();
		try
		{
			if (Time.time >= nextMaintenance)
			{
				nextMaintenance = Time.time + 5f;
				Plugin.Chests.RemoveWhere((Container c) => !Object.op_Implicit((Object)(object)c));
				Plugin.Machines.RemoveWhere((Smelter s) => !Object.op_Implicit((Object)(object)s));
				Stations.All.RemoveWhere((Component s) => !Object.op_Implicit((Object)(object)s));
				Pieces.AttachHammer();
				PortFactory.EnsureAll();
				InventoryWake.Prune();
			}
			if (Plugin.RunConnectionChecks.Value && Routing.Dirty)
			{
				long timestamp2 = Stopwatch.GetTimestamp();
				Routing.Rebuild();
				PerformanceStats.Section("graph", timestamp2);
			}
			if (!Plugin.RunAutomation.Value)
			{
				return;
			}
			if (cursor >= work.Count)
			{
				if (Time.time < nextCycle)
				{
					return;
				}
				nextCycle = Time.time + 1f;
				work.Clear();
				cursor = 0;
				foreach (Smelter machine in Plugin.Machines)
				{
					if (Object.op_Implicit((Object)(object)machine))
					{
						work.Add((Component)(object)machine);
					}
				}
				foreach (Container chest in Plugin.Chests)
				{
					if (Object.op_Implicit((Object)(object)chest))
					{
						work.Add((Component)(object)chest);
					}
				}
				foreach (Component item in Stations.All)
				{
					if (Object.op_Implicit((Object)(object)item) && !(item is Smelter))
					{
						work.Add(item);
					}
				}
				if (Plugin.CollectLooseItems.Value)
				{
					work.Add(null);
				}
			}
			int num = 0;
			double num2 = Math.Max(0.25, Math.Min(8f, Plugin.WorkBudgetMs.Value));
			while (cursor < work.Count && num++ < 8 && (num <= 1 || !((double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency >= num2)))
			{
				Component val = work[cursor++];
				long timestamp3 = Stopwatch.GetTimestamp();
				try
				{
					if (object.ReferenceEquals(val, null))
					{
						LoosePickup.Tick();
						PerformanceStats.Section("pickup", timestamp3);
					}
					else if (val is Smelter)
					{
						Smelter val2 = (Smelter)val;
						if (Object.op_Implicit((Object)(object)val2) && Automation.Supported(val2))
						{
							MachineHandoff.Prepare((Component)(object)val2);
							Automation.Feed(val2);
						}
						PerformanceStats.Section("smelters", timestamp3);
					}
					else if (val is Container)
					{
						ChestTransfers.Tick((Container)val);
						PerformanceStats.Section("chests", timestamp3);
					}
					else if (Object.op_Implicit((Object)(object)val))
					{
						Stations.Tick(val);
						PerformanceStats.Section("stations", timestamp3);
					}
				}
				catch (Exception ex)
				{
					Plugin.Error("Automation job failed: " + ex);
				}
			}
		}
		finally
		{
			PerformanceStats.Tick(timestamp);
		}
	}
}
public struct PipeRun
{
	public int Node;

	public Point A;

	public Point B;

	public PipeRun(int node, Point a, Point b)
	{
		Node = node;
		A = a;
		B = b;
	}
}
public static class BodyConnections
{
	private static Point Sub(Point a, Point b)
	{
		return new Point(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
	}

	private static Point At(Point a, Point delta, float t)
	{
		return new Point(a.X + delta.X * t, a.Y + delta.Y * t, a.Z + delta.Z * t);
	}

	private static float Clamp(float v)
	{
		return Math.Max(0f, Math.Min(1f, v));
	}

	public static float DistanceSquared(PipeRun one, PipeRun two)
	{
		Point point = Sub(one.B, one.A);
		Point point2 = Sub(two.B, two.A);
		Point b = Sub(one.A, two.A);
		float num = Point.Dot(point, point);
		float num2 = Point.Dot(point, point2);
		float num3 = Point.Dot(point2, point2);
		float num4 = Point.Dot(point, b);
		float num5 = Point.Dot(point2, b);
		if (num < 1E-10f && num3 < 1E-10f)
		{
			return Point.DistanceSquared(one.A, two.A);
		}
		float num6 = ((num < 1E-10f) ? 0f : ((num3 < 1E-10f) ? Clamp((0f - num4) / num) : ((num * num3 - num2 * num2 > 1E-10f) ? Clamp((num2 * num5 - num3 * num4) / (num * num3 - num2 * num2)) : 0f)));
		float num7 = ((num3 < 1E-10f) ? 0f : ((num2 * num6 + num5) / num3));
		if (num7 < 0f)
		{
			num7 = 0f;
			num6 = ((num < 1E-10f) ? 0f : Clamp((0f - num4) / num));
		}
		else if (num7 > 1f)
		{
			num7 = 1f;
			num6 = ((num < 1E-10f) ? 0f : Clamp((num2 - num4) / num));
		}
		return Point.DistanceSquared(At(one.A, point, num6), At(two.A, point2, num7));
	}

	public static void Connect(NetworkGraph graph, IEnumerable<PipeRun> runs)
	{
		Dictionary<NetworkGraph.Cell, List<PipeRun>> dictionary = new Dictionary<NetworkGraph.Cell, List<PipeRun>>();
		foreach (PipeRun run in runs)
		{
			Point point = Sub(run.B, run.A);
			int num = Math.Max(1, (int)Math.Ceiling(Math.Sqrt(Point.Dot(point, point)) / 0.4));
			for (int i = 0; i < num; i++)
			{
				PipeRun pipeRun = new PipeRun(run.Node, At(run.A, point, (float)i / (float)num), At(run.A, point, (float)(i + 1) / (float)num));
				Point point2 = At(pipeRun.A, Sub(pipeRun.B, pipeRun.A), 0.5f);
				NetworkGraph.Cell key = new NetworkGraph.Cell
				{
					X = (int)Math.Floor(point2.X / 0.5f),
					Y = (int)Math.Floor(point2.Y / 0.5f),
					Z = (int)Math.Floor(point2.Z / 0.5f)
				};
				for (int j = -1; j <= 1; j++)
				{
					for (int k = -1; k <= 1; k++)
					{
						for (int l = -1; l <= 1; l++)
						{
							if (!dictionary.TryGetValue(new NetworkGraph.Cell
							{
								X = key.X + j,
								Y = key.Y + k,
								Z = key.Z + l
							}, out var value))
							{
								continue;
							}
							foreach (PipeRun item in value)
							{
								if (item.Node != pipeRun.Node && !graph.Connected(item.Node, pipeRun.Node) && DistanceSquared(item, pipeRun) <= 0.0064f)
								{
									graph.Join(item.Node, pipeRun.Node);
								}
							}
						}
					}
				}
				if (!dictionary.TryGetValue(key, out var value2))
				{
					value2 = new List<PipeRun>();
					dictionary.Add(key, value2);
				}
				value2.Add(pipeRun);
			}
		}
	}
}
[HarmonyPatch(typeof(PieceTable), "UpdateAvailable")]
internal static class BuildMenuPatch
{
	private static readonly MethodInfo AddKnown = AccessTools.Method(typeof(Player), "AddKnownPiece", (Type[])null, (Type[])null);

	private static Player lastPlayer;

	private static string lastReport;

	private static bool IsHammer(PieceTable table)
	{
		if (Object.op_Implicit((Object)(object)ObjectDB.instance))
		{
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Hammer");
			if (Object.op_Implicit((Object)(object)itemPrefab) && (Object)(object)itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_buildPieces == (Object)(object)table)
			{
				return true;
			}
		}
		return table.m_pieces.Any((GameObject p) => Object.op_Implicit((Object)(object)p) && Native.Name(p) == "wood_beam");
	}

	private static void Prefix(PieceTable __instance, HashSet<string> __0, Player __1)
	{
		if (!Object.op_Implicit((Object)(object)__1) || (Object)(object)__1 != (Object)(object)Player.m_localPlayer || !IsHammer(__instance))
		{
			return;
		}
		GameObject val = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.GetItemPrefab("Copper") : null);
		ItemDrop val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<ItemDrop>() : null);
		bool flag = Object.op_Implicit((Object)(object)val2) && __1.IsKnownMaterial(val2.m_itemData.m_shared.m_name);
		int num = 0;
		foreach (GameObject prefab in Plugin.Prefabs)
		{
			if (!__instance.m_pieces.Contains(prefab))
			{
				__instance.m_pieces.Add(prefab);
			}
			Piece component = prefab.GetComponent<Piece>();
			if (!flag)
			{
				__0.Remove(component.m_name);
				continue;
			}
			if (!__0.Contains(component.m_name) && __1.HaveRequirements(component, (RequirementMode)1))
			{
				if (Object.op_Implicit((Object)(object)Hud.instance) && Object.op_Implicit((Object)(object)Hud.instance.m_buildUi) && Object.op_Implicit((Object)(object)MessageHud.instance))
				{
					AddKnown.Invoke(__1, new object[1] { component });
				}
				else
				{
					__0.Add(component.m_name);
				}
			}
			if (__0.Contains(component.m_name))
			{
				num++;
			}
		}
		string text = "Hammer registration: " + Plugin.Prefabs.Count + " pipes; " + num + " recipes unlocked. Find them under Crafting / Transport or search 'Forge pipe'.";
		if (num < Plugin.Prefabs.Count)
		{
			text += " Discover Wood, a Copper ingot, and a workbench to unlock the recipes.";
		}
		if ((Object)(object)lastPlayer != (Object)(object)__1 || lastReport != text)
		{
			Plugin.LogInfo(text);
			lastPlayer = __1;
			lastReport = text;
		}
	}
}
public enum ChestFlow
{
	Both,
	SendOnly,
	ReceiveOnly
}
public static class ChestFlowRules
{
	public static ChestFlow Read(int value)
	{
		if (value < 0 || value > 2)
		{
			return ChestFlow.Both;
		}
		return (ChestFlow)value;
	}

	public static ChestFlow Next(ChestFlow mode)
	{
		return (ChestFlow)((int)(mode + 1) % 3);
	}

	public static bool Sends(ChestFlow mode)
	{
		return mode != ChestFlow.ReceiveOnly;
	}

	public static bool Receives(ChestFlow mode)
	{
		return mode != ChestFlow.SendOnly;
	}

	public static bool Transfer(ChestFlow source, ChestFlow destination)
	{
		if (source != ChestFlow.SendOnly || !Receives(destination))
		{
			if (source == ChestFlow.Both)
			{
				return destination == ChestFlow.ReceiveOnly;
			}
			return false;
		}
		return true;
	}

	public static string Label(ChestFlow mode)
	{
		return mode switch
		{
			ChestFlow.ReceiveOnly => "Receive only (input)", 
			ChestFlow.SendOnly => "Send only (output)", 
			_ => "Both (input + output)", 
		};
	}
}
public sealed class ChestPreviewVisibility : MonoBehaviour
{
	private LODGroup[] lods;

	private void Awake()
	{
		lods = ((Component)this).GetComponentsInChildren<LODGroup>(true);
		Renderer[] componentsInChildren = ((Component)this).GetComponentsInChildren<Renderer>(true);
		foreach (Renderer val in componentsInChildren)
		{
			if (!(val is LineRenderer))
			{
				val.allowOcclusionWhenDynamic = false;
			}
		}
		Refresh();
	}

	private void LateUpdate()
	{
		Refresh();
	}

	private void Refresh()
	{
		if (lods == null)
		{
			return;
		}
		LODGroup[] array = lods;
		foreach (LODGroup val in array)
		{
			if (Object.op_Implicit((Object)(object)val))
			{
				val.RecalculateBounds();
				val.ForceLOD(0);
			}
		}
	}
}
internal static class ChestSettings
{
	private const string ModeKey = "FP_chest_mode_v1";

	private static readonly FieldInfo Areas = AccessTools.Field(typeof(PrivateArea), "m_allAreas");

	private static readonly MethodInfo Enabled = AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null);

	private static readonly MethodInfo Inside = AccessTools.Method(typeof(PrivateArea), "IsInside", (Type[])null, (Type[])null);

	private static readonly MethodInfo Permitted = AccessTools.Method(typeof(PrivateArea), "IsPermitted", (Type[])null, (Type[])null);

	internal static ChestFlow Mode(Container chest)
	{
		if (!Hopper.Is(chest))
		{
			if (!Native.Valid((Component)(object)chest))
			{
				return ChestFlow.Both;
			}
			return ChestFlowRules.Read(Native.View((Component)(object)chest).GetZDO().GetInt("FP_chest_mode_v1", 0));
		}
		return ChestFlow.SendOnly;
	}

	internal static void SetFromPeer(Container chest, long sender, ZDOID playerId, int value)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		if (Hopper.Is(chest) || !Native.Owner((Component)(object)chest) || !ChestHandoff.Eligible(chest) || ChestHandoff.Busy(chest) || value < 0 || value > 2)
		{
			return;
		}
		GameObject val = ZNetScene.instance.FindInstance(playerId);
		Player val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<Player>() : null);
		if (!Object.op_Implicit((Object)(object)val2) || !Native.Valid((Component)(object)val2) || Native.View((Component)(object)val2).GetZDO().GetOwner() != sender || Vector3.Distance(((Component)val2).transform.position, ((Component)chest).transform.position) > val2.m_maxInteractDistance + 1f)
		{
			return;
		}
		long playerID = val2.GetPlayerID();
		foreach (PrivateArea item in (List<PrivateArea>)Areas.GetValue(null))
		{
			if (Object.op_Implicit((Object)(object)item) && (bool)Enabled.Invoke(item, null) && (bool)Inside.Invoke(item, new object[2]
			{
				((Component)chest).transform.position,
				0f
			}))
			{
				Piece component = ((Component)item).GetComponent<Piece>();
				if ((!Object.op_Implicit((Object)(object)component) || component.GetCreator() != playerID) && !(bool)Permitted.Invoke(item, new object[1] { playerID }))
				{
					return;
				}
			}
		}
		Native.View((Component)(object)chest).GetZDO().Set("FP_chest_mode_v1", value);
		Routing.Dirty = true;
	}
}
[HarmonyPatch(typeof(Container), "GetHoverText")]
internal static class ChestModeHoverPatch
{
	private static void Postfix(Container __instance, ref string __result)
	{
		if (!Hopper.Is(__instance) && ChestHandoff.Eligible(__instance) && ChestHandoff.Access((Component)(object)__instance))
		{
			__result = __result + "\n<color=#dca06b>Forge Pipes: " + PipeLanguage.Text(ChestFlowRules.Label(ChestSettings.Mode(__instance))) + "</color>";
			__result = __result + Localization.instance.Localize("\n[<color=yellow>$KEY_AltPlace + $KEY_Use</color>] ") + PipeLanguage.Text("Change pipe mode");
		}
	}
}
[HarmonyPatch(typeof(Container), "Interact")]
internal static class ChestModeInteractPatch
{
	private static bool Prefix(Container __instance, Humanoid __0, bool __1, bool __2, ref bool __result)
	{
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		if (Hopper.Is(__instance) || !__2 || (Object)(object)__0 != (Object)(object)Player.m_localPlayer || !ChestHandoff.Eligible(__instance))
		{
			return true;
		}
		__result = true;
		if (__1)
		{
			return false;
		}
		if (!ChestHandoff.Access((Component)(object)__instance))
		{
			return false;
		}
		if (ChestHandoff.Busy(__instance))
		{
			((Character)__0).Message((MessageType)2, PipeLanguage.Text("Close the chest before changing its pipe mode."), 0, (Sprite)null, false);
			return false;
		}
		ChestFlow chestFlow = ChestFlowRules.Next(ChestSettings.Mode(__instance));
		Native.View((Component)(object)__instance).InvokeRPC("FP_ChestMode", new object[2]
		{
			Native.View((Component)(object)__0).GetZDO().m_uid,
			(int)chestFlow
		});
		((Character)__0).Message((MessageType)2, PipeLanguage.Text("Requested pipe mode: ") + PipeLanguage.Text(ChestFlowRules.Label(chestFlow)), 0, (Sprite)null, false);
		return false;
	}
}
internal static class ChestTransfers
{
	internal static void Tick(Container source)
	{
		if (!ChestHandoff.Eligible(source) || !Native.Owner((Component)(object)source))
		{
			return;
		}
		List<Container> list = Routing.ConnectedChests(source);
		if (!InventoryWake.ShouldRun(source, list))
		{
			return;
		}
		if (OpenChestAccess.Viewing(source))
		{
			foreach (Container item in list)
			{
				if (OpenChestRules.MayPull(Native.Owner((Component)(object)source), targetViewed: true, ChestHandoff.Busy(item)) && ChestFlowRules.Transfer(ChestSettings.Mode(item), ChestSettings.Mode(source)))
				{
					ChestHandoff component = ((Component)item).GetComponent<ChestHandoff>();
					if (Object.op_Implicit((Object)(object)component))
					{
						component.Ready((Component)(object)source);
					}
				}
			}
		}
		if (!ChestFlowRules.Sends(ChestSettings.Mode(source)))
		{
			return;
		}
		ChestHandoff component2 = ((Component)source).GetComponent<ChestHandoff>();
		if (!Object.op_Implicit((Object)(object)component2) || !component2.Ready((Component)(object)source))
		{
			return;
		}
		int num = 10;
		try
		{
			foreach (Container item2 in list)
			{
				if (!ChestFlowRules.Transfer(ChestSettings.Mode(source), ChestSettings.Mode(item2)))
				{
					continue;
				}
				ChestHandoff component3 = ((Component)item2).GetComponent<ChestHandoff>();
				if (!Object.op_Implicit((Object)(object)component3) || !component3.Ready((Component)(object)source))
				{
					continue;
				}
				Inventory inventory = source.GetInventory();
				Inventory inventory2 = item2.GetInventory();
				ItemData[] array = inventory.GetAllItems().ToArray();
				foreach (ItemData val in array)
				{
					if (OpenChestAccess.Reserved(inventory, val) || !Hopper.Allows(source, item2, val))
					{
						continue;
					}
					while (num > 0 && inventory.ContainsItem(val) && StockReserve.Available(inventory, val) > 0 && OpenChestAccess.CanEdit(source) && OpenChestAccess.CanEdit(item2) && ChestFlowRules.Transfer(ChestSettings.Mode(source), ChestSettings.Mode(item2)))
					{
						bool flag = false;
						for (int j = 0; j < inventory2.GetHeight(); j++)
						{
							if (flag)
							{
								break;
							}
							for (int k = 0; k < inventory2.GetWidth(); k++)
							{
								if (flag)
								{
									break;
								}
								ItemData itemAt = inventory2.GetItemAt(k, j);
								if (itemAt == null || !OpenChestAccess.Reserved(inventory2, itemAt))
								{
									int stack = val.m_stack;
									inventory2.MoveItemToThis(inventory, val, 1, k, j);
									flag = val.m_stack < stack;
								}
							}
						}
						if (!flag)
						{
							break;
						}
						num--;
					}
					if (num == 0)
					{
						break;
					}
				}
				if (num == 0)
				{
					break;
				}
			}
		}
		finally
		{
			InventoryWake.Completed(source, list, num < 10);
		}
	}
}
internal static class CookingCredit
{
	internal sealed class Snapshot
	{
		internal Container Source;

		internal Inventory From;

		internal Inventory To;

		internal Dictionary<string, int> Before;

		internal Dictionary<string, int> Destination;

		internal bool Completed;
	}

	private static readonly Dictionary<Inventory, Container> containers = new Dictionary<Inventory, Container>();

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

	private static readonly HashSet<Inventory> credited = new HashSet<Inventory>();

	internal static int Moving;

	internal static void Clear()
	{
		containers.Clear();
		observed.Clear();
		credited.Clear();
		Moving = 0;
	}

	internal static void Register(Container c)
	{
		if (Object.op_Implicit((Object)(object)c) && c.GetInventory() != null)
		{
			containers[c.GetInventory()] = c;
			Refresh(c);
		}
	}

	internal static void Forget(Container c)
	{
		if (Object.op_Implicit((Object)(object)c) && c.GetInventory() != null)
		{
			containers.Remove(c.GetInventory());
			observed.Remove(c.GetInventory());
			credited.Remove(c.GetInventory());
		}
	}

	internal static void Refresh(Container c)
	{
		if (Object.op_Implicit((Object)(object)c) && Native.Owner((Component)(object)c) && Native.View((Component)(object)c).GetZDO().GetBool("FP_has_cook_credit", false))
		{
			credited.Add(c.GetInventory());
			observed[c.GetInventory()] = Counts(c.GetInventory());
		}
	}

	internal static void Observe(Inventory inventory)
	{
		if (Moving > 0 || inventory == null || !credited.Contains(inventory))
		{
			return;
		}
		Container val = Find(inventory);
		if (Object.op_Implicit((Object)(object)val) && Native.Owner((Component)(object)val))
		{
			if (observed.TryGetValue(inventory, out var value))
			{
				Snapshot snapshot = new Snapshot();
				snapshot.Source = val;
				snapshot.From = inventory;
				snapshot.Before = value;
				Finish(snapshot);
			}
			else
			{
				Refresh(val);
			}
		}
	}

	private static string Identity(ItemData i)
	{
		return (Object.op_Implicit((Object)(object)i.m_dropPrefab) ? ((Object)i.m_dropPrefab).name : i.m_shared.m_name) + ":" + i.m_quality + ":" + i.m_worldLevel;
	}

	private static Dictionary<string, int> Counts(Inventory inv)
	{
		return inv.GetAllItems().GroupBy(Identity).ToDictionary((IGrouping<string, ItemData> g) => g.Key, (IGrouping<string, ItemData> g) => g.Sum((ItemData i) => i.m_stack));
	}

	private static int Count(Dictionary<string, int> counts, string key)
	{
		if (!counts.TryGetValue(key, out var value))
		{
			return 0;
		}
		return value;
	}

	private static Container Find(Inventory inv)
	{
		if (inv == null || !containers.TryGetValue(inv, out var value) || !Object.op_Implicit((Object)(object)value))
		{
			return null;
		}
		return value;
	}

	internal static void Add(Container c, ItemData item, int amount, SkillType skill)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Invalid comparison between Unknown and I4
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Expected I4, but got Unknown
		if ((int)skill != 0 && amount > 0 && Native.Owner((Component)(object)c) && !(((int)skill == -1) ? (!Plugin.HoneyAchievement.Value) : (!Plugin.CookingCollectionXP.Value)))
		{
			Register(c);
			Add(c, Identity(item), amount, (int)skill);
		}
	}

	private static void Add(Container c, string id, int amount, int skill)
	{
		ZDO zDO = Native.View((Component)(object)c).GetZDO();
		string text = "FP_cook_credit_" + id;
		zDO.Set(text, Math.Max(0, zDO.GetInt(text, 0)) + amount);
		zDO.Set(text + "_skill", skill);
		zDO.Set("FP_has_cook_credit", true);
		credited.Add(c.GetInventory());
		Refresh(c);
	}

	internal static Snapshot Capture(Inventory from, Inventory to = null)
	{
		Container val = Find(from);
		if (!Object.op_Implicit((Object)(object)val) || !Native.Owner((Component)(object)val) || !Native.View((Component)(object)val).GetZDO().GetBool("FP_has_cook_credit", false))
		{
			return null;
		}
		Snapshot snapshot = new Snapshot();
		snapshot.Source = val;
		snapshot.From = from;
		snapshot.To = to;
		snapshot.Before = Counts(from);
		snapshot.Destination = ((to == null) ? null : Counts(to));
		return snapshot;
	}

	internal static void Finish(Snapshot state)
	{
		if (state == null || state.Completed || !Object.op_Implicit((Object)(object)state.Source) || !Native.Owner((Component)(object)state.Source))
		{
			return;
		}
		state.Completed = true;
		Dictionary<string, int> dictionary = Counts(state.From);
		Dictionary<string, int> dictionary2 = ((state.To == null) ? null : Counts(state.To));
		Container val = Find(state.To);
		Player localPlayer = Player.m_localPlayer;
		bool flag = Object.op_Implicit((Object)(object)localPlayer) && state.To == ((Humanoid)localPlayer).GetInventory() && OpenChestAccess.Viewing(state.Source) && ChestHandoff.Access((Component)(object)state.Source);
		ZDO z = Native.View((Component)(object)state.Source).GetZDO();
		foreach (KeyValuePair<string, int> item in state.Before)
		{
			int num = Math.Max(0, item.Value - Count(dictionary, item.Key));
			if (num == 0)
			{
				continue;
			}
			string text = "FP_cook_credit_" + item.Key;
			int num2 = Math.Min(item.Value, Math.Max(0, z.GetInt(text, 0)));
			int num3 = Math.Min(num, num2);
			if (num3 == 0)
			{
				continue;
			}
			int num4 = z.GetInt(text + "_skill", 0);
			z.Set(text, num2 - num3);
			int num5 = ((dictionary2 != null) ? Math.Min(num3, Math.Max(0, Count(dictionary2, item.Key) - Count(state.Destination, item.Key))) : 0);
			if (num5 == 0 || num4 == 0)
			{
				continue;
			}
			if (flag)
			{
				if (num4 == -1)
				{
					if (Plugin.HoneyAchievement.Value && Object.op_Implicit((Object)(object)Game.instance))
					{
						Game.instance.IncrementPlayerStat((PlayerStatType)76, (float)num5, false);
					}
				}
				else if (Plugin.CookingCollectionXP.Value)
				{
					for (int i = 0; i < num5; i++)
					{
						((Character)localPlayer).RaiseSkill((SkillType)num4, 0.6f);
					}
				}
			}
			else if (Object.op_Implicit((Object)(object)val) && Native.Owner((Component)(object)val))
			{
				Add(val, item.Key, num5, num4);
			}
		}
		bool flag2 = dictionary.Any((KeyValuePair<string, int> entry) => entry.Value > 0 && z.GetInt("FP_cook_credit_" + entry.Key, 0) > 0);
		z.Set("FP_has_cook_credit", flag2);
		if (flag2)
		{
			Refresh(state.Source);
		}
		else
		{
			credited.Remove(state.Source.GetInventory());
			observed.Remove(state.Source.GetInventory());
		}
		if (Object.op_Implicit((Object)(object)val))
		{
			Refresh(val);
		}
	}
}
[HarmonyPatch(typeof(Inventory), "Changed")]
internal static class CookingCreditChangedPatch
{
	private static void Postfix(Inventory __instance)
	{
		CookingCredit.Observe(__instance);
	}
}
[HarmonyPatch(typeof(Container), "Load")]
internal static class CookingCreditLoadPatch
{
	private static void Prefix()
	{
		CookingCredit.Moving++;
	}

	private static void Finalizer(Container __instance)
	{
		try
		{
			CookingCredit.Refresh(__instance);
		}
		finally
		{
			CookingCredit.Moving = Math.Max(0, CookingCredit.Moving - 1);
		}
	}
}
[HarmonyPatch]
internal static class CookingCreditMovePatch
{
	private static IEnumerable<MethodBase> TargetMethods()
	{
		return (from m in typeof(Inventory).GetMethods()
			where m.Name == "MoveItemToThis" || m.Name == "MoveAll" || m.Name == "StackAll"
			select m).Cast<MethodBase>();
	}

	private static void Prefix(Inventory __instance, Inventory __0, out CookingCredit.Snapshot __state)
	{
		__state = ((CookingCredit.Moving++ == 0) ? CookingCredit.Capture(__0, __instance) : null);
	}

	private static void Finalizer(CookingCredit.Snapshot __state)
	{
		try
		{
			CookingCredit.Finish(__state);
		}
		finally
		{
			CookingCredit.Moving = Math.Max(0, CookingCredit.Moving - 1);
		}
	}
}
[HarmonyPatch]
internal static class CookingCreditRemovePatch
{
	private static IEnumerable<MethodBase> TargetMethods()
	{
		return (from m in typeof(Inventory).GetMethods()
			where m.Name == "RemoveItem" || m.Name == "RemoveOneItem" || m.Name == "RemoveAll"
			select m).Cast<MethodBase>();
	}

	private static void Prefix(Inventory __instance, out CookingCredit.Snapshot __state)
	{
		__state = ((CookingCredit.Moving++ == 0) ? CookingCredit.Capture(__instance) : null);
	}

	private static void Finalizer(CookingCredit.Snapshot __state)
	{
		try
		{
			CookingCredit.Finish(__state);
		}
		finally
		{
			CookingCredit.Moving = Math.Max(0, CookingCredit.Moving - 1);
		}
	}
}
public static class CopperGrain
{
	public const int Size = 256;

	private static int Wrap(int n, int size)
	{
		return (n % size + size) % size;
	}

	private static float Noise(int x, int y, int size)
	{
		uint num = (uint)(Wrap(x, size) * 374761393 + Wrap(y, size) * 668265263 + 9021);
		num = (num ^ (num >> 13)) * 1274126177;
		num ^= num >> 16;
		return (float)(num & 0xFFFF) / 65535f;
	}

	private static float Broad(int x, int y, int cell)
	{
		x = Wrap(x, 256);
		y = Wrap(y, 256);
		int num = x / cell;
		int num2 = y / cell;
		float num3 = (float)(x % cell) / (float)cell;
		float num4 = (float)(y % cell) / (float)cell;
		num3 = num3 * num3 * (3f - 2f * num3);
		num4 = num4 * num4 * (3f - 2f * num4);
		float num5 = Noise(num, num2, 256 / cell) * (1f - num3) + Noise(num + 1, num2, 256 / cell) * num3;
		float num6 = Noise(num, num2 + 1, 256 / cell) * (1f - num3) + Noise(num + 1, num2 + 1, 256 / cell) * num3;
		return num5 * (1f - num4) + num6 * num4;
	}

	public static float Height(int x, int y)
	{
		return 0.4f * Noise(x, y, 256) + 0.36f * Broad(x, y, 4) + 0.24f * Broad(x, y, 32);
	}

	public static float Tone(int x, int y)
	{
		return 0.63f + 0.43f * Height(x, y) + 0.12f * Broad(x, y, 32);
	}
}
internal static class CopperMaterial
{
	internal const int AtlasTileSize = 256;

	internal const int AtlasTiles = 4;

	internal const int AtlasGutter = 32;

	private const int AtlasMipCount = 6;

	private static Material copper;

	private static Material collar;

	private static Material pipe;

	internal static Material Get(Material fallback, bool isCollar)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)copper))
		{
			copper = Create(atlas: false);
			collar = new Material(copper);
			((Object)collar).name = "ForgePipes dark copper collars";
			collar.color = new Color(0.87f, 0.83f, 0.78f);
		}
		if (!isCollar)
		{
			return copper;
		}
		return collar;
	}

	internal static Material GetPipe()
	{
		if (!Object.op_Implicit((Object)(object)pipe))
		{
			return pipe = Create(atlas: true);
		}
		return pipe;
	}

	internal static float AtlasU(float u, int surface)
	{
		int num = Mathf.Clamp(surface, 0, 2);
		float num2 = 191f;
		return ((float)(num * 256 + 32) + 0.5f + Mathf.Clamp01(u) * num2) / 1024f;
	}

	private static Material Create(bool atlas)
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Expected O, but got Unknown
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_0167: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0151: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0204: Unknown result type (might be due to invalid IL or missing references)
		//IL_0209: Unknown result type (might be due to invalid IL or missing references)
		//IL_020d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0212: Unknown result type (might be due to invalid IL or missing references)
		//IL_024d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0252: Unknown result type (might be due to invalid IL or missing references)
		//IL_0294: Unknown result type (might be due to invalid IL or missing references)
		//IL_0299: Unknown result type (might be due to invalid IL or missing references)
		Shader val = Shader.Find("Standard") ?? Shader.Find("Legacy Shaders/Diffuse") ?? Shader.Find("Unlit/Texture");
		if (!Object.op_Implicit((Object)(object)val))
		{
			throw new InvalidOperationException("No non-deforming copper shader is available.");
		}
		Material val2 = new Material(val);
		((Object)val2).name = (atlas ? "ForgePipes shared copper atlas" : "ForgePipes rough copper");
		val2.color = Color.white;
		bool value = Plugin.MatteCopper.Value;
		bool flag = !value && val2.HasProperty("_BumpMap");
		bool flag2 = !value && val2.HasProperty("_MetallicGlossMap");
		int num = 256;
		int num2 = (atlas ? 1024 : num);
		Color[] array = (Color[])(object)new Color[num2 * num];
		Color[] array2 = (Color[])(object)(flag ? new Color[array.Length] : null);
		Color[] array3 = (Color[])(object)(flag2 ? new Color[array.Length] : null);
		for (int i = 0; i < num; i++)
		{
			for (int j = 0; j < num2; j++)
			{
				int num3 = i * num2 + j;
				int num4 = (atlas ? (j / 256) : 0);
				int num5 = (atlas ? ((int)Mathf.Floor((float)(j % 256 - 32) * 256f / 191f)) : j);
				float num6 = CopperGrain.Tone(num5, i);
				Color val3 = (Color)(num4 switch
				{
					2 => new Color(0.22f, 0.2f, 0.18f), 
					1 => new Color(0.76f, 0.72f, 0.68f), 
					_ => Color.white, 
				});
				ref Color reference = ref array[num3];
				reference = new Color(0.78f * num6 * val3.r, 0.405f * num6 * val3.g, 0.225f * num6 * val3.b, 1f);
				if (array2 != null)
				{
					float num7 = (CopperGrain.Height(num5 - 1, i) - CopperGrain.Height(num5 + 1, i)) * 0.7f;
					float num8 = (CopperGrain.Height(num5, i - 1) - CopperGrain.Height(num5, i + 1)) * 0.7f;
					Vector3 val4 = new Vector3(num7, num8, 1f);
					Vector3 normalized = ((Vector3)(ref val4)).normalized;
					ref Color reference2 = ref array2[num3];
					reference2 = new Color(1f, normalized.y * 0.5f + 0.5f, 1f, normalized.x * 0.5f + 0.5f);
				}
				if (array3 != null)
				{
					ref Color reference3 = ref array3[num3];
					reference3 = new Color((num4 == 2) ? 0.1f : 0.55f, 0f, 0f, 0.18f + CopperGrain.Height(num5, i) * 0.18f);
				}
			}
		}
		val2.mainTexture = (Texture)(object)Texture(num2, num, linear: false, atlas, array, "grainy copper albedo");
		if (array2 != null)
		{
			val2.SetTexture("_BumpMap", (Texture)(object)Texture(num2, num, linear: true, atlas, array2, "copper grain normals"));
			val2.SetFloat("_BumpScale", 0.65f);
			val2.EnableKeyword("_NORMALMAP");
		}
		if (array3 != null)
		{
			val2.SetTexture("_MetallicGlossMap", (Texture)(object)Texture(num2, num, linear: true, atlas, array3, "copper roughness"));
			val2.EnableKeyword("_METALLICGLOSSMAP");
		}
		if (val2.HasProperty("_Metallic"))
		{
			val2.SetFloat("_Metallic", value ? 0f : 0.55f);
		}
		if (val2.HasProperty("_Glossiness"))
		{
			val2.SetFloat("_Glossiness", value ? 0.08f : 0.26f);
		}
		val2.enableInstancing = true;
		return val2;
	}

	private static Texture2D Texture(int width, int height, bool linear, bool atlas, Color[] pixels, string name)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		Texture2D val = new Texture2D(width, height, (TextureFormat)4, atlas ? 6 : (-1), linear);
		Finish(val, pixels, name + (atlas ? " atlas" : ""));
		if (atlas)
		{
			((Texture)val).wrapModeU = (TextureWrapMode)1;
		}
		return val;
	}

	private static void Finish(Texture2D texture, Color[] pixels, string name)
	{
		((Object)texture).name = name;
		((Texture)texture).wrapMode = (TextureWrapMode)0;
		((Texture)texture).filterMode = (FilterMode)2;
		((Texture)texture).anisoLevel = 4;
		texture.SetPixels(pixels);
		texture.Apply(true, true);
	}
}
internal static class CraftingInterop
{
	internal static bool CanYield(Container chest)
	{
		MonoBehaviour val = null;
		MonoBehaviour[] components = ((Component)chest).GetComponents<MonoBehaviour>();
		foreach (MonoBehaviour val2 in components)
		{
			if (Object.op_Implicit((Object)(object)val2) && ((object)val2).GetType().FullName == "ForgeChestCrafting.CraftChestLease")
			{
				val = val2;
				break;
			}
		}
		if (!Object.op_Implicit((Object)(object)val))
		{
			return true;
		}
		MethodInfo methodInfo = AccessTools.Method(((object)val).GetType(), "CanYieldToPipes", (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			return true;
		}
		try
		{
			return (bool)methodInfo.Invoke(val, null);
		}
		catch (Exception ex)
		{
			Plugin.Error("Crafting lease check failed: " + ex);
			return false;
		}
	}
}
internal static class EitrDamageRules
{
	internal static bool IsSpark(bool hasAttacker, DamageTypes damage)
	{
		if (!hasAttacker && damage.m_lightning > 0f && damage.m_poison > 0f && damage.m_damage <= 0f && damage.m_blunt <= 0f && damage.m_slash <= 0f && damage.m_pierce <= 0f && damage.m_chop <= 0f && damage.m_pickaxe <= 0f && damage.m_fire <= 0f && damage.m_frost <= 0f && damage.m_spirit <= 0f)
		{
			return damage.m_nonPlayer <= 0f;
		}
		return false;
	}
}
internal static class EitrProtection
{
	internal static bool Protected(WearNTear wear)
	{
		if (!Native.Owner((Component)(object)wear))
		{
			return false;
		}
		if (Object.op_Implicit((Object)(object)((Component)wear).GetComponent<PipeNode>()))
		{
			return true;
		}
		Container componentInChildren = ((Component)wear).GetComponentInChildren<Container>();
		if (!ChestHandoff.Eligible(componentInChildren))
		{
			return false;
		}
		if (!Routing.Dirty)
		{
			return Routing.HasChestAnchor(componentInChildren);
		}
		return PipeStructure.Anchored(wear);
	}
}
[HarmonyPatch(typeof(WearNTear), "RPC_Damage")]
internal static class PipeEitrProtectionPatch
{
	private static bool Prefix(WearNTear __instance, HitData __1)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (__1 != null && EitrDamageRules.IsSpark(__1.HaveAttacker(), __1.m_damage))
		{
			return !EitrProtection.Protected(__instance);
		}
		return true;
	}
}
public sealed class Hopper : MonoBehaviour
{
	public bool HasLegacyBounds;

	public Vector3 LegacyCenter;

	public Vector3 LegacySize;

	private static readonly HashSet<Inventory> inventories = new HashSet<Inventory>();

	private void Awake()
	{
		RegisterInventory(((Component)this).GetComponent<Container>());
	}

	internal static void RegisterInventory(Container c)
	{
		if (Is(c) && c.GetInventory() != null)
		{
			inventories.Add(c.GetInventory());
		}
	}

	private void OnDestroy()
	{
		Container component = ((Component)this).GetComponent<Container>();
		if (Object.op_Implicit((Object)(object)component) && component.GetInventory() != null)
		{
			inventories.Remove(component.GetInventory());
		}
	}

	internal static bool InventoryIsHopper(Inventory inventory)
	{
		return inventories.Contains(inventory);
	}

	internal static bool Is(Container chest)
	{
		if (Object.op_Implicit((Object)(object)chest))
		{
			return Object.op_Implicit((Object)(object)((Component)chest).GetComponent<Hopper>());
		}
		return false;
	}

	internal static bool Allows(Container source, Container destination, ItemData item)
	{
		if (!Is(source))
		{
			return true;
		}
		if (Is(destination) || !Object.op_Implicit((Object)(object)item.m_dropPrefab))
		{
			return false;
		}
		return destination.GetInventory().GetAllItems().Any((ItemData i) => Object.op_Implicit((Object)(object)i.m_dropPrefab) && i.m_stack > 0 && SortingRules.Matches(((Object)item.m_dropPrefab).name, item.m_quality, item.m_worldLevel, ((Object)i.m_dropPrefab).name, i.m_quality, i.m_worldLevel));
	}

	private static void CaptureLegacySockets(GameObject go, Container chest)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Expected O, but got Unknown
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_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_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0102: Unknown result type (might be due to invalid IL or missing references)
		List<GameObject> list = new List<GameObject>();
		for (int i = 0; i < 4; i++)
		{
			GameObject val = new GameObject("legacy_hopper_bounds");
			val.transform.SetParent(go.transform, false);
			Quaternion val2 = Quaternion.Euler(0f, (float)(i * 90), 0f);
			val.transform.localPosition = Vector3.up * 0.95f + val2 * (Vector3.forward * 0.43f);
			val.transform.localRotation = val2 * Quaternion.Euler(-25f, 0f, 0f);
			val.transform.localScale = new Vector3(1.15f, 0.65f, 0.065f);
			val.AddComponent<BoxCollider>();
			list.Add(val);
		}
		Hopper component = go.GetComponent<Hopper>();
		if (PortFactory.TryChestBounds(chest, preview: true, out var bounds))
		{
			component.HasLegacyBounds = true;
			component.LegacyCenter = ((Bounds)(ref bounds)).center;
			component.LegacySize = ((Bounds)(ref bounds)).size;
		}
		foreach (GameObject item in list)
		{
			Object.DestroyImmediate((Object)(object)item);
		}
	}

	internal static void Register(ZNetScene scene)
	{
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Expected O, but got Unknown
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Expected O, but got Unknown
		if (!Plugin.Prefabs.Any((GameObject p) => ((Object)p).name == "FP_Hopper"))
		{
			GameObject prefab = scene.GetPrefab("piece_chest_wood");
			GameObject val = Object.Instantiate<GameObject>(prefab, Plugin.PrefabRoot.transform, false);
			((Object)val).name = "FP_Hopper";
			val.AddComponent<Hopper>();
			Container component = val.GetComponent<Container>();
			component.m_name = PipeLanguage.Token("Forge sorting hopper");
			Piece component2 = val.GetComponent<Piece>();
			component2.m_name = "Forge sorting hopper";
			component2.m_icon = PipeIcons.Load("Hopper");
			component2.m_description = "Drop items into the hopper or open its inventory. Items move only to connected receiving chests already containing the same item. Unmatched items stay here.";
			component2.m_category = (PieceCategory)1;
			component2.m_usage = (UsageTagFlags)2050;
			component2.m_craftingStation = scene.GetPrefab("piece_workbench").GetComponent<CraftingStation>();
			Requirement[] array = (Requirement[])(object)new Requirement[2];
			Requirement val2 = new Requirement();
			val2.m_resItem = scene.GetPrefab("Wood").GetComponent<ItemDrop>();
			val2.m_amount = 10;
			val2.m_recover = true;
			array[0] = val2;
			Requirement val3 = new Requirement();
			val3.m_resItem = scene.GetPrefab("Copper").GetComponent<ItemDrop>();
			val3.m_amount = 4;
			val3.m_recover = true;
			array[1] = val3;
			component2.m_resources = array;
			MetalPlacement.Apply(component2, scene);
			CaptureLegacySockets(val, component);
			HopperModel.Build(val);
			Plugin.Prefabs.Add(val);
			val.SetActive(true);
		}
	}
}
internal static class HopperModel
{
	internal static Mesh Create()
	{
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Expected O, but got Unknown
		HopperShape hopperShape = HopperShape.Create();
		List<Vector3> list = new List<Vector3>();
		List<Vector2> list2 = new List<Vector2>();
		foreach (Point vertex in hopperShape.Vertices)
		{
			list.Add(new Vector3(vertex.X, vertex.Y, vertex.Z));
		}
		foreach (Point item in hopperShape.UV)
		{
			list2.Add(new Vector2(item.X, item.Y));
		}
		Mesh val = new Mesh();
		((Object)val).name = "Forge sorting hopper fitted funnel";
		Mesh val2 = val;
		val2.SetVertices(list);
		val2.SetUVs(0, list2);
		val2.SetTriangles(hopperShape.Indices, 0);
		val2.RecalculateNormals();
		val2.RecalculateTangents();
		val2.RecalculateBounds();
		return val2;
	}

	internal static void Build(GameObject go)
	{
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Expected O, but got Unknown
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0114: Expected O, but got Unknown
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		//IL_0149: Unknown result type (might be due to invalid IL or missing references)
		//IL_0152: Unknown result type (might be due to invalid IL or missing references)
		//IL_015c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0161: Unknown result type (might be due to invalid IL or missing references)
		//IL_0163: 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_0172: 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_0188: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Unknown result type (might be due to invalid IL or missing references)
		//IL_019e: 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_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
		LODGroup[] componentsInChildren = go.GetComponentsInChildren<LODGroup>(true);
		foreach (LODGroup val in componentsInChildren)
		{
			Object.DestroyImmediate((Object)(object)val);
		}
		Renderer[] componentsInChildren2 = go.GetComponentsInChildren<Renderer>(true);
		foreach (Renderer val2 in componentsInChildren2)
		{
			val2.enabled = false;
		}
		GameObject val3 = new GameObject("FP_hopper_funnel");
		val3.layer = go.layer;
		val3.transform.SetParent(go.transform, false);
		val3.AddComponent<MeshFilter>().sharedMesh = Create();
		((Renderer)val3.AddComponent<MeshRenderer>()).sharedMaterial = CopperMaterial.GetPipe();
		Collider[] componentsInChildren3 = go.GetComponentsInChildren<Collider>(true);
		foreach (Collider val4 in componentsInChildren3)
		{
			Object.DestroyImmediate((Object)(object)val4);
		}
		BoxCollider val5 = val3.AddComponent<BoxCollider>();
		val5.center = new Vector3(0f, 0.365f, 0f);
		val5.size = new Vector3(0.76f, 0.43f, 0.76f);
		for (int l = 0; l < 4; l++)
		{
			GameObject val6 = new GameObject("hopper_wall_collider");
			val6.layer = go.layer;
			val6.transform.SetParent(val3.transform, false);
			Quaternion val7 = Quaternion.Euler(0f, (float)(l * 90), 0f);
			val6.transform.localPosition = Vector3.up * 0.88f + val7 * (Vector3.forward * 0.515f);
			val6.transform.localRotation = val7 * Quaternion.FromToRotation(Vector3.up, new Vector3(0f, 0.6f, 0.27f));
			val6.AddComponent<BoxCollider>().size = new Vector3(1.28f, 0.658f, 0.055f);
		}
		WearNTear component = go.GetComponent<WearNTear>();
		if (Object.op_Implicit((Object)(object)component))
		{
			component.m_new = val3;
			component.m_worn = val3;
			component.m_broken = val3;
			component.m_wet = null;
		}
	}
}
public sealed class HopperShape
{
	public readonly List<Point> Vertices = new List<Point>();

	public readonly List<Point> UV = new List<Point>();

	public readonly List<int> Indices = new List<int>();

	public static HopperShape Create()
	{
		HopperShape hopperShape = new HopperShape();
		Point[] array = new Point[6]
		{
			new Point(0.38f, 0.15f, 0f),
			new Point(0.38f, 0.58f, 0f),
			new Point(0.65f, 1.18f, 0f),
			new Point(0.59f, 1.18f, 0f),
			new Point(0.32f, 0.59f, 0f),
			new Point(0.32f, 0.19f, 0f)
		};
		for (int i = 0; i < array.Length - 1; i++)
		{
			for (int j = 0; j < 4; j++)
			{
				int n = (j + 1) % 4;
				int count = hopperShape.Vertices.Count;
				hopperShape.Vertices.Add(Corner(j, array[i]));
				hopperShape.Vertices.Add(Corner(n, array[i]));
				hopperShape.Vertices.Add(Corner(n, array[i + 1]));
				hopperShape.Vertices.Add(Corner(j, array[i + 1]));
				float num = ((i == 2) ? 0.37f : 0.12f);
				hopperShape.UV.Add(new Point(num - 0.07f, 0.15f, 0f));
				hopperShape.UV.Add(new Point(num + 0.07f, 0.15f, 0f));
				hopperShape.UV.Add(new Point(num + 0.07f, 0.85f, 0f));
				hopperShape.UV.Add(new Point(num - 0.07f, 0.85f, 0f));
				hopperShape.Indices.AddRange(new int[6]
				{
					count,
					count + 2,
					count + 1,
					count,
					count + 3,
					count + 2
				});
			}
		}
		int count2 = hopperShape.Vertices.Count;
		for (int k = 0; k < 4; k++)
		{
			hopperShape.Vertices.Add(Corner(k, array[5]));
			hopperShape.UV.Add(new Point(0.62f, 0.5f, 0f));
		}
		hopperShape.Indices.AddRange(new int[6]
		{
			count2,
			count2 + 2,
			count2 + 1,
			count2,
			count2 + 3,
			count2 + 2
		});
		count2 = hopperShape.Vertices.Count;
		for (int l = 0; l < 4; l++)
		{
			hopperShape.Vertices.Add(Corner(l, array[0]));
			hopperShape.UV.Add(new Point(0.12f, 0.5f, 0f));
		}
		hopperShape.Indices.AddRange(new int[6]
		{
			count2,
			count2 + 1,
			count2 + 2,
			count2,
			count2 + 2,
			count2 + 3
		});
		return hopperShape;
	}

	private static Point Corner(int n, Point ring)
	{
		return new Point((n == 0 || n == 3) ? (0f - ring.X) : ring.X, ring.Y, (n < 2) ? (0f - ring.X) : ring.X);
	}
}
internal static class InventoryWake
{
	private sealed class State
	{
		internal ulong Fingerprint;

		internal float Next;

		internal int Misses;
	}

	private static readonly Dictionary<Inventory, int> revisions = new Dictionary<Inventory, int>();

	private static readonly Dictionary<Container, State> idle = new Dictionary<Container, State>();

	internal static void Clear()
	{
		revisions.Clear();
		idle.Clear();
	}

	internal static void Watch(Inventory inventory)
	{
		if (inventory != null && !revisions.ContainsKey(inventory))
		{
			revisions.Add(inventory, 0);
		}
	}

	internal static void Changed(Inventory inventory)
	{
		if (revisions.TryGetValue(inventory, out var value))
		{
			revisions[inventory] = value + 1;
		}
	}

	private static ulong Fingerprint(Container source, List<Container> peers)
	{
		ulong n = (ulong)Routing.Revision;
		Mix(ref n, source);
		foreach (Container peer in peers)
		{
			if (Object.op_Implicit((Object)(object)peer))
			{
				Mix(ref n, peer);
			}
		}
		return n;
	}

	private static void Mix(ref ulong n, Container c)
	{
		Inventory inventory = c.GetInventory();
		Watch(inventory);
		revisions.TryGetValue(inventory, out var value);
		n = n * 1099511628211L + (uint)((Object)c).GetInstanceID();
		n = n * 1099511628211L + (uint)value;
		n = n * 1099511628211L + (uint)ChestSettings.Mode(c);
		n = n * 1099511628211L + (ulong)(Native.Owner((Component)(object)c) ? 1 : 0);
	}

	internal static bool ShouldRun(Container source, List<Container> peers)
	{
		if (!idle.TryGetValue(source, out var value))
		{
			idle[source] = new State();
			return true;
		}
		if (!OpenChestAccess.Viewing(source) && !(Time.time >= value.Next))
		{
			return value.Fingerprint != Fingerprint(source, peers);
		}
		return true;
	}

	internal static void Completed(Container source, List<Container> peers, bool moved)
	{
		if (!idle.TryGetValue(source, out var value))
		{
			value = (idle[source] = new State());
		}
		value.Misses = ((!moved) ? Math.Min(3, value.Misses + 1) : 0);
		value.Fingerprint = Fingerprint(source, peers);
		value.Next = Time.time + (float)((!moved) ? value.Misses : 0);
	}

	internal static void Prune()
	{
		Container[] array = idle.Keys.Where((Container c) => !Object.op_Implicit((Object)(object)c) || !Plugin.Chests.Contains(c)).ToArray();
		foreach (Container key in array)
		{
			idle.Remove(key);
		}
		HashSet<Inventory> live = new HashSet<Inventory>();
		foreach (Container chest in Plugin.Chests)
		{
			if (Object.op_Implicit((Object)(object)chest))
			{
				live.Add(chest.GetInventory());
			}
		}
		Inventory[] array2 = revisions.Keys.Where((Inventory i) => !live.Contains(i)).ToArray();
		foreach (Inventory key2 in array2)
		{
			revisions.Remove(key2);
		}
	}
}
[HarmonyPatch(typeof(Inventory), "Changed")]
internal static class InventoryWakePatch
{
	private static void Postfix(Inventory __instance)
	{
		InventoryWake.Changed(__instance);
	}
}
internal static class LoosePickup
{
	private sealed class Mouth
	{
		internal Component Owner;

		internal Transform Socket;

		internal List<Container> Chests;

		internal Vector3 Position
		{
			get
			{
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0027: Unknown result type (might be due to invalid IL or missing references)
				if (!Object.op_Implicit((Object)(object)Socket))
				{
					return Owner.transform.TransformPoint(Vector3.up * 1.15f);
				}
				return Socket.position;
			}
		}
	}

	private static readonly Collider[] hits = (Collider[])(object)new Collider[64];

	private static readonly HashSet<ItemDrop> seen = new HashSet<ItemDrop>();

	private static readonly int itemMask = LayerMask.GetMask(new string[1] { "item" });

	private static readonly int wallMask = LayerMask.GetMask(new string[3] { "terrain", "piece", "static_solid" });

	private static int cursor;

	private static int revision = -1;

	private static readonly List<Mouth> mouths = new List<Mouth>();

	internal static void Tick()
	{
		//IL_0230: Unknown result type (might be due to invalid IL or missing references)
		//IL_0266: 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)
		//IL_0079: Expected O, but got Unknown
		//IL_039b: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a7: Unknown result type (might be due to invalid IL or missing references)
		if (revision != Routing.Revision)
		{
			mouths.Clear();
			revision = Routing.Revision;
			foreach (PipeNode item in PipeNode.All)
			{
				if (!Object.op_Implicit((Object)(object)item) || !item.Ready || !Native.Valid((Component)(object)item))
				{
					continue;
				}
				foreach (Transform item2 in ((Component)item).transform)
				{
					Transform val = item2;
					if (!(((Object)val).name != "_snap") || (item.Kind == PipeKind.Output && ((Object)val).name == "_fp_dock"))
					{
						mouths.Add(new Mouth
						{
							Owner = (Component)(object)item,
							Socket = val
						});
					}
				}
			}
			foreach (Container chest in Plugin.Chests)
			{
				if (Hopper.Is(chest) && ChestHandoff.Eligible(chest))
				{
					mouths.Add(new Mouth
					{
						Owner = (Component)(object)chest,
						Chests = new List<Container> { chest }
					});
				}
			}
		}
		if (mouths.Count == 0)
		{
			cursor = 0;
			return;
		}
		int num = Math.Min(4, mouths.Count);
		for (int i = 0; i < num; i++)
		{
			Mouth mouth = mouths[cursor++ % mouths.Count];
			PipeNode pipeNode = mouth.Owner as PipeNode;
			if (!Object.op_Implicit((Object)(object)mouth.Owner) || !Native.Owner(mouth.Owner) || !ChestHandoff.Access(mouth.Owner))
			{
				continue;
			}
			int num2 = Physics.OverlapSphereNonAlloc(mouth.Position, 0.85f, hits, itemMask, (QueryTriggerInteraction)2);
			if (num2 == 0)
			{
				continue;
			}
			if (Object.op_Implicit((Object)(object)pipeNode))
			{
				MachinePort machinePort = SocketIndex.NearbyPorts(mouth.Position).FirstOrDefault((MachinePort p) => Object.op_Implicit((Object)(object)p) && !p.Preview && Object.op_Implicit((Object)(object)p.Target) && Vector3.SqrMagnitude(((Component)p).transform.position - mouth.Position) < 0.0324f);
				if ((Object.op_Implicit((Object)(object)machinePort) && machinePort.Kind != PipeKind.Output) || (!Object.op_Implicit((Object)(object)machinePort) && (!Object.op_Implicit((Object)(object)mouth.Socket) || PipeSnapping.Occupied(mouth.Socket, pipeNode))))
				{
					continue;
				}
				mouth.Chests = Routing.StorageForPipe(pipeNode);
			}
			if (mouth.Chests.Count == 0)
			{
				continue;
			}
			seen.Clear();
			int num3 = 10;
			for (int num4 = 0; num4 < num2; num4++)
			{
				if (num3 <= 0)
				{
					break;
				}
				ItemDrop val2 = (Object.op_Implicit((Object)(object)hits[num4]) ? ((Component)hits[num4]).GetComponentInParent<ItemDrop>() : null);
				if (!Object.op_Implicit((Object)(object)val2) || !seen.Add(val2) || !val2.m_autoPickup || !Native.Valid((Component)(object)val2) || !ChestHandoff.Access((Component)(object)val2))
				{
					continue;
				}
				val2.Load();
				if (val2.m_itemData.m_stack <= 0 || Physics.Linecast(mouth.Position, ((Component)val2).transform.position, wallMask, (QueryTriggerInteraction)1))
				{
					continue;
				}
				foreach (Container chest2 in mouth.Chests)
				{
					ChestHandoff chestHandoff = (Object.op_Implicit((Object)(object)chest2) ? ((Component)chest2).GetComponent<ChestHandoff>() : null);
					if (!Object.op_Implicit((Object)(object)chestHandoff) || !chestHandoff.Ready(mouth.Owner) || !chest2.GetInventory().CanAddItem(val2.m_itemData, 1))
					{
						continue;
					}
					if (!Native.Owner((Component)(object)val2))
					{
						val2.RequestOwn();
						break;
					}
					if (!val2.CanPickup(true))
					{
						break;
					}
					while (Object.op_Implicit((Object)(object)val2) && num3 > 0 && val2.m_itemData.m_stack > 0 && OpenChestAccess.CanEdit(chest2) && Native.Owner((Component)(object)val2))
					{
						ItemData val3 = val2.m_itemData.Clone();
						val3.m_stack = 1;
						if (!chest2.GetInventory().AddItem(val3))
						{
							break;
						}
						num3--;
						if (val2.m_itemData.m_stack == 1)
						{
							val2.SetStack(0);
							ZNetScene.instance.Destroy(((Component)val2).gameObject);
							break;
						}
						val2.SetStack(val2.m_itemData.m_stack - 1);
					}
					if (!Object.op_Implicit((Object)(object)val2) || num3 == 0)
					{
						break;
					}
				}
			}
		}
		if (cursor > 1000000)
		{
			cursor %= mouths.Count;
		}
	}
}
public sealed class MachineHandoff : MonoBehaviour
{
	private Component machine;

	private float nextRequest;

	private float nextGrant;

	private void Awake()
	{
		machine = Stations.Find(((Component)this).gameObject);
		Native.View(machine).Register<ZDOID>("FP_RequestMachine", (Action<long, ZDOID>)OnRequest);
	}

	private Container PreferredViewer()
	{
		return (from c in (from c in Routing.Connected(machine, PipeKind.Output).Concat(Routing.Connected(machine, PipeKind.Input)).Concat(Routing.Connected(machine, PipeKind.Fuel))
				where ChestHandoff.Eligible(c) && ChestHandoff.Busy(c)
				select c).Distinct()
			orderby ((object)Unsafe.As<ZDOID, ZDOID>(ref Native.View((Component)(object)c).GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString()
			select c).FirstOrDefault();
	}

	internal static void Prepare(Component s)
	{
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		if (!Native.Valid(s))
		{
			return;
		}
		MachineHandoff machineHandoff = s.GetComponent<MachineHandoff>();
		if (!Object.op_Implicit((Object)(object)machineHandoff))
		{
			machineHandoff = s.gameObject.AddComponent<MachineHandoff>();
		}
		if (!Native.Owner(s) && !(Time.time < machineHandoff.nextRequest))
		{
			Container val = machineHandoff.PreferredViewer();
			if (Object.op_Implicit((Object)(object)val) && Native.Owner((Component)(object)val) && OpenChestAccess.Viewing(val) && ChestHandoff.Access((Component)(object)val) && ChestHandoff.Access(s))
			{
				machineHandoff.nextRequest = Time.time + 3f;
				Native.View(s).InvokeRPC("FP_RequestMachine", new object[1] { Native.View((Component)(object)val).GetZDO().m_uid });
			}
		}
	}

	private void OnRequest(long sender, ZDOID chestId)
	{
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		if (Native.Owner(machine) && Stations.Supported(machine) && Stations.CanHandoff(machine) && !(Time.time < nextGrant) && ChestHandoff.Access(machine))
		{
			Container val = PreferredViewer();
			if (Object.op_Implicit((Object)(object)val) && !(Native.View((Component)(object)val).GetZDO().m_uid != chestId) && Native.View((Component)(object)val).GetZDO().GetOwner() == sender && ChestHandoff.Access((Component)(object)val))
			{
				ZDO zDO = Native.View(machine).GetZDO();
				ZDOMan.instance.ForceSendZDO(sender, zDO.m_uid);
				zDO.SetOwner(sender);
				nextGrant = Time.time + 3f;
			}
		}
	}
}
internal static class MetalPlacement
{
	internal static void Apply(Piece piece, ZNetScene scene)
	{
		//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ff: Expected O, but got Unknown
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Expected O, but got Unknown
		GameObject val = scene.GetPrefab("sfx_build_hammer_metal");
		if (!Object.op_Implicit((Object)(object)val))
		{
			string[] array = new string[3] { "iron_wall_1x1", "iron_floor_1x1", "piece_chest_blackmetal" };
			foreach (string text in array)
			{
				GameObject prefab = scene.GetPrefab(text);
				Piece val2 = (Object.op_Implicit((Object)(object)prefab) ? prefab.GetComponent<Piece>() : null);
				if (Object.op_Implicit((Object)(object)val2) && val2.m_placeEffect != null)
				{
					val = (from e in val2.m_placeEffect.m_effectPrefabs
						where Object.op_Implicit((Object)(object)e.m_prefab) && ((Object)e.m_prefab).name.IndexOf("metal", StringComparison.OrdinalIgnoreCase) >= 0 && Object.op_Implicit((Object)(object)e.m_prefab.GetComponentInChildren<AudioSource>(true))
						select e.m_prefab).FirstOrDefault();
					if (Object.op_Implicit((Object)(object)val))
					{
						break;
					}
				}
			}
		}
		if (!Object.op_Implicit((Object)(object)val))
		{
			Plugin.Error("Metal placement sound prefab unavailable; keeping native placement effects.");
			return;
		}
		EffectList val3 = new EffectList();
		EffectData[] array2 = (EffectData[])(object)new EffectData[1];
		EffectData val4 = new EffectData();
		val4.m_prefab = val;
		val4.m_enabled = true;
		val4.m_scale = true;
		array2[0] = val4;
		val3.m_effectPrefabs = array2;
		piece.m_placeEffect = val3;
	}
}
public struct Point
{
	public float X;

	public float Y;

	public float Z;

	public Point(float x, float y, float z)
	{
		X = x;
		Y = y;
		Z = z;
	}

	public static float DistanceSquared(Point a, Point b)
	{
		float num = a.X - b.X;
		float num2 = a.Y - b.Y;
		float num3 = a.Z - b.Z;
		return num * num + num2 * num2 + num3 * num3;
	}

	public static float Dot(Point a, Point b)
	{
		return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
	}
}
public struct Socket
{
	public int Node;

	public Point Position;

	public Point Direction;

	public Socket(int node, Point position, Point direction)
	{
		Node = node;
		Position = position;
		Direction = direction;
	}
}
public sealed class NetworkGraph
{
	internal struct Cell : IEquatable<Cell>
	{
		internal int X;

		internal int Y;

		internal int Z;

		public bool Equals(Cell c)
		{
			if (X == c.X && Y == c.Y)
			{
				return Z == c.Z;
			}
			return false;
		}

		public override bool Equals(object other)
		{
			if (other is Cell)
			{
				return Equals((Cell)other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (X * 73856093) ^ (Y * 19349663) ^ (Z * 83492791);
		}
	}

	private readonly int[] parents;

	public NetworkGraph(int count, IList<Socket> sockets, float tolerance)
	{
		if (count < 0 || tolerance <= 0f)
		{
			throw new ArgumentOutOfRangeException();
		}
		parents = new int[count];
		for (int i = 0; i < count; i++)
		{
			parents[i] = i;
		}
		Dictionary<Cell, List<Socket>> dictionary = new Dictionary<Cell, List<Socket>>();
		foreach (Socket socket in sockets)
		{
			if (socket.Node < 0 || socket.Node >= count)
			{
				throw new ArgumentOutOfRangeException("sockets");
			}
			int num = (int)Math.Floor(socket.Position.X / tolerance);
			int num2 = (int)Math.Floor(socket.Position.Y / tolerance);
			int num3 = (int)Math.Floor(socket.Position.Z / tolerance);
			for (int j = -1; j <= 1; j++)
			{
				for (int k = -1; k <= 1; k++)
				{
					for (int l = -1; l <= 1; l++)
					{
						if (!dictionary.TryGetValue(Key(num + j, num2 + k, num3 + l), out var value))
						{
							continue;
						}
						foreach (Socket item in value)
						{
							if (item.Node != socket.Node && Point.DistanceSquared(socket.Position, item.Position) <= tolerance * tolerance && Point.Dot(socket.Direction, item.Direction) < -0.8f)
							{
								parents[Root(socket.Node)] = Root(item.Node);
							}
						}
					}
				}
			}
			Cell key = Key(num, num2, num3);
			if (!dictionary.TryGetValue(key, out var value2))
			{
				value2 = new List<Socket>();
				dictionary.Add(key, value2);
			}
			value2.Add(socket);
		}
	}

	private static Cell Key(int x, int y, int z)
	{
		return new Cell
		{
			X = x,
			Y = y,
			Z = z
		};
	}

	public void Join(int a, int b)
	{
		parents[Root(a)] = Root(b);
	}

	public int Root(int i)
	{
		while (parents[i] != i)
		{
			parents[i] = parents[parents[i]];
			i = parents[i];
		}
		return i;
	}

	public bool Connected(int a, int b)
	{
		return Root(a) == Root(b);
	}
}
internal static class OpenChestAccess
{
	private static readonly FieldInfo Current = AccessTools.Field(typeof(InventoryGui), "m_currentContainer");

	private static readonly FieldInfo Drag = AccessTools.Field(typeof(InventoryGui), "m_dragItem");

	private static readonly FieldInfo DragInventory = AccessTools.Field(typeof(InventoryGui), "m_dragInventory");

	private static readonly FieldInfo DragObject = AccessTools.Field(typeof(InventoryGui), "m_dragGo");

	private static readonly FieldInfo Split = AccessTools.Field(typeof(InventoryGui), "m_splitItem");

	private static readonly FieldInfo SplitInventory = AccessTools.Field(typeof(InventoryGui), "m_splitInventory");

	internal static bool Viewing(Container chest)
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		if (Object.op_Implicit((Object)(object)InventoryGui.instance))
		{
			return (Object)(Container)Current.GetValue(InventoryGui.instance) == (Object)(object)chest;
		}
		return false;
	}

	internal static bool CanEdit(Container chest)
	{
		return OpenChestRules.CanEdit(Native.Owner((Component)(object)chest), ChestHandoff.Busy(chest), Viewing(chest));
	}

	internal static bool Reserved(Inventory inventory, ItemData item)
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Expected O, but got Unknown
		InventoryGui instance = InventoryGui.instance;
		if (!Object.op_Implicit((Object)(object)instance))
		{
			return false;
		}
		if (!Object.op_Implicit((Object)(GameObject)DragObject.GetValue(instance)) || DragInventory.GetValue(instance) != inventory || Drag.GetValue(instance) != item)
		{
			if (Object.op_Implicit((Object)(object)instance.m_splitDialog) && ((Component)instance.m_splitDialog).gameObject.activeInHierarchy && SplitInventory.GetValue(instance) == inventory)
			{
				return Split.GetValue(instance) == item;
			}
			return false;
		}
		return true;
	}
}
internal static class OpenChestRules
{
	internal static bool CanEdit(bool owned, bool inUse, bool localViewer)
	{
		if (owned)
		{
			if (inUse)
			{
				return localViewer;
			}
			return true;
		}
		return false;
	}

	internal static bool CanHandoff(bool owned, bool inUse, bool localViewer)
	{
		if (owned && !inUse)
		{
			return !localViewer;
		}
		return false;
	}

	internal static bool MayPull(bool targetOwned, bool targetViewed, bool sourceBusy)
	{
		if (targetOwned && targetViewed)
		{
			return !sourceBusy;
		}
		return false;
	}
}
internal static class PerformanceConsole
{
	internal static void Register()
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Expected O, but got Unknown
		new ConsoleCommand("fpperf", "Forge Pipes: fpperf status | render/automation/connections/groups on/off | reset", (ConsoleEvent)delegate(ConsoleEventArgs args)
		{
			if (args.Args.Length == 2 && args.Args[1] == "reset")
			{
				ConfigEntry<bool> renderPipes = Plugin.RenderPipes;
				ConfigEntry<bool> runAutomation = Plugin.RunAutomation;
				ConfigEntry<bool> runConnectionChecks = Plugin.RunConnectionChecks;
				bool flag = (Plugin.GroupRendering.Value = true);
				bool flag3 = (runConnectionChecks.Value = flag);
				bool value = (runAutomation.Value = flag3);
				renderPipes.Value = value;
				Routing.Dirty = true;
			}
			else if (args.Args.Length == 3 && (args.Args[2] == "on" || args.Args[2] == "off"))
			{
				bool value2 = args.Args[2] == "on";
				switch (args.Args[1])
				{
				case "render":
					Plugin.RenderPipes.Value = value2;
					break;
				case "automation":
					Plugin.RunAutomation.Value = value2;
					break;
				case "connections":
					Plugin.RunConnectionChecks.Value = value2;
					Routing.Dirty = true;
					break;
				case "groups":
					Plugin.GroupRendering.Value = value2;
					break;
				default:
					args.Context.AddString("Use render, automation, connections or groups.");
					return;
				}
			}
			string text = "Forge Pipes: render=" + Plugin.RenderPipes.Value + ", automation=" + Plugin.RunAutomation.Value + ", connections=" + Plugin.RunConnectionChecks.Value + ", groups=" + Plugin.GroupRendering.Value + ". Use fpperf reset to restore. Read 30-second FP timing samples in LogOutput.log.";
			args.Context.AddString(text);
			Plugin.LogInfo(text);
		}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
	}
}
internal static class PerformanceStats
{
	private static readonly Dictionary<string, double[]> sections = new Dictionary<string, double[]>();

	private static int frames;

	private static int ticks;

	private static int lastRevision;

	private static float elapsed;

	private static float worstFrame;

	private static double tickTotal;

	private static double tickWorst;

	internal static void Section(string name, long start)
	{
		if (Plugin.PerformanceLogging.Value)
		{
			if (!sections.TryGetValue(name, out var value))
			{
				value = (sections[name] = new double[3]);
			}
			double num = (double)(Stopwatch.GetTimestamp() - start) * 1000.0 / (double)Stopwatch.Frequency;
			value[0] += num;
			value[1] = Math.Max(value[1], num);
			value[2] += 1.0;
		}
	}

	internal static void Tick(long started)
	{
		double num = (double)(Stopwatch.GetTimestamp() - started) * 1000.0 / (double)Stopwatch.Frequency;
		ticks++;
		tickTotal += num;
		tickWorst = Math.Max(tickWorst, num);
	}

	internal static void Frame()
	{
		if (!Plugin.PerformanceLogging.Value || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
		{
			frames = (ticks = 0);
			elapsed = (worstFrame = 0f);
			tickTotal = (tickWorst = 0.0);
			lastRevision = Routing.Revision;
			return;
		}
		frames++;
		elapsed += Time.unscaledDeltaTime;
		worstFrame = Mathf.Max(worstFrame, Time.unscaledDeltaTime);
		if (elapsed < 30f)
		{
			return;
		}
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		long num4 = 0L;
		foreach (PipeNode item in PipeNode.All)
		{
			if (!Object.op_Implicit((Object)(object)item) || !Native.Valid((Component)(object)item))
			{
				continue;
			}
			num++;
			MeshRenderer[] componentsInChildren = ((Component)item).GetComponentsInChildren<MeshRenderer>();
			foreach (MeshRenderer val in componentsInChildren)
			{
				if (((Renderer)val).enabled && !((Renderer)val).forceRenderingOff)
				{
					num2++;
					MeshFilter component = ((Component)val).GetComponent<MeshFilter>();
					if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.sharedMesh))
					{
						num4 += (long)component.sharedMesh.GetIndexCount(0) / 3L;
					}
				}
			}
			Collider[] componentsInChildren2 = ((Component)item).GetComponentsInChildren<Collider>();
			foreach (Collider val2 in componentsInChildren2)
			{
				if (val2.enabled)
				{
					num3++;
				}
			}
		}
		num2 += PipeRenderGroups.RendererCount;
		num4 += PipeRenderGroups.TriangleCount;
		Plugin.LogInfo($"FP performance 1.1.1: {(float)frames / elapsed:F1} FPS avg, worst frame {worstFrame * 1000f:F1} ms; pipes={num}, enabled renderers={num2}, triangles={num4}, colliders={num3}; automation avg/max={((ticks == 0) ? 0.0 : (tickTotal / (double)ticks)):F2}/{tickWorst:F2} ms; graph rebuilds={Routing.Revision - lastRevision} in {elapsed:F1}s.");
		foreach (KeyValuePair<string, double[]> section in sections)
		{
			Plugin.LogInfo($"FP timing {section.Key}: avg {section.Value[0] / Math.Max(1.0, section.Value[2]):F3} ms, max {section.Value[1]:F3} ms, calls {section.Value[2]}; managed memory {(double)GC.GetTotalMemory(forceFullCollection: false) / 1048576.0:F1} MB");
		}
		sections.Clear();
		frames = (ticks = 0);
		elapsed = (worstFrame = 0f);
		tickTotal = (tickWorst = 0.0);
		lastRevision = Routing.Revision;
	}
}
public sealed class PipeNode : MonoBehaviour, Hoverable
{
	public PipeKind Kind;

	public Vector3[] Ends;

	public Vector3[] Directions;

	public Vector3[] RunStarts;

	public Vector3[] RunEnds;

	private readonly List<LineRenderer> endGuides = new List<LineRenderer>();

	internal static readonly HashSet<PipeNode> All = new HashSet<PipeNode>();

	internal int VisualRevision;

	private bool registered;

	internal bool Terminal
	{
		get
		{
			if (Kind >= PipeKind.Chest)
			{
				return Kind <= PipeKind.Output;
			}
			return false;
		}
	}

	internal bool Ready
	{
		get
		{
			if (Kind == PipeKind.Stretch)
			{
				if (Object.op_Implicit((Object)(object)((Component)this).GetComponent<StretchPipe>()))
				{
					return ((Component)this).GetComponent<StretchPipe>().Shape != null;
				}
				return false;
			}
			return true;
		}
	}

	internal Vector3 LocalDock
	{
		get
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			Transform val = ((Component)this).transform.Find("_fp_dock");
			if (!Object.op_Implicit((Object)(object)val))
			{
				return Vector3.back * 0.5f;
			}
			return val.localPosition;
		}
	}

	internal Vector3 Dock => ((Component)this).transform.TransformPoint(LocalDock);

	private void Start()
	{
		if (Native.Valid((Component)(object)this))
		{
			registered = true;
			Routing.Dirty = true;
			SocketIndex.Invalidate();
		}
	}

	private void OnEnable()
	{
		All.Add(this);
		SocketIndex.Invalidate();
		if (Native.Valid((Component)(object)this))
		{
			registered = true;
			Routing.Dirty = true;
		}
	}

	private void OnDisable()
	{
		All.Remove(this);
		PipeRenderGroups.Remove(this);
		SocketIndex.Invalidate();
		foreach (LineRenderer endGuide in endGuides)
		{
			if (Object.op_Implicit((Object)(object)endGuide))
			{
				((Renderer)endGuide).enabled = false;
			}
		}
		if (registered)
		{
			Routing.Dirty = true;
		}
		registered = false;
	}

	private void BuildEndGuides()
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Expected O, but got Unknown
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: 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_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: 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_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0126: Unknown result type (might be due to invalid IL or missing references)
		//IL_0176: Unknown result type (might be due to invalid IL or missing references)
		if (endGuides.Count > 0 || Ends == null || Ends.Length == 0)
		{
			return;
		}
		Material guideMaterial = PortFactory.GuideMaterial;
		if (!Object.op_Implicit((Object)(object)guideMaterial))
		{
			return;
		}
		for (int i = 0; i < Ends.Length; i++)
		{
			GameObject val = new GameObject("FP_pipe_end_guide");
			val.layer = ((Component)this).gameObject.layer;
			val.transform.SetParent(((Component)this).transform, false);
			Vector3 localPosition = ((Kind == PipeKind.Chest) ? LocalDock : Ends[i]);
			Vector3 val2 = ((Kind == PipeKind.Chest) ? Vector3.back : Directions[i]);
			val.transform.localPosition = localPosition;
			val.transform.localRotation = Quaternion.FromToRotation(Vector3.forward, val2);
			LineRenderer val3 = val.AddComponent<LineRenderer>();
			val3.useWorldSpace = false;
			val3.loop = true;
			val3.positionCount = 24;
			float startWidth = (val3.endWidth = 0.025f);
			val3.startWidth = startWidth;
			((Renderer)val3).sharedMaterial = guideMaterial;
			Color startColor = (val3.endColor = Pieces.PortColor(Kind));
			val3.startColor = startColor;
			((Renderer)val3).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)val3).receiveShadows = false;
			for (int j = 0; j < 24; j++)
			{
				float num2 = (float)j * (float)Math.PI / 12f;
				val3.SetPosition(j, new Vector3(Mathf.Cos(num2) * 0.24f, Mathf.Sin(num2) * 0.24f, 0.025f));
			}
			((Renderer)val3).enabled = false;
			endGuides.Add(val3);
		}
	}

	internal void RefreshGuides(bool chest, bool stretch, Vector3 playerPosition)
	{
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: 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)
		//IL_00a3: 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_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		if (chest || stretch)
		{
			BuildEndGuides();
		}
		for (int i = 0; i < endGuides.Count; i++)
		{
			if (Object.op_Implicit((Object)(object)endGuides[i]))
			{
				Transform transform = ((Component)endGuides[i]).transform;
				if (Kind == PipeKind.Stretch || Kind == PipeKind.Chest)
				{
					transform.localPosition = ((Kind == PipeKind.Chest && !stretch) ? LocalDock : Ends[i]);
					transform.localRotation = Quaternion.FromToRotation(Vector3.forward, (Kind == PipeKind.Chest && !stretch) ? Vector3.back : Directions[i]);
				}
				((Renderer)endGuides[i]).enabled = (chest || stretch) && (stretch || !Terminal || Kind == PipeKind.Chest) && Native.Valid((Component)(object)this) && (stretch || SnapGeometry.UprightPort(new Point(transform.forward.x, transform.forward.y, transform.forward.z)) || Mathf.Abs(transform.forward.y) > 0.999f) && !PipeSnapping.Occupied(transform, this);
			}
		}
	}

	public string GetHoverName()
	{
		return PipeLanguage.Text(((Component)this).GetComponent<Piece>().m_name);
	}

	public float GetHoverOffset()
	{
		return 0.3f;
	}

	public string GetHoverText()
	{
		string text = Routing.Status(this);
		return GetHoverName() + "\n<color=#e9ba77>" + text + "</color>";
	}
}
internal static class Pieces
{
	private sealed class FieldInfoCache
	{
		internal readonly FieldInfo Named = AccessTools.Field(typeof(ZNetScene), "m_namedPrefabs");
	}

	private static readonly FieldInfoCache fields = new FieldInfoCache();

	internal static void Register(ZNetScene scene)
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Expected O, but got Unknown
		if (Plugin.Prefabs.Count == 0)
		{
			GameObject prefab = scene.GetPrefab("wood_beam");
			if (!Object.op_Implicit((Object)(object)prefab))
			{
				throw new InvalidOperationException("Vanilla wood_beam prefab not found.");
			}
			Plugin.PrefabRoot = new GameObject("ForgePipes_Prefabs");
			Plugin.PrefabRoot.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)Plugin.PrefabRoot);
			foreach (PipeKind value in Enum.GetValues(typeof(PipeKind)))
			{
				Build(prefab, value, scene);
			}
			Hopper.Register(scene);
		}
		Dictionary<int, GameObject> dictionary = (Dictionary<int, GameObject>)fields.Named.GetValue(scene);
		foreach (GameObject prefab2 in Plugin.Prefabs)
		{
			dictionary[StringExtensionMethods.GetStableHashCode(((Object)prefab2).name)] = prefab2;
			if (!scene.m_prefabs.Contains(prefab2))
			{
				scene.m_prefabs.Add(prefab2);
			}
		}
		AttachHammer();
		Plugin.LogInfo("Registered " + Plugin.Prefabs.Count + " Forge pipe network prefabs.");
	}

	internal static void AttachHammer()
	{
		if (!Object.op_Implicit((Object)(object)ObjectDB.instance))
		{
			return;
		}
		GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Hammer");
		if (!Object.op_Implicit((Object)(object)itemPrefab))
		{
			return;
		}
		PieceTable buildPieces = itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_buildPieces;
		bool flag = false;
		foreach (GameObject prefab in Plugin.Prefabs)
		{
			if (!buildPieces.m_pieces.Contains(prefab))
			{
				buildPieces.m_pieces.Add(prefab);
				flag = true;
			}
		}
		if (flag)
		{
			AccessTools.Field(typeof(ObjectDB), "m_buildPieces").SetValue(ObjectDB.instance, null);
		}
	}

	internal static string Label(PipeKind kind)
	{
		return kind switch
		{
			PipeKind.TightBend => "Forge pipe - tight elbow", 
			PipeKind.TightUpBend => "Forge pipe - tight elbow up", 
			PipeKind.TightDownBend => "Forge pipe - tight elbow down", 
			PipeKind.ShortStraight => "Forge pipe - straight (1m)", 
			PipeKind.Cross => "Forge pipe - four-way junction", 
			PipeKind.SixWay => "Forge pipe - six-way junction", 
			PipeKind.TeeUp => "Forge pipe - tee up", 
			PipeKind.TeeDown => "Forge pipe - tee down", 
			PipeKind.ShortTee => "Forge pipe - short junction", 
			PipeKind.ShortTeeUp => "Forge pipe - short tee up", 
			PipeKind.ShortTeeDown => "Forge pipe - short tee down", 
			PipeKind.ShortCross => "Forge pipe - short four-way junction", 
			PipeKind.ShortSixWay => "Forge pipe - short six-way junction", 
			PipeKind.TeePlusUp => "Forge pipe - four-way tee up", 
			PipeKind.TeePlusDown => "Forge pipe - four-way tee down", 
			PipeKind.ShortTeePlusUp => "Forge pipe - short four-way tee up", 
			PipeKind.ShortTeePlusDown => "Forge pipe - short four-way tee down", 
			PipeKind.CompactChest => "Forge pipe - compact chest connector", 
			PipeKind.CompactInput => "Forge pipe - compact ingredient inlet", 
			PipeKind.CompactFuel => "Forge pipe - compact fuel inlet", 
			PipeKind.CompactOutput => "Forge pipe - compact product outlet", 
			PipeKind.Stretch => "Forge pipe - stretch (0.5-16m)", 
			PipeKind.Straight => "Forge pipe - straight (2m)", 
			PipeKind.Bend => "Forge pipe - elbow", 
			PipeKind.Tee => "Forge pipe - junction", 
			PipeKind.Riser => "Forge pipe - vertical (2m)", 
			PipeKind.UpBend => "Forge pipe - elbow up", 
			PipeKind.DownBend => "Forge pipe - elbow down", 
			PipeKind.Chest => "Forge pipe - chest connector", 
			PipeKind.Input => "Forge pipe - ingredient inlet", 
			PipeKind.Fuel => "Forge pipe - coal inlet", 
			_ => "Forge pipe - product outlet", 
		};
	}

	internal static Color PortColor(PipeKind kind)
	{
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		return (Color)(kind switch
		{
			PipeKind.Output => new Color(0.95f, 0.7f, 0.2f), 
			PipeKind.Fuel => new Color(0.5f, 0.52f, 0.58f), 
			PipeKind.Input => new Color(0.3f, 0.75f, 0.38f), 
			PipeKind.Chest => new Color(0.2f, 0.6f, 0.85f), 
			_ => new Color(0.62f, 0.36f, 0.18f), 
		});
	}

	private static void Build(GameObject source, PipeKind kind, ZNetScene scene)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0218: Unknown result type (might be due to invalid IL or missing references)
		//IL_024a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0251: Unknown result type (might be due to invalid IL or missing references)
		//IL_0232: Unknown result type (might be due to invalid IL or missing references)
		//IL_0239: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_03de: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e5: Expected O, but got Unknown
		//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0294: Unknown result type (might be due to invalid IL or missing references)
		//IL_029b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0428: Unknown result type (might be due to invalid IL or missing references)
		//IL_042d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0434: Unknown result type (might be due to invalid IL or missing references)
		//IL_043b: Expected O, but got Unknown
		//IL_0461: Unknown result type (might be due to invalid IL or missing references)
		//IL_046f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0476: Unknown result type (might be due to invalid IL or missing references)
		//IL_047b: Unknown result type (might be due to invalid IL or missing references)
		//IL_04a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_04a8: Expected O, but got Unknown
		//IL_04ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_04d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_04f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0300: Unknown result type (might be due to invalid IL or missing references)
		//IL_0286: Unknown result type (might be due to invalid IL or missing references)
		//IL_028d: Unknown result type (might be due to invalid IL or missing references)
		//IL_052b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0537: Unknown result type (might be due to invalid IL or missing references)
		//IL_0278: Unknown result type (might be due to invalid IL or missing references)
		//IL_027f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0320: Unknown result type (might be due to invalid IL or missing references)
		//IL_0327: Unknown result type (might be due to invalid IL or missing references)
		//IL_0347: Unknown result type (might be due to invalid IL or missing references)
		//IL_034e: Unknown result type (might be due to invalid IL or missing references)
		//IL_035a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0361: Unknown result type (might be due to invalid IL or missing references)
		//IL_0377: Unknown result type (might be due to invalid IL or missing references)
		//IL_037e: Unknown result type (might be due to invalid IL or missing references)
		//IL_038a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0391: Unknown result type (might be due to invalid IL or missing references)
		//IL_0608: Unknown result type (might be due to invalid IL or missing references)
		//IL_060f: Expected O, but got Unknown
		//IL_063c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0643: Expected O, but got Unknown
		GameObject val = Object.Instantiate<GameObject>(source, Plugin.PrefabRoot.transform, false);
		((Object)val).name = "FP_" + kind;
		val.transform.localPosition = Vector3.zero;
		val.transform.localRotation = Quaternion.identity;
		val.transform.localScale = Vector3.one;
		Renderer componentInChildren = val.GetComponentInChildren<Renderer>(true);
		if (Object.op_Implicit((Object)(object)componentInChildren))
		{
			_ = componentInChildren.sharedMaterial;
		}
		LODGroup[] componentsInChildren = val.GetComponentsInChildren<LODGroup>(true);
		foreach (LODGroup val2 in componentsInChildren)
		{
			Object.DestroyImmediate((Object)(object)val2);
		}
		for (int num = val.transform.childCount - 1; num >= 0; num--)
		{
			Object.DestroyImmediate((Object)(object)((Component)val.transform.GetChild(num)).gameObject);
		}
		Rigidbody[] componentsInChildren2 = val.GetComponentsInChildren<Rigidbody>(true);
		foreach (Rigidbody val3 in componentsInChildren2)
		{
			Object.DestroyImmediate((Object)(object)val3);
		}
		Collider[] components = val.GetComponents<Collider>();
		foreach (Collider val4 in components)
		{
			Object.DestroyImmediate((Object)(object)val4);
		}
		Renderer[] components2 = val.GetComponents<Renderer>();
		foreach (Renderer val5 in components2)
		{
			Object.DestroyImmediate((Object)(object)val5);
		}
		MeshFilter[] components3 = val.GetComponents<MeshFilter>();
		foreach (MeshFilter val6 in components3)
		{
			Object.DestroyImmediate((Object)(object)val6);
		}
		bool flag = kind >= PipeKind.CompactChest && kind <= PipeKind.CompactOutput;
		bool flag2 = kind >= PipeKind.TightBend && kind <= PipeKind.TightDownBend;
		PipeNode pipeNode = val.AddComponent<PipeNode>();
		pipeNode.Kind = (flag ? (6 + kind - 16) : kind);
		bool flag3 = kind == PipeKind.ShortTee || kind == PipeKind.ShortTeeUp || kind == PipeKind.ShortTeeDown || kind == PipeKind.ShortCross || kind == PipeKind.ShortSixWay || kind == PipeKind.ShortTeePlusUp || kind == PipeKind.ShortTeePlusDown;
		float num2 = ((kind == PipeKind.ShortStraight || flag2 || flag3) ? 0.5f : 1f);
		float num3 = (flag ? 0.25f : 0.5f);
		List<Vector3> list = new List<Vector3>();
		if (kind == PipeKind.Riser)
		{
			list.Add(Vector3.down);
			list.Add(Vector3.up);
		}
		else if (pipeNode.Terminal)
		{
			list.Add(Vector3.forward * num3);
		}
		else
		{
			list.Add(Vector3.back * num2);
			Vector3 item;
			switch (kind)
			{
			default:
				item = Vector3.forward * num2;
				break;
			case PipeKind.DownBend:
			case PipeKind.TightDownBend:
				item = Vector3.down * num2;
				break;
			case PipeKind.UpBend:
			case PipeKind.TightUpBend:
				item = Vector3.up * num2;
				break;
			case PipeKind.Bend:
			case PipeKind.TightBend:
				item = Vector3.right * num2;
				break;
			}
			list.Add(item);
			if (kind == PipeKind.Tee || kind == PipeKind.ShortTee || kind == PipeKind.TeePlusUp || kind == PipeKind.TeePlusDown || kind == PipeKind.ShortTeePlusUp || kind == PipeKind.ShortTeePlusDown)
			{
				list.Add(Vector3.right * num2);
			}
			if (kind == PipeKind.TeeUp || kind == PipeKind.ShortTeeUp || kind == PipeKind.TeePlusUp || kind == PipeKind.ShortTeePlusUp)
			{
				list.Add(Vector3.up * num2);
			}
			if (kind == PipeKind.TeeDown || kind == PipeKind.ShortTeeDown || kind == PipeKind.TeePlusDown || kind == PipeKind.ShortTeePlusDown)
			{
				list.Add(Vector3.down * num2);
			}
			if (kind == PipeKind.Cross || kind == PipeKind.SixWay || kind == PipeKind.ShortCross || kind == PipeKind.ShortSixWay)
			{
				list.Add(Vector3.left * num2);
				list.Add(Vector3.right * num2);
			}
			if (kind == PipeKind.SixWay || kind == PipeKind.ShortSixWay)
			{
				list.Add(Vector3.up * num2);
				list.Add(Vector3.down * num2);
			}
		}
		pipeNode.Ends = list.ToArray();
		pipeNode.Directions = list.Select((Vector3 e) => ((Vector3)(ref e)).normalized).ToArray();
		GameObject val7 = new GameObject("pipe_mesh");
		val7.layer = val.layer;
		val7.transform.SetParent(val.transform, false);
		PipeModels.Build(val7, pipeNode, kind, num3);
		Vector3[] ends = pipeNode.Ends;
		for (int num4 = 0; num4 < ends.Length; num4++)
		{
			Vector3 localPosition = ends[num4];
			GameObject val8 = new GameObject("_snap");
			val8.tag = "snappoint";
			val8.transform.SetParent(val.transform, false);
			val8.transform.localPosition = localPosition;
			val8.transform.localRotation = Quaternion.FromToRotation(Vector3.forward, ((Vector3)(ref localPosition)).normalized);
		}
		if (pipeNode.Terminal)
		{
			GameObject val9 = new GameObject("_fp_dock");
			val9.tag = "snappoint";
			val9.transform.SetParent(val.transform, false);
			val9.transform.localPosition = Vector3.back * num3;
			val9.transform.localRotation = Quaternion.Euler(0f, 180f, 0f);
		}
		if (kind == PipeKind.Stretch)
		{
			val.AddComponent<StretchPipe>();
		}
		Piece component = val.GetComponent<Piece>();
		component.m_name = Label(kind);
		MetalPlacement.Apply(component, scene);
		component.m_category = (PieceCategory)1;
		component.m_usage = (UsageTagFlags)2050;
		component.m_description = (pipeNode.Terminal ? "Aim at a matching colored port to snap and align automatically. Connect pipes to the narrow end. Hold the game's alternative-placement control for free placement." : "Aim at a chest port, machine port, or pipe collar to snap automatically. Only physically connected pipes share items.");
		if (kind == PipeKind.Stretch)
		{
			component.m_description = "Click a port or free pipe end to start. Aim to stretch, then click to finish. Select another piece or unequip the Hammer to cancel. One saved piece, up to 16m.";
		}
		if (kind == PipeKind.Straight || kind == PipeKind.ShortStraight)
		{
			component.m_description = PipeLanguage.Token(component.m_description) + " " + PipeLanguage.Token("While snapping to a pipe: [ / ] turn, Page Up / Page Down tilt, backslash resets. Maximum bend: 15 degrees.");
		}
		Sprite val10 = PipeIcons.Render(val7, kind);
		if (Object.op_Implicit((Object)(object)val10))
		{
			component.m_icon = val10;
		}
		component.m_enabled = true;
		component.m_noClipping = false;
		component.m_clipEverything = true;
		component.m_groundOnly = false;
		component.m_groundPiece = false;
		component.m_noInWater = false;
		component.m_craftingStation = scene.GetPrefab("piece_workbench").GetComponent<CraftingStation>();
		Requirement[] array = (Requirement[])(object)new Requirement[2];
		Requirement val11 = new Requirement();
		val11.m_resItem = scene.GetPrefab("Wood").GetComponent<ItemDrop>();
		val11.m_amount = 2;
		val11.m_recover = true;
		array[0] = val11;
		Requirement val12 = new Requirement();
		val12.m_resItem = scene.GetPrefab("Copper").GetComponent<ItemDrop>();
		val12.m_amount = 1;
		val12.m_recover = true;
		array[1] = val12;
		component.m_resources = array;
		WearNTear component2 = val.GetComponent<WearNTear>();
		component2.m_new = val7;
		component2.m_worn = val7;
		component2.m_broken = val7;
		component2.m_wet = null;
		component2.m_noRoofWear = false;
		component2.m_noSupportWear = false;
		component2.m_supports = true;
		component2.m_health = 150f;
		component2.m_autoCreateFragments = false;
		component2.m_fragmentRoots = (GameObject[])(object)new GameObject[0];
		val.SetActive(true);
		Plugin.Prefabs.Add(val);
	}
}
public static class PipeCollisionPath
{
	public const float Tolerance = 0.01f;

	public static int[] Simplify(Point[] points)
	{
		if (points == null || points.Length < 2)
		{
			throw new ArgumentException("A pipe needs two endpoints.");
		}
		List<int> list = new List<int>();
		list.Add(0);
		Split(points, 0, points.Length - 1, list);
		return list.ToArray();
	}

	private static void Split(Point[] p, int first, int last, List<int> keep)
	{
		float num = 0.0001f;
		int num2 = -1;
		PipeRun one = new PipeRun(0, p[first], p[last]);
		for (int i = first + 1; i < last; i++)
		{
			float num3 = BodyConnections.DistanceSquared(one, new PipeRun(1, p[i], p[i]));
			if (num3 > num)
			{
				num = num3;
				num2 = i;
			}
		}
		if (num2 < 0)
		{
			keep.Add(last);
			return;
		}
		Split(p, first, num2, keep);
		Split(p, num2, last, keep);
	}
}
internal static class PipeDeflection
{
	private static Transform ghost;

	private static int frame = -1;

	private static float yaw;

	private static float pitch;

	internal static void Update(Transform current)
	{
		if ((Object)(object)ghost != (Object)(object)current)
		{
			ghost = current;
			yaw = (pitch = 0f);
		}
		if (frame != Time.frameCount)
		{
			frame = Time.frameCount;
			if (ZInput.GetKeyDown((KeyCode)91, false))
			{
				yaw = Mathf.Max(-15f, yaw - 5f);
			}
			if (ZInput.GetKeyDown((KeyCode)93, false))
			{
				yaw = Mathf.Min(15f, yaw + 5f);
			}
			if (ZInput.GetKeyDown((KeyCode)280, false))
			{
				pitch = Mathf.Min(15f, pitch + 5f);
			}
			if (ZInput.GetKeyDown((KeyCode)281, false))
			{
				pitch = Mathf.Max(-15f, pitch - 5f);
			}
			if (ZInput.GetKeyDown((KeyCode)92, false))
			{
				yaw = (pitch = 0f);
			}
		}
	}

	internal static Quaternion Apply(Quaternion aligned, Transform target)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		Quaternion val = Quaternion.AngleAxis(yaw, target.up) * Quaternion.AngleAxis(pitch, target.right);
		float num = Quaternion.Angle(Quaternion.identity, val);
		if (num > 15f)
		{
			val = Quaternion.Slerp(Quaternion.identity, val, 15f / num);
		}
		return val * aligned;
	}
}
internal static class PipeGuideScheduler
{
	private static HashSet<MachinePort> visiblePorts = new HashSet<MachinePort>();

	private static HashSet<MachinePort> nextPorts = new HashSet<MachinePort>();

	private static HashSet<PipeNode> visiblePipes = new HashSet<PipeNode>();

	private static HashSet<PipeNode> nextPipes = new HashSet<PipeNode>();

	private static GameObject lastGhost;

	private static Player lastPlayer;

	private static float nextRefresh;

	private static bool showing;

	internal static void Tick()
	{
		//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		//IL_0127: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: 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_0149: Unknown result type (might be due to invalid IL or missing references)
		//IL_0249: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cb: 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_01d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_029b: Unknown result type (might be due to invalid IL or missing references)
		Player localPlayer = Player.m_localPlayer;
		PipeNode ghostNode = PipeSnapping.GhostNode;
		bool flag = Object.op_Implicit((Object)(object)PipeSnapping.GhostChest);
		bool flag2 = Object.op_Implicit((Object)(object)ghostNode) && ghostNode.Kind == PipeKind.Stretch;
		if (!