Decompiled source of BiomeBlueprints v1.0.3

BiomeBlueprints.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("BiomeBlueprints")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.3.0")]
[assembly: AssemblyInformationalVersion("1.0.3")]
[assembly: AssemblyProduct("BiomeBlueprints")]
[assembly: AssemblyTitle("BiomeBlueprints")]
[assembly: AssemblyVersion("1.0.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Homesteads
{
	public static class CatalogueId
	{
		public const string PiecePrefix = "piece_blueprint:";

		public const string IdPrefix = "hs_";

		public const string Category = "Biome Blueprints";

		public const int TabCapacity = 89;

		public static readonly string[] Kinds = new string[2] { "Furniture", "Decoration" };

		private static readonly Dictionary<string, string> Biomes = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			{ "meadows", "Meadows" },
			{ "blackforest", "BlackForest" },
			{ "swamp", "Swamp" },
			{ "mountain", "Mountain" },
			{ "plains", "Plains" },
			{ "mistlands", "Mistlands" },
			{ "ashlands", "AshLands" },
			{ "deepnorth", "DeepNorth" },
			{ "ocean", "Ocean" }
		};

		public static string CategoryFor(string id)
		{
			string text = TokenOf(id);
			if (text == null)
			{
				return "Biome Blueprints";
			}
			for (int i = 0; i < MaterialTier.Tokens.Length; i++)
			{
				if (string.Equals(MaterialTier.Tokens[i], text, StringComparison.Ordinal))
				{
					return MaterialTier.Biomes[i];
				}
			}
			return "Biome Blueprints";
		}

		public static string TabName(string biome, int index)
		{
			if (index <= 0)
			{
				return biome;
			}
			return index switch
			{
				1 => biome + " II", 
				2 => biome + " III", 
				_ => biome + " " + (index + 1), 
			};
		}

		public static IEnumerable<string> AllTabs()
		{
			string[] biomes = MaterialTier.Biomes;
			for (int i = 0; i < biomes.Length; i++)
			{
				yield return biomes[i];
			}
			biomes = Kinds;
			for (int i = 0; i < biomes.Length; i++)
			{
				yield return biomes[i];
			}
		}

		public static string BiomeOfTab(string tab)
		{
			if (string.IsNullOrEmpty(tab))
			{
				return tab;
			}
			string[] kinds = Kinds;
			foreach (string text in kinds)
			{
				if (string.Equals(tab, text, StringComparison.Ordinal))
				{
					return text;
				}
				if (tab.StartsWith(text + " ", StringComparison.Ordinal))
				{
					return text;
				}
			}
			kinds = MaterialTier.Biomes;
			foreach (string text2 in kinds)
			{
				if (string.Equals(tab, text2, StringComparison.Ordinal))
				{
					return text2;
				}
				if (tab.StartsWith(text2 + " ", StringComparison.Ordinal))
				{
					return text2;
				}
			}
			return tab;
		}

		public static string TokenOf(string id)
		{
			if (!IsOurs(id))
			{
				return null;
			}
			string text = id.Substring("hs_".Length);
			int num = text.IndexOf('_');
			string text2 = ((num < 0) ? text : text.Substring(0, num)).ToLowerInvariant();
			if (!Biomes.ContainsKey(text2))
			{
				return null;
			}
			return text2;
		}

		public static string IdFromPieceName(string pieceName)
		{
			if (string.IsNullOrEmpty(pieceName))
			{
				return null;
			}
			if (!pieceName.StartsWith("piece_blueprint:", StringComparison.Ordinal))
			{
				return null;
			}
			return pieceName.Substring("piece_blueprint:".Length);
		}

		public static bool IsOurs(string id)
		{
			return id?.StartsWith("hs_", StringComparison.Ordinal) ?? false;
		}

		public static string BiomeOf(string id)
		{
			string text = TokenOf(id);
			if (text == null)
			{
				return null;
			}
			if (!Biomes.TryGetValue(text, out var value))
			{
				return null;
			}
			return value;
		}

		public static string BiomeOfPiece(string pieceName)
		{
			return BiomeOf(IdFromPieceName(pieceName));
		}

		public static IEnumerable<string> KnownTokens()
		{
			return Biomes.Keys;
		}
	}
	public static class FlattenToggle
	{
		private static bool applied;

		private static bool broken;

		private static readonly Dictionary<string, object> Saved = new Dictionary<string, object>(StringComparer.Ordinal);

		internal static IEnumerator Watch()
		{
			while (!broken)
			{
				yield return (object)new WaitForSeconds(2f);
				bool flag = Plugin.FlattenGround == null || Plugin.FlattenGround.Value;
				if (flag != !applied)
				{
					Apply(flag);
				}
			}
		}

		private static void Apply(bool flatten)
		{
			try
			{
				Type type = AccessTools.TypeByName("PlanBuild.Blueprints.BlueprintManager");
				FieldInfo fieldInfo = ((type == null) ? null : AccessTools.Field(type, "LocalBlueprints"));
				IDictionary dictionary = ((fieldInfo == null) ? null : (fieldInfo.GetValue(null) as IDictionary));
				if (dictionary == null)
				{
					broken = true;
					Plugin.Log.LogWarning((object)"BiomeBlueprints: PlanBuild's blueprint list was not found, so ground flattening cannot be turned off. Every design will level its own plot.");
					return;
				}
				int num = 0;
				FieldInfo fieldInfo2 = null;
				foreach (DictionaryEntry item in dictionary)
				{
					object value = item.Value;
					if (value == null)
					{
						continue;
					}
					if (fieldInfo2 == null)
					{
						fieldInfo2 = AccessTools.Field(value.GetType(), "TerrainMods");
						if (fieldInfo2 == null)
						{
							broken = true;
							Plugin.Log.LogWarning((object)"BiomeBlueprints: PlanBuild's Blueprint has no TerrainMods field; flattening cannot be toggled.");
							return;
						}
					}
					string text = item.Key as string;
					if (!CatalogueId.IsOurs(text))
					{
						continue;
					}
					if (flatten)
					{
						if (Saved.TryGetValue(text, out var value2))
						{
							fieldInfo2.SetValue(value, value2);
							num++;
						}
					}
					else if (fieldInfo2.GetValue(value) is Array { Length: not 0 } array)
					{
						Saved[text] = array;
						fieldInfo2.SetValue(value, Array.CreateInstance(array.GetType().GetElementType(), 0));
						num++;
					}
				}
				applied = !flatten;
				Plugin.Log.LogInfo((object)("BiomeBlueprints: ground flattening turned " + (flatten ? "ON" : "OFF") + " for " + num + " design(s)."));
			}
			catch (Exception ex)
			{
				broken = true;
				Plugin.Log.LogWarning((object)("BiomeBlueprints: could not change ground flattening - " + ex.Message));
			}
		}
	}
	public static class FlattenWhenNeeded
	{
		private const int Grid = 5;

		private const float AboveGround = 1f;

		private static FieldInfo terrainMods;

		private static object heldBack;

		private static object heldFor;

		private static bool broken;

		private static string lastSaid;

		private static MethodInfo Position;

		private static readonly FieldRef<Player, GameObject> GhostOf = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost");

		internal static void Apply(Harmony harmony)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected O, but got Unknown
			//IL_007e: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("PlanBuild.Blueprints.Components.PlacementComponent");
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "OnPlacePiece", (Type[])null, (Type[])null));
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)"BiomeBlueprints: PlanBuild's PlacementComponent was not found, so every design levels its plot whether or not the ground needs it.");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(FlattenWhenNeeded).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(FlattenWhenNeeded).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void Prefix(Player __0)
		{
			heldBack = null;
			heldFor = null;
			if (broken || (Object)(object)__0 == (Object)null)
			{
				return;
			}
			if (Plugin.FlattenWhenUneven == null || Plugin.FlattenWhenUneven.Value <= 0f)
			{
				Say("the uneven-ground threshold is 0, so terrain is always levelled");
				return;
			}
			if (Plugin.FlattenGround != null && !Plugin.FlattenGround.Value)
			{
				Say("flattening is off in the config");
				return;
			}
			try
			{
				GameObject val = GhostOf.Invoke(__0);
				if ((Object)(object)val == (Object)null)
				{
					Say("no placement ghost");
					return;
				}
				object obj = BlueprintOf(((Object)val).name);
				if (obj == null)
				{
					Say("no blueprint found for ghost \"" + ((Object)val).name + "\" - the terrain markers are PlanBuild's to apply");
					return;
				}
				if (terrainMods == null)
				{
					terrainMods = AccessTools.Field(obj.GetType(), "TerrainMods");
					if (terrainMods == null)
					{
						broken = true;
						Plugin.Log.LogWarning((object)"BiomeBlueprints: PlanBuild's Blueprint has no TerrainMods field; ground is levelled unconditionally.");
						return;
					}
				}
				if (terrainMods.GetValue(obj) is Array { Length: not 0 } array)
				{
					string why;
					Array array2 = Needed(val, array, out why);
					if (array2 == null)
					{
						Say("levelling all " + array.Length + " markers: " + why);
						return;
					}
					heldFor = obj;
					heldBack = array;
					terrainMods.SetValue(obj, array2);
					Say(why + " - " + (array.Length - array2.Length) + " of " + array.Length + " markers held back, " + array2.Length + " applied");
				}
				else
				{
					Say("this design has no terrain markers");
				}
			}
			catch (Exception ex)
			{
				broken = true;
				Plugin.Log.LogWarning((object)("BiomeBlueprints: cannot test the ground - " + ex.Message));
			}
		}

		private static void Say(string what)
		{
			if (!(what == lastSaid))
			{
				lastSaid = what;
				Plugin.Log.LogInfo((object)("BiomeBlueprints: [terrain] " + what));
			}
		}

		private static void Postfix()
		{
			if (heldBack == null || heldFor == null || terrainMods == null)
			{
				return;
			}
			try
			{
				terrainMods.SetValue(heldFor, heldBack);
			}
			catch (Exception ex)
			{
				broken = true;
				Plugin.Log.LogWarning((object)("BiomeBlueprints: could not restore a design's terrain markers - " + ex.Message + ". Flattening is off until you restart."));
			}
			finally
			{
				heldBack = null;
				heldFor = null;
			}
		}

		private static Array Needed(GameObject ghost, Array mods, out string why)
		{
			//IL_0021: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			why = null;
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance == (Object)null)
			{
				why = "no ZoneSystem to measure against";
				return null;
			}
			Vector3 position = ghost.transform.position;
			float groundHeight = instance.GetGroundHeight(position);
			float num = position.y - groundHeight;
			if (num > 1f)
			{
				why = "placed " + num.ToString("0.0") + "m above the ground, so it is standing on something built";
				return Array.CreateInstance(mods.GetType().GetElementType(), 0);
			}
			if (Position == null)
			{
				object value = mods.GetValue(0);
				if (value == null)
				{
					why = "a terrain marker was null";
					return null;
				}
				Position = AccessTools.Method(value.GetType(), "GetPosition", (Type[])null, (Type[])null);
				if (Position == null)
				{
					why = "PlanBuild's TerrainModEntry has no GetPosition, so every marker is applied";
					return null;
				}
			}
			float value2 = Plugin.FlattenWhenUneven.Value;
			List<object> list = new List<object>(mods.Length);
			float num2 = 0f;
			foreach (object mod in mods)
			{
				if (mod != null)
				{
					Vector3 val = (Vector3)Position.Invoke(mod, null);
					Vector3 val2 = ghost.transform.TransformPoint(val);
					float num3 = Mathf.Abs(val2.y - instance.GetGroundHeight(val2));
					if (num3 > num2)
					{
						num2 = num3;
					}
					if (num3 > value2)
					{
						list.Add(mod);
					}
				}
			}
			if (list.Count == mods.Length)
			{
				why = "every marker moves ground, worst " + num2.ToString("0.00") + "m";
				return null;
			}
			Array array = Array.CreateInstance(mods.GetType().GetElementType(), list.Count);
			for (int i = 0; i < list.Count; i++)
			{
				array.SetValue(list[i], i);
			}
			why = ((list.Count == 0) ? ("the ground already matches this design within " + value2.ToString("0.00") + "m") : ("part of the plot already matches, worst gap " + num2.ToString("0.00") + "m"));
			return array;
		}

		private static object BlueprintOf(string ghostName)
		{
			string text = CatalogueId.IdFromPieceName(Strip(ghostName));
			if (text == null)
			{
				return null;
			}
			Type type = AccessTools.TypeByName("PlanBuild.Blueprints.BlueprintManager");
			FieldInfo fieldInfo = ((type == null) ? null : AccessTools.Field(type, "LocalBlueprints"));
			IDictionary dictionary = ((fieldInfo == null) ? null : (fieldInfo.GetValue(null) as IDictionary));
			if (dictionary == null)
			{
				return null;
			}
			if (!dictionary.Contains(text))
			{
				return null;
			}
			return dictionary[text];
		}

		private static string Strip(string name)
		{
			if (name == null)
			{
				return null;
			}
			int num = name.IndexOf('(');
			if (num <= 0)
			{
				return name;
			}
			return name.Substring(0, num).Trim();
		}
	}
	[HarmonyPatch(typeof(Player), "SetupPlacementGhost")]
	public static class GhostBatching
	{
		private sealed class Plan
		{
			public int ChildCount;

			public List<int> DoomedAt;

			public List<KeyValuePair<int, int[]>> Partial;

			public readonly List<Material> Materials = new List<Material>();

			public readonly List<Mesh> Meshes = new List<Mesh>();
		}

		private sealed class Group
		{
			public Material Material;

			public readonly List<CombineInstance> Parts = new List<CombineInstance>();
		}

		private const int MinRenderers = 200;

		private const int MinPieces = 100;

		private static readonly FieldRef<Player, GameObject> GhostOf = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost");

		private static readonly FieldRef<Player, PieceTable> BuildPiecesOf = AccessTools.FieldRefAccess<Player, PieceTable>("m_buildPieces");

		private static readonly Dictionary<string, Material> Canonical = new Dictionary<string, Material>(StringComparer.Ordinal);

		private static bool failed;

		private static bool dumped;

		private const string MergedName = "HomesteadsMerged";

		private static readonly HashSet<string> Welding = new HashSet<string>(StringComparer.Ordinal);

		private static void Postfix(Player __instance)
		{
			if (failed || (Object)(object)__instance == (Object)null)
			{
				return;
			}
			try
			{
				GameObject val = GhostOf.Invoke(__instance);
				if (!((Object)(object)val == (Object)null) && ((Object)val).name.StartsWith("piece_blueprint", StringComparison.Ordinal))
				{
					DumpOnce(val);
					PieceTable val2 = BuildPiecesOf.Invoke(__instance);
					GameObject val3 = (((Object)(object)val2 == (Object)null) ? null : val2.GetSelectedPrefab());
					if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3 == (Object)(object)val) && !Welded(val3) && !Welding.Contains(((Object)val3).name) && val3.transform.childCount >= 100)
					{
						Weld(val3, val);
					}
				}
			}
			catch (Exception ex)
			{
				failed = true;
				Plugin.Log.LogWarning((object)("BiomeBlueprints: ghost batching disabled - " + ex.Message));
			}
		}

		private static void DumpOnce(GameObject ghost)
		{
			if (dumped)
			{
				return;
			}
			dumped = true;
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine(((Object)ghost).name);
				stringBuilder.AppendLine("transforms=" + ghost.GetComponentsInChildren<Transform>(true).Length);
				stringBuilder.AppendLine("pieces(direct children)=" + ghost.transform.childCount);
				stringBuilder.AppendLine("lodGroups=" + ghost.GetComponentsInChildren<LODGroup>(true).Length);
				int num = 0;
				int num2 = 0;
				int num3 = 0;
				int num4 = 0;
				long num5 = 0L;
				MeshFilter[] componentsInChildren = ghost.GetComponentsInChildren<MeshFilter>(true);
				foreach (MeshFilter val in componentsInChildren)
				{
					Mesh val2 = (((Object)(object)val == (Object)null) ? null : val.sharedMesh);
					if (!((Object)(object)val2 == (Object)null))
					{
						MeshRenderer component = ((Component)val).GetComponent<MeshRenderer>();
						if ((Object)(object)component != (Object)null && ((Renderer)component).enabled && ((Component)val).gameObject.activeInHierarchy)
						{
							num++;
							num5 += val2.vertexCount;
						}
						else
						{
							num2++;
						}
						if (val2.isReadable)
						{
							num3++;
						}
						else
						{
							num4++;
						}
					}
				}
				stringBuilder.AppendLine("renderers active=" + num + " inactive=" + num2);
				stringBuilder.AppendLine("meshes readable=" + num3 + " unreadable=" + num4);
				stringBuilder.AppendLine("active vertices=" + num5);
				if (ghost.transform.childCount > 0)
				{
					Describe(stringBuilder, ghost.transform.GetChild(0), 0);
				}
				File.WriteAllText(Path.Combine(Paths.BepInExRootPath, "biomeblueprints-ghost.txt"), stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("BiomeBlueprints: could not dump ghost stats - " + ex.Message));
			}
		}

		private static void Describe(StringBuilder sb, Transform t, int depth)
		{
			if (depth <= 4)
			{
				MeshFilter component = ((Component)t).GetComponent<MeshFilter>();
				MeshRenderer component2 = ((Component)t).GetComponent<MeshRenderer>();
				sb.Append(new string(' ', depth * 2)).Append(((Object)t).name);
				if (!((Component)t).gameObject.activeSelf)
				{
					sb.Append(" [inactive]");
				}
				if ((Object)(object)component2 != (Object)null)
				{
					sb.Append(" [renderer]");
				}
				if ((Object)(object)component != (Object)null && (Object)(object)component.sharedMesh != (Object)null)
				{
					sb.Append(" verts=").Append(component.sharedMesh.vertexCount);
				}
				if ((Object)(object)((Component)t).GetComponent<LODGroup>() != (Object)null)
				{
					sb.Append(" [lodgroup]");
				}
				sb.AppendLine();
				for (int i = 0; i < t.childCount; i++)
				{
					Describe(sb, t.GetChild(i), depth + 1);
				}
			}
		}

		private static bool Welded(GameObject root)
		{
			Transform transform = root.transform;
			for (int i = 0; i < transform.childCount; i++)
			{
				if (((Object)transform.GetChild(i)).name == "HomesteadsMerged")
				{
					return true;
				}
			}
			return false;
		}

		private static void Weld(GameObject prefab, GameObject ghost)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Expected O, but got Unknown
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			string name = ((Object)prefab).name;
			Welding.Add(name);
			List<GameObject> list = new List<GameObject>();
			try
			{
				Transform transform = prefab.transform;
				int childCount = transform.childCount;
				Matrix4x4 worldToLocalMatrix = transform.worldToLocalMatrix;
				Dictionary<string, Group> dictionary = new Dictionary<string, Group>(StringComparer.Ordinal);
				List<GameObject> list2 = new List<GameObject>();
				List<int> list3 = new List<int>();
				List<GameObject> list4 = new List<GameObject>();
				List<KeyValuePair<int, int[]>> list5 = new List<KeyValuePair<int, int[]>>();
				Stopwatch stopwatch = Stopwatch.StartNew();
				for (int i = 0; i < childCount; i++)
				{
					GameObject gameObject = ((Component)transform.GetChild(i)).gameObject;
					if (IsScaffolding(gameObject))
					{
						continue;
					}
					List<MeshRenderer> list6 = Drawn(gameObject);
					if (list6.Count == 0)
					{
						continue;
					}
					if (Mergeable(list6))
					{
						foreach (MeshRenderer item in list6)
						{
							Gather(dictionary, item, worldToLocalMatrix);
						}
						list2.Add(gameObject);
						list3.Add(i);
						continue;
					}
					list4.Add(gameObject);
					if (AnyReadable(list6))
					{
						int[] array = MergeReadable(gameObject, list6, dictionary, worldToLocalMatrix);
						if (array != null)
						{
							list5.Add(new KeyValuePair<int, int[]>(i, array));
						}
					}
				}
				if (list2.Count == 0 && list5.Count == 0)
				{
					return;
				}
				long elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
				Plan plan = new Plan
				{
					ChildCount = childCount,
					DoomedAt = list3,
					Partial = list5
				};
				foreach (Group value in dictionary.Values)
				{
					Mesh val = new Mesh();
					val.indexFormat = (IndexFormat)1;
					val.CombineMeshes(value.Parts.ToArray(), true, true);
					plan.Materials.Add(value.Material);
					plan.Meshes.Add(val);
					list.Add(Hold(transform, value.Material, val, active: false));
				}
				foreach (GameObject item2 in list)
				{
					item2.SetActive(true);
				}
				list.Clear();
				foreach (KeyValuePair<int, int[]> item3 in list5)
				{
					foreach (MeshRenderer item4 in RenderersToHide(transform, item3))
					{
						((Renderer)item4).enabled = false;
					}
				}
				foreach (GameObject item5 in list2)
				{
					Object.Destroy((Object)(object)item5);
				}
				if ((Object)(object)ghost != (Object)null && !Apply(ghost, plan))
				{
					Plugin.Log.LogWarning((object)"BiomeBlueprints: ghost no longer matches its prefab - it stays unwelded until the next selection");
				}
				Plugin.Log.LogInfo((object)("BiomeBlueprints: " + ((Object)prefab).name + " - merged " + list2.Count + " pieces into " + plan.Meshes.Count + " meshes over " + stopwatch.ElapsedMilliseconds + "ms (walk " + elapsedMilliseconds + "ms, " + list4.Count + " kept, " + list5.Count + " partly merged)"));
				Collapse(list4);
			}
			finally
			{
				foreach (GameObject item6 in list)
				{
					if ((Object)(object)item6 != (Object)null)
					{
						Object.Destroy((Object)(object)item6);
					}
				}
				Welding.Remove(name);
			}
		}

		private static bool AnyReadable(List<MeshRenderer> drawn)
		{
			foreach (MeshRenderer item in drawn)
			{
				if ((Object)(object)ReadableMesh(item) != (Object)null)
				{
					return true;
				}
			}
			return false;
		}

		private static IEnumerable<MeshRenderer> RenderersToHide(Transform root, KeyValuePair<int, int[]> entry)
		{
			if (entry.Key >= root.childCount)
			{
				yield break;
			}
			MeshRenderer[] renderers = ((Component)root.GetChild(entry.Key)).GetComponentsInChildren<MeshRenderer>(true);
			int[] value = entry.Value;
			foreach (int num in value)
			{
				if (num >= 0 && num < renderers.Length && (Object)(object)renderers[num] != (Object)null)
				{
					yield return renderers[num];
				}
			}
		}

		private static GameObject Hold(Transform root, Material material, Mesh mesh, bool active)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: 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_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			GameObject val = new GameObject("HomesteadsMerged", new Type[2]
			{
				typeof(MeshFilter),
				typeof(MeshRenderer)
			});
			val.SetActive(active);
			val.transform.SetParent(root, false);
			val.GetComponent<MeshFilter>().sharedMesh = mesh;
			MeshRenderer component = val.GetComponent<MeshRenderer>();
			((Renderer)component).sharedMaterial = material;
			((Renderer)component).shadowCastingMode = (ShadowCastingMode)0;
			return val;
		}

		private static bool Apply(GameObject ghost, Plan plan)
		{
			if (plan == null)
			{
				return false;
			}
			Transform transform = ghost.transform;
			if (transform.childCount != plan.ChildCount)
			{
				Plugin.Log.LogWarning((object)("BiomeBlueprints: ghost has " + transform.childCount + " children but its prefab had " + plan.ChildCount + " - welding it separately"));
				return false;
			}
			HashSet<int> hashSet = new HashSet<int>(plan.DoomedAt);
			List<GameObject> list = new List<GameObject>(plan.DoomedAt.Count);
			List<GameObject> list2 = new List<GameObject>();
			for (int i = 0; i < transform.childCount; i++)
			{
				GameObject gameObject = ((Component)transform.GetChild(i)).gameObject;
				if (hashSet.Contains(i))
				{
					list.Add(gameObject);
				}
				else if (!IsScaffolding(gameObject))
				{
					list2.Add(gameObject);
				}
			}
			for (int j = 0; j < plan.Meshes.Count; j++)
			{
				Hold(transform, plan.Materials[j], plan.Meshes[j], active: true);
			}
			foreach (GameObject item in list)
			{
				Object.Destroy((Object)(object)item);
			}
			foreach (KeyValuePair<int, int[]> item2 in plan.Partial)
			{
				if (item2.Key >= plan.ChildCount)
				{
					continue;
				}
				MeshRenderer[] componentsInChildren = ((Component)transform.GetChild(item2.Key)).GetComponentsInChildren<MeshRenderer>(true);
				int[] value = item2.Value;
				foreach (int num in value)
				{
					if (num >= 0 && num < componentsInChildren.Length)
					{
						((Renderer)componentsInChildren[num]).enabled = false;
					}
				}
			}
			Collapse(list2);
			return true;
		}

		private static bool IsScaffolding(GameObject piece)
		{
			switch (((Object)piece).name)
			{
			case "HomesteadsMerged":
			case "place_collider":
			case "_GhostOnly":
				return true;
			default:
				return piece.CompareTag("snappoint");
			}
		}

		private static List<MeshRenderer> Drawn(GameObject piece)
		{
			List<MeshRenderer> list = new List<MeshRenderer>();
			HashSet<int> hashSet = new HashSet<int>();
			LODGroup[] componentsInChildren = piece.GetComponentsInChildren<LODGroup>(true);
			foreach (LODGroup val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				LOD[] lODs = val.GetLODs();
				for (int j = 0; j < lODs.Length; j++)
				{
					Renderer[] renderers = lODs[j].renderers;
					foreach (Renderer val2 in renderers)
					{
						if ((Object)(object)val2 == (Object)null)
						{
							continue;
						}
						hashSet.Add(((Object)val2).GetInstanceID());
						if (j == 0)
						{
							MeshRenderer val3 = (MeshRenderer)(object)((val2 is MeshRenderer) ? val2 : null);
							if ((Object)(object)val3 != (Object)null)
							{
								list.Add(val3);
							}
						}
					}
				}
			}
			MeshRenderer[] componentsInChildren2 = piece.GetComponentsInChildren<MeshRenderer>(true);
			foreach (MeshRenderer val4 in componentsInChildren2)
			{
				if (!((Object)(object)val4 == (Object)null) && !hashSet.Contains(((Object)val4).GetInstanceID()) && ((Renderer)val4).enabled && ActiveUnder(((Component)val4).transform, piece.transform.parent))
				{
					list.Add(val4);
				}
			}
			return list;
		}

		private static bool ActiveUnder(Transform t, Transform stop)
		{
			while ((Object)(object)t != (Object)null && (Object)(object)t != (Object)(object)stop)
			{
				if (!((Component)t).gameObject.activeSelf)
				{
					return false;
				}
				t = t.parent;
			}
			return true;
		}

		private static bool Mergeable(List<MeshRenderer> drawn)
		{
			foreach (MeshRenderer item in drawn)
			{
				if ((Object)(object)ReadableMesh(item) == (Object)null)
				{
					return false;
				}
			}
			return true;
		}

		private static Mesh ReadableMesh(MeshRenderer renderer)
		{
			MeshFilter component = ((Component)renderer).GetComponent<MeshFilter>();
			Mesh val = (((Object)(object)component == (Object)null) ? null : component.sharedMesh);
			if (!((Object)(object)val != (Object)null) || !val.isReadable)
			{
				return null;
			}
			return val;
		}

		private static int[] MergeReadable(GameObject piece, List<MeshRenderer> drawn, Dictionary<string, Group> groups, Matrix4x4 toRoot)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			MeshRenderer[] componentsInChildren = piece.GetComponentsInChildren<MeshRenderer>(true);
			List<int> list = new List<int>();
			foreach (MeshRenderer item in drawn)
			{
				if (!((Object)(object)ReadableMesh(item) == (Object)null))
				{
					int num = Array.IndexOf(componentsInChildren, item);
					if (num >= 0)
					{
						Gather(groups, item, toRoot);
						((Renderer)item).enabled = false;
						list.Add(num);
					}
				}
			}
			if (list.Count != 0)
			{
				return list.ToArray();
			}
			return null;
		}

		private static void Gather(Dictionary<string, Group> groups, MeshRenderer renderer, Matrix4x4 toRoot)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			MeshFilter component = ((Component)renderer).GetComponent<MeshFilter>();
			Mesh val = (((Object)(object)component == (Object)null) ? null : component.sharedMesh);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Material[] sharedMaterials = ((Renderer)renderer).sharedMaterials;
			if (sharedMaterials == null)
			{
				return;
			}
			Matrix4x4 transform = toRoot * ((Component)renderer).transform.localToWorldMatrix;
			for (int i = 0; i < val.subMeshCount && i < sharedMaterials.Length; i++)
			{
				Material val2 = sharedMaterials[i];
				if (!((Object)(object)val2 == (Object)null))
				{
					string key = KeyOf(val2);
					if (!groups.TryGetValue(key, out var value))
					{
						value = (groups[key] = new Group
						{
							Material = val2
						});
					}
					List<CombineInstance> parts = value.Parts;
					CombineInstance item = default(CombineInstance);
					((CombineInstance)(ref item)).mesh = val;
					((CombineInstance)(ref item)).subMeshIndex = i;
					((CombineInstance)(ref item)).transform = transform;
					parts.Add(item);
				}
			}
		}

		private static void Collapse(List<GameObject> pieces)
		{
			if (pieces.Count == 0)
			{
				return;
			}
			List<MeshRenderer> list = new List<MeshRenderer>();
			foreach (GameObject piece in pieces)
			{
				if ((Object)(object)piece != (Object)null)
				{
					list.AddRange(piece.GetComponentsInChildren<MeshRenderer>(true));
				}
			}
			if (list.Count < 200)
			{
				return;
			}
			int num = 0;
			foreach (MeshRenderer item in list)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				Material[] sharedMaterials = ((Renderer)item).sharedMaterials;
				if (sharedMaterials == null)
				{
					continue;
				}
				bool flag = false;
				for (int i = 0; i < sharedMaterials.Length; i++)
				{
					Material val = sharedMaterials[i];
					if (!((Object)(object)val == (Object)null))
					{
						string key = KeyOf(val);
						if (!Canonical.TryGetValue(key, out var value) || (Object)(object)value == (Object)null)
						{
							val.enableInstancing = true;
							Canonical[key] = val;
						}
						else if (!((Object)(object)value == (Object)(object)val))
						{
							sharedMaterials[i] = value;
							flag = true;
							num++;
						}
					}
				}
				if (flag)
				{
					((Renderer)item).sharedMaterials = sharedMaterials;
				}
			}
			if (num > 0)
			{
				Plugin.Log.LogInfo((object)("BiomeBlueprints: collapsed " + num + " duplicate materials across " + list.Count + " renderers the merge could not take"));
			}
		}

		private static string KeyOf(Material material)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			Shader shader = material.shader;
			Texture val = (material.HasProperty("_MainTex") ? material.mainTexture : null);
			string text = (material.HasProperty("_Color") ? ((object)material.color/*cast due to .constrained prefix*/).ToString() : "");
			return ((Object)material).name + "|" + ((!((Object)(object)shader == (Object)null)) ? ((Object)shader).GetInstanceID() : 0) + "|" + ((!((Object)(object)val == (Object)null)) ? ((Object)val).GetInstanceID() : 0) + "|" + text + "|" + material.renderQueue;
		}
	}
	public static class IconGrid
	{
		public struct Layout
		{
			public int PerRow;

			public int Rows;

			public float Pitch;

			public float Scale;

			public float Width => (float)PerRow * Pitch;
		}

		public const int MaxRows = 2;

		public const float WidthBudget = 2f;

		public const float MinScale = 0.4f;

		public static Layout For(int count, float pitchFull, float parentWidth)
		{
			if (count < 1)
			{
				count = 1;
			}
			if (pitchFull < 1f)
			{
				pitchFull = 66f;
			}
			if (parentWidth < 1f)
			{
				parentWidth = pitchFull;
			}
			float num = parentWidth * 2f;
			int val = Math.Max(1, (int)(num / pitchFull));
			int num2 = Math.Max((count + 2 - 1) / 2, Math.Min(count, val));
			float num3 = Math.Min(pitchFull, num / (float)num2);
			if (num3 < pitchFull * 0.4f)
			{
				num3 = pitchFull * 0.4f;
			}
			return new Layout
			{
				PerRow = num2,
				Rows = (count + num2 - 1) / num2,
				Pitch = num3,
				Scale = num3 / pitchFull
			};
		}
	}
	[HarmonyPatch(typeof(PieceTable), "UpdateAvailable")]
	public static class KnownMaterials
	{
		private static readonly FieldRef<PieceTable, List<List<Piece>>> AvailablePieces = AccessTools.FieldRefAccess<PieceTable, List<List<Piece>>>("m_availablePieces");

		private static int calls;

		private static double totalMs;

		private static double worstMs;

		private static float nextReport;

		private static readonly Dictionary<string, List<string>> Gates = new Dictionary<string, List<string>>(StringComparer.Ordinal);

		private static readonly Dictionary<string, string> Names = new Dictionary<string, string>(StringComparer.Ordinal);

		private static void Postfix(PieceTable __instance, bool noPlacementCost)
		{
			if (noPlacementCost)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)__instance == (Object)null)
			{
				return;
			}
			List<List<Piece>> list;
			try
			{
				list = AvailablePieces.Invoke(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("BiomeBlueprints: cannot read m_availablePieces, filter inactive - " + ex.Message));
				return;
			}
			if (list == null)
			{
				return;
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			int num = 0;
			foreach (List<Piece> item in list)
			{
				if (item == null)
				{
					continue;
				}
				for (int num2 = item.Count - 1; num2 >= 0; num2--)
				{
					Piece val = item[num2];
					if (!((Object)(object)val == (Object)null) && CatalogueId.IsOurs(CatalogueId.IdFromPieceName(((Object)((Component)val).gameObject).name)) && !Known(localPlayer, ((Object)((Component)val).gameObject).name, val.m_description))
					{
						item.RemoveAt(num2);
						num++;
					}
				}
			}
			if (num > 0)
			{
				Repair(__instance, list);
			}
			Report(stopwatch.Elapsed.TotalMilliseconds);
		}

		private static void Repair(PieceTable table, List<List<Piece>> categories)
		{
			//IL_007a: 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_007f: Unknown result type (might be due to invalid IL or missing references)
			Vector2Int[] selectedPiece = table.m_selectedPiece;
			if (selectedPiece == null)
			{
				return;
			}
			for (int i = 0; i < categories.Count && i < selectedPiece.Length; i++)
			{
				List<Piece> list = categories[i];
				if (list != null && list.Count != 0)
				{
					int num = ((Vector2Int)(ref selectedPiece[i])).y * 15 + ((Vector2Int)(ref selectedPiece[i])).x;
					if (num < 0 || num >= list.Count)
					{
						int num2 = list.Count - 1;
						selectedPiece[i] = (Vector2Int)((num2 < 0) ? Vector2Int.zero : new Vector2Int(num2 % 15, num2 / 15));
						Plugin.Log.LogInfo((object)("BiomeBlueprints: the selection in category " + i + " pointed at slot " + num + " of a " + list.Count + "-piece list after filtering; moved it back inside."));
					}
				}
			}
		}

		private static void Report(double elapsed)
		{
			calls++;
			totalMs += elapsed;
			if (elapsed > worstMs)
			{
				worstMs = elapsed;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (nextReport == 0f)
			{
				nextReport = realtimeSinceStartup + 120f;
			}
			else if (!(realtimeSinceStartup < nextReport))
			{
				nextReport = realtimeSinceStartup + 120f;
				Plugin.Log.LogInfo((object)("BiomeBlueprints: piece-list filter ran " + calls + " times, " + totalMs.ToString("0.0") + "ms total, worst " + worstMs.ToString("0.0") + "ms"));
			}
		}

		private static bool Known(Player player, string key, string description)
		{
			foreach (string item in GateOf(key, description))
			{
				if (!player.IsMaterialKnown(item))
				{
					return false;
				}
			}
			return true;
		}

		private static List<string> GateOf(string key, string description)
		{
			if (Gates.TryGetValue(key ?? "", out var value))
			{
				return value;
			}
			List<string> list = new List<string>();
			List<KeyValuePair<string, int>> list2 = new List<KeyValuePair<string, int>>();
			foreach (MaterialList.Entry item in MaterialList.Parse(description, 0))
			{
				list2.Add(new KeyValuePair<string, int>(item.Item, item.Amount));
			}
			foreach (string item2 in MaterialTier.Significant(list2))
			{
				string text = MaterialNameOf(item2);
				if (text != null)
				{
					list.Add(text);
				}
			}
			Gates[key ?? ""] = list;
			return list;
		}

		private static string MaterialNameOf(string prefabName)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Invalid comparison between Unknown and I4
			if (Names.TryGetValue(prefabName, out var value))
			{
				return value;
			}
			string text = null;
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance != (Object)null)
			{
				GameObject itemPrefab = instance.GetItemPrefab(prefabName);
				if ((Object)(object)itemPrefab != (Object)null)
				{
					ItemDrop component = itemPrefab.GetComponent<ItemDrop>();
					if ((Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null && (int)component.m_itemData.m_shared.m_itemType == 1)
					{
						text = component.m_itemData.m_shared.m_name;
					}
				}
			}
			if ((Object)(object)instance != (Object)null)
			{
				Names[prefabName] = text;
			}
			return text;
		}
	}
	[HarmonyPatch(typeof(Hud), "SetupPieceInfo")]
	public static class MaterialIcons
	{
		private sealed class Plan
		{
			public string Head;

			public Requirement[] Rows;
		}

		private const int MaxCells = 60;

		private static bool failed;

		private const float RefreshSeconds = 0.2f;

		private static string shownFor;

		private static float shownAt;

		private static readonly List<GameObject> Cells = new List<GameObject>();

		private static readonly Dictionary<string, Plan> Plans = new Dictionary<string, Plan>(StringComparer.Ordinal);

		private static bool dumped;

		private static void Postfix(Piece piece)
		{
			if (failed)
			{
				return;
			}
			try
			{
				Decorate(piece);
			}
			catch (Exception ex)
			{
				failed = true;
				Hide();
				Plugin.Log.LogWarning((object)("BiomeBlueprints: material icons disabled - " + ex.Message));
			}
		}

		private static void Decorate(Piece piece)
		{
			Hud instance = Hud.instance;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)instance == (Object)null || (Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			GameObject[] requirementItems = instance.m_requirementItems;
			if (requirementItems == null || requirementItems.Length == 0 || (Object)(object)requirementItems[0] == (Object)null)
			{
				return;
			}
			string text = (((Object)(object)piece == (Object)null) ? null : piece.m_description);
			if (string.IsNullOrEmpty(text) || text.IndexOf("Materials", StringComparison.Ordinal) < 0)
			{
				Hide();
				shownFor = null;
				return;
			}
			DumpGeometryOnce(requirementItems);
			Plan plan = PlanFor(text);
			if ((Object)(object)instance.m_pieceDescription != (Object)null)
			{
				instance.m_pieceDescription.text = plan.Head;
			}
			float unscaledTime = Time.unscaledTime;
			if (!(text == shownFor) || !(unscaledTime - shownAt < 0.2f))
			{
				shownFor = text;
				shownAt = unscaledTime;
				Fill(plan.Rows, requirementItems, localPlayer);
			}
		}

		private static void Fill(Requirement[] rows, GameObject[] slots, Player player)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: 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_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = slots[0];
			RectTransform val = (RectTransform)obj.transform;
			Transform parent = ((Transform)val).parent;
			RectTransform val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			Vector2 anchoredPosition = val.anchoredPosition;
			float num = ((slots.Length > 1 && (Object)(object)slots[1] != (Object)null) ? (((RectTransform)slots[1].transform).anchoredPosition.x - anchoredPosition.x) : 0f);
			Rect rect;
			if (num < 1f)
			{
				rect = val.rect;
				num = ((Rect)(ref rect)).width + 2f;
			}
			if (num < 1f)
			{
				num = 66f;
			}
			float num2;
			if ((Object)(object)val2 != (Object)null)
			{
				rect = val2.rect;
				if (((Rect)(ref rect)).width > 1f)
				{
					rect = val2.rect;
					num2 = ((Rect)(ref rect)).width;
					goto IL_00bb;
				}
			}
			num2 = num * (float)slots.Length;
			goto IL_00bb;
			IL_00bb:
			float parentWidth = num2;
			int num3 = rows.Length;
			IconGrid.Layout layout = IconGrid.For(num3, num, parentWidth);
			int perRow = layout.PerRow;
			float pitch = layout.Pitch;
			float num4 = anchoredPosition.x + (float)(slots.Length - 1) * num * 0.5f;
			Grow(obj, num3);
			for (int i = 0; i < Cells.Count; i++)
			{
				GameObject val3 = Cells[i];
				if ((Object)(object)val3 == (Object)null)
				{
					continue;
				}
				if (i >= num3)
				{
					val3.SetActive(false);
					continue;
				}
				val3.SetActive(true);
				InventoryGui.SetupRequirement(val3.transform, rows[i], player, false, 0, 1);
				Transform val4 = val3.transform.Find("res_name");
				if ((Object)(object)val4 != (Object)null)
				{
					((Component)val4).gameObject.SetActive(false);
				}
				int num5 = i / perRow;
				int num6 = i % perRow;
				int num7 = Math.Min(perRow, num3 - num5 * perRow);
				RectTransform val5 = (RectTransform)val3.transform;
				((Transform)val5).localScale = new Vector3(layout.Scale, layout.Scale, 1f);
				val5.anchoredPosition = new Vector2(num4 - (float)(num7 - 1) * pitch * 0.5f + (float)num6 * pitch, anchoredPosition.y - (float)num5 * pitch);
			}
		}

		private static void Grow(GameObject template, int needed)
		{
			if (needed > 60)
			{
				needed = 60;
			}
			while (Cells.Count < needed)
			{
				GameObject val = Object.Instantiate<GameObject>(template, template.transform.parent, false);
				((Object)val).name = "HomesteadsMaterial" + Cells.Count;
				Cells.Add(val);
			}
		}

		private static void Hide()
		{
			for (int i = 0; i < Cells.Count; i++)
			{
				if ((Object)(object)Cells[i] != (Object)null)
				{
					Cells[i].SetActive(false);
				}
			}
		}

		private static Plan PlanFor(string raw)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			if (Plans.TryGetValue(raw, out var value))
			{
				return value;
			}
			List<Requirement> list = new List<Requirement>();
			int num = 0;
			foreach (MaterialList.Entry item in MaterialList.Parse(raw, 0))
			{
				num++;
				if (list.Count < 60)
				{
					ItemDrop val = ItemFor(item.Item);
					if (!((Object)(object)val == (Object)null))
					{
						list.Add(new Requirement
						{
							m_resItem = val,
							m_amount = item.Amount,
							m_recover = false
						});
					}
				}
			}
			string text = MaterialList.HeadOf(raw);
			int num2 = num - list.Count;
			if (num2 > 0)
			{
				text = text + "<br>+ " + num2 + " more";
			}
			Plan plan = new Plan
			{
				Head = text,
				Rows = list.ToArray()
			};
			Plans[raw] = plan;
			return plan;
		}

		private static ItemDrop ItemFor(string item)
		{
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			GameObject itemPrefab = instance.GetItemPrefab(item);
			if (!((Object)(object)itemPrefab == (Object)null))
			{
				return itemPrefab.GetComponent<ItemDrop>();
			}
			return null;
		}

		private static void DumpGeometryOnce(GameObject[] slots)
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: 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_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
			if (dumped)
			{
				return;
			}
			dumped = true;
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				Transform parent = slots[0].transform.parent;
				RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
				stringBuilder.AppendLine("slots=" + slots.Length);
				Rect rect;
				if ((Object)(object)val != (Object)null)
				{
					string[] obj = new string[8]
					{
						"parent=",
						((Object)val).name,
						" size=",
						null,
						null,
						null,
						null,
						null
					};
					rect = val.rect;
					obj[3] = ((Rect)(ref rect)).width.ToString();
					obj[4] = "x";
					rect = val.rect;
					obj[5] = ((Rect)(ref rect)).height.ToString();
					obj[6] = " layoutGroup=";
					obj[7] = ((Object)(object)((Component)val).GetComponent<LayoutGroup>() != (Object)null).ToString();
					stringBuilder.AppendLine(string.Concat(obj));
				}
				for (int i = 0; i < slots.Length && i < 12; i++)
				{
					if ((Object)(object)slots[i] == (Object)null)
					{
						stringBuilder.AppendLine("[" + i + "] null");
						continue;
					}
					RectTransform val2 = (RectTransform)slots[i].transform;
					Transform obj2 = ((Transform)val2).Find("res_icon");
					RectTransform val3 = (RectTransform)(object)((obj2 is RectTransform) ? obj2 : null);
					string[] obj3 = new string[12]
					{
						"[",
						i.ToString(),
						"] ",
						((Object)slots[i]).name,
						" pos=",
						((object)val2.anchoredPosition/*cast due to .constrained prefix*/).ToString(),
						" size=",
						null,
						null,
						null,
						null,
						null
					};
					rect = val2.rect;
					obj3[7] = ((Rect)(ref rect)).width.ToString();
					obj3[8] = "x";
					rect = val2.rect;
					obj3[9] = ((Rect)(ref rect)).height.ToString();
					obj3[10] = " icon=";
					object obj4;
					if (!((Object)(object)val3 == (Object)null))
					{
						rect = val3.rect;
						string text = ((Rect)(ref rect)).width.ToString();
						rect = val3.rect;
						obj4 = text + "x" + ((Rect)(ref rect)).height;
					}
					else
					{
						obj4 = "none";
					}
					obj3[11] = (string)obj4;
					stringBuilder.AppendLine(string.Concat(obj3));
				}
				File.WriteAllText(Path.Combine(Paths.BepInExRootPath, "biomeblueprints-hud.txt"), stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("BiomeBlueprints: could not dump hud geometry - " + ex.Message));
			}
		}
	}
	public static class MaterialList
	{
		public sealed class Entry
		{
			public string Item;

			public int Amount;
		}

		public const string Marker = "Materials";

		public static int PiecesIn(string description)
		{
			if (string.IsNullOrEmpty(description))
			{
				return 0;
			}
			for (int num = description.IndexOf("piece", StringComparison.OrdinalIgnoreCase); num >= 0; num = description.IndexOf("piece", num + 1, StringComparison.OrdinalIgnoreCase))
			{
				int num2 = DigitsBefore(description, num);
				if (num2 > 0)
				{
					return num2;
				}
				int num3 = DigitsAfter(description, num + 5);
				if (num3 > 0)
				{
					return num3;
				}
			}
			return 0;
		}

		private static int DigitsBefore(string text, int at)
		{
			int num = at - 1;
			while (num >= 0 && text[num] == ' ')
			{
				num--;
			}
			if (num < 0 || !char.IsDigit(text[num]))
			{
				return 0;
			}
			int num2 = num;
			while (num2 > 0 && char.IsDigit(text[num2 - 1]))
			{
				num2--;
			}
			return Number(text, num2, num);
		}

		private static int DigitsAfter(string text, int at)
		{
			int i;
			for (i = at; i < text.Length && (text[i] == ' ' || text[i] == ':' || text[i] == 's' || text[i] == 'S'); i++)
			{
			}
			if (i >= text.Length || !char.IsDigit(text[i]))
			{
				return 0;
			}
			int j;
			for (j = i; j + 1 < text.Length && char.IsDigit(text[j + 1]); j++)
			{
			}
			return Number(text, i, j);
		}

		private static int Number(string text, int first, int last)
		{
			int num = 0;
			for (int i = first; i <= last; i++)
			{
				num = num * 10 + (text[i] - 48);
				if (num > 1000000)
				{
					return 0;
				}
			}
			return num;
		}

		public static string WithPieceCount(string description, int count)
		{
			if (string.IsNullOrEmpty(description) || count <= 0)
			{
				return description;
			}
			string text = count.ToString(CultureInfo.InvariantCulture);
			int num = description.IndexOf("piece", StringComparison.OrdinalIgnoreCase);
			int num2 = num - 1;
			if (num > 0)
			{
				while (num2 >= 0 && description[num2] == ' ')
				{
					num2--;
				}
			}
			if (num <= 0 || num2 < 0 || !char.IsDigit(description[num2]))
			{
				int num3 = (description.StartsWith("\"", StringComparison.Ordinal) ? 1 : 0);
				return description.Substring(0, num3) + text + " pieces<br>" + description.Substring(num3);
			}
			int num4 = num2;
			while (num4 > 0 && char.IsDigit(description[num4 - 1]))
			{
				num4--;
			}
			return description.Substring(0, num4) + text + description.Substring(num2 + 1);
		}

		public static List<Entry> Parse(string markup, int max)
		{
			List<Entry> list = new List<Entry>();
			if (string.IsNullOrEmpty(markup))
			{
				return list;
			}
			int num = markup.IndexOf("Materials", StringComparison.Ordinal);
			if (num < 0)
			{
				return list;
			}
			string[] array = markup.Substring(num + "Materials".Length).Split(new string[1] { "<br>" }, StringSplitOptions.None);
			for (int i = 0; i < array.Length; i++)
			{
				string text = StripTags(array[i]).Trim();
				if (text.Length == 0 || text.StartsWith("+", StringComparison.Ordinal))
				{
					continue;
				}
				int num2 = text.LastIndexOf(" x", StringComparison.Ordinal);
				if (num2 <= 0)
				{
					continue;
				}
				string text2 = text.Substring(0, num2).Trim();
				if (int.TryParse(text.Substring(num2 + 2).Trim(), out var result) && text2.Length != 0)
				{
					list.Add(new Entry
					{
						Item = text2,
						Amount = result
					});
					if (max > 0 && list.Count >= max)
					{
						break;
					}
				}
			}
			return list;
		}

		public static string HeadOf(string markup)
		{
			if (string.IsNullOrEmpty(markup))
			{
				return "";
			}
			int num = markup.IndexOf("Materials", StringComparison.Ordinal);
			string text = ((num < 0) ? markup : markup.Substring(0, num));
			int num2 = text.LastIndexOf('\n');
			if (num2 >= 0)
			{
				text = text.Substring(num2 + 1);
			}
			string text2;
			while (true)
			{
				text2 = text.TrimEnd(Array.Empty<char>());
				if (!text2.EndsWith("<br>", StringComparison.Ordinal))
				{
					break;
				}
				text = text2.Substring(0, text2.Length - 4);
			}
			return text2;
		}

		public static string StripTags(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length);
			int num = 0;
			foreach (char c in s)
			{
				switch (c)
				{
				case '<':
					num++;
					break;
				case '>':
					if (num > 0)
					{
						num--;
					}
					break;
				default:
					if (num == 0)
					{
						stringBuilder.Append(c);
					}
					break;
				}
			}
			return stringBuilder.ToString();
		}
	}
	public static class MaterialTier
	{
		public static readonly string[] Biomes = new string[8] { "Meadows", "Black Forest", "Swamp", "Mountain", "Plains", "Mistlands", "Ashlands", "Deep North" };

		public static readonly string[] Tokens = new string[8] { "meadows", "blackforest", "swamp", "mountain", "plains", "mistlands", "ashlands", "deepnorth" };

		public const int Unknown = -1;

		private static readonly Dictionary<string, int> Tiers = new Dictionary<string, int>(StringComparer.Ordinal)
		{
			{ "Wood", 0 },
			{ "Stone", 0 },
			{ "Resin", 0 },
			{ "LeatherScraps", 0 },
			{ "Flint", 0 },
			{ "Coal", 0 },
			{ "DeerHide", 0 },
			{ "Feathers", 0 },
			{ "BoneFragments", 0 },
			{ "Coins", 0 },
			{ "Honey", 0 },
			{ "Dandelion", 0 },
			{ "Raspberry", 0 },
			{ "Mushroom", 0 },
			{ "StoneRock", 0 },
			{ "FirCone", 0 },
			{ "PineCone", 0 },
			{ "FineWood", 1 },
			{ "RoundLog", 1 },
			{ "CoreWood", 1 },
			{ "Copper", 1 },
			{ "CopperScrap", 1 },
			{ "Tin", 1 },
			{ "Bronze", 1 },
			{ "BronzeNails", 1 },
			{ "GreydwarfEye", 1 },
			{ "SurtlingCore", 1 },
			{ "TrollHide", 1 },
			{ "Ruby", 1 },
			{ "Thistle", 1 },
			{ "Blueberries", 1 },
			{ "Carrot", 1 },
			{ "BarrelRings", 1 },
			{ "SharpeningStone", 1 },
			{ "Iron", 2 },
			{ "IronNails", 2 },
			{ "ElderBark", 2 },
			{ "Guck", 2 },
			{ "Chain", 2 },
			{ "Turnip", 2 },
			{ "WitheredBone", 2 },
			{ "Entrails", 2 },
			{ "Ooze", 2 },
			{ "Obsidian", 3 },
			{ "Silver", 3 },
			{ "WolfPelt", 3 },
			{ "WolfClaw", 3 },
			{ "Crystal", 3 },
			{ "DragonTear", 3 },
			{ "Onion", 3 },
			{ "FreezeGland", 3 },
			{ "SilverNecklace", 3 },
			{ "BlackMetal", 4 },
			{ "BlackMetalScrap", 4 },
			{ "Tar", 4 },
			{ "Needle", 4 },
			{ "LinenThread", 4 },
			{ "Flax", 4 },
			{ "Barley", 4 },
			{ "JuteRed", 4 },
			{ "JuteBlue", 4 },
			{ "LoxPelt", 4 },
			{ "Cloudberry", 4 },
			{ "BarleyWine", 4 },
			{ "BlackMarble", 5 },
			{ "YggdrasilWood", 5 },
			{ "Eitr", 5 },
			{ "Wisp", 5 },
			{ "MechanicalSpring", 5 },
			{ "GemstoneRed", 5 },
			{ "Softtissue", 5 },
			{ "BlackCore", 5 },
			{ "Sap", 5 },
			{ "Carapace", 5 },
			{ "RoyalJelly", 5 },
			{ "Lantern", 5 },
			{ "DvergrNeedle", 5 },
			{ "Pot_Shard_Green", 5 },
			{ "Pot_Shard_Red", 5 },
			{ "FlametalNew", 6 },
			{ "Flametal", 6 },
			{ "CharredBone", 6 },
			{ "AskHide", 6 },
			{ "MorgenSinew", 6 },
			{ "ProustitePowder", 6 },
			{ "SulfurStone", 6 },
			{ "CelestialFeather", 6 },
			{ "Blackwood", 6 },
			{ "Grausten", 6 },
			{ "CharcoalResin", 6 },
			{ "MoltenCore", 6 },
			{ "CharredCogwheel", 6 },
			{ "Charredskull", 6 },
			{ "ScaleHide", 6 },
			{ "Vineberry", 6 },
			{ "Ironpit", 6 },
			{ "AsksvinCarrionNeck", 6 },
			{ "AsksvinCarrionPelvic", 6 },
			{ "AsksvinCarrionRibcage", 6 },
			{ "AsksvinCarrionSkull", 6 },
			{ "BjornHide", 7 },
			{ "BjornPaw", 7 }
		};

		public const double Coverage = 0.95;

		public static List<string> Significant(IEnumerable<KeyValuePair<string, int>> cost)
		{
			List<string> list = new List<string>();
			if (cost == null)
			{
				return list;
			}
			List<KeyValuePair<string, int>> list2 = new List<KeyValuePair<string, int>>();
			long num = 0L;
			foreach (KeyValuePair<string, int> item in cost)
			{
				if (item.Value > 0)
				{
					list2.Add(item);
					num += item.Value;
				}
			}
			if (num <= 0)
			{
				return list;
			}
			list2.Sort(delegate(KeyValuePair<string, int> a, KeyValuePair<string, int> b)
			{
				int num4 = b.Value.CompareTo(a.Value);
				return (num4 == 0) ? string.Compare(a.Key, b.Key, StringComparison.Ordinal) : num4;
			});
			double num2 = (double)num * 0.95;
			long num3 = 0L;
			foreach (KeyValuePair<string, int> item2 in list2)
			{
				list.Add(item2.Key);
				num3 += item2.Value;
				if ((double)num3 >= num2)
				{
					break;
				}
			}
			return list;
		}

		public static int Of(string material)
		{
			if (string.IsNullOrEmpty(material))
			{
				return -1;
			}
			if (!Tiers.TryGetValue(material, out var value))
			{
				return -1;
			}
			return value;
		}

		public static int Of(IEnumerable<string> materials)
		{
			int num = 0;
			if (materials == null)
			{
				return num;
			}
			foreach (string material in materials)
			{
				int num2 = Of(material);
				if (num2 > num)
				{
					num = num2;
				}
			}
			return num;
		}

		public static string BiomeOf(int tier)
		{
			if (tier < 0)
			{
				tier = 0;
			}
			if (tier >= Biomes.Length)
			{
				tier = Biomes.Length - 1;
			}
			return Biomes[tier];
		}

		public static string TokenOf(int tier)
		{
			if (tier < 0)
			{
				tier = 0;
			}
			if (tier >= Tokens.Length)
			{
				tier = Tokens.Length - 1;
			}
			return Tokens[tier];
		}
	}
	[HarmonyPatch(typeof(Hud), "UpdatePieceList")]
	public static class PieceGridPatch
	{
		public const int GridColumns = 15;

		public const int GridCapacity = 90;

		private static bool baseKnown;

		private static Vector2 basePosition;

		private static void Postfix(Hud __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.m_pieceListRoot == (Object)null))
			{
				ClearTheTabs(__instance);
			}
		}

		private static void ClearTheTabs(Hud hud)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: 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_014e: Unknown result type (might be due to invalid IL or missing references)
			RectTransform pieceListRoot = hud.m_pieceListRoot;
			if ((Object)(object)pieceListRoot == (Object)null || ((Transform)pieceListRoot).childCount == 0)
			{
				return;
			}
			if (!baseKnown)
			{
				basePosition = pieceListRoot.anchoredPosition;
				baseKnown = true;
			}
			pieceListRoot.anchoredPosition = basePosition;
			GameObject[] pieceCategoryTabs = hud.m_pieceCategoryTabs;
			if (pieceCategoryTabs == null || pieceCategoryTabs.Length == 0)
			{
				return;
			}
			float num = float.MaxValue;
			Vector3[] array = (Vector3[])(object)new Vector3[4];
			GameObject[] array2 = pieceCategoryTabs;
			foreach (GameObject val in array2)
			{
				if ((Object)(object)val == (Object)null || !val.activeInHierarchy)
				{
					continue;
				}
				Transform transform = val.transform;
				RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null);
				if (!((Object)(object)val2 == (Object)null))
				{
					val2.GetWorldCorners(array);
					if (array[0].y < num)
					{
						num = array[0].y;
					}
				}
			}
			if (num == float.MaxValue)
			{
				return;
			}
			Transform child = ((Transform)pieceListRoot).GetChild(0);
			RectTransform val3 = (RectTransform)(object)((child is RectTransform) ? child : null);
			if ((Object)(object)val3 == (Object)null)
			{
				return;
			}
			val3.GetWorldCorners(array);
			float num2 = array[1].y - num;
			if (!(num2 <= 0f))
			{
				float num3 = (((Object)(object)((Transform)pieceListRoot).parent == (Object)null) ? 1f : ((Transform)pieceListRoot).parent.lossyScale.y);
				if (!(num3 < 0.0001f))
				{
					pieceListRoot.anchoredPosition = basePosition - new Vector2(0f, num2 / num3);
				}
			}
		}
	}
	[HarmonyPatch(typeof(WearNTear), "Awake")]
	public static class PieceShadows
	{
		public const float DefaultSmallerThan = 1.2f;

		private static readonly Dictionary<string, bool> Decided = new Dictionary<string, bool>(StringComparer.Ordinal);

		private static float measuredAt = float.NaN;

		internal static int Silenced;

		internal static int Kept;

		private static bool broken;

		private static float SmallerThan
		{
			get
			{
				if (Plugin.ShadowVolume != null)
				{
					return Plugin.ShadowVolume.Value;
				}
				return 1.2f;
			}
		}

		private static void Postfix(WearNTear __instance)
		{
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Invalid comparison between Unknown and I4
			if (broken || (Object)(object)__instance == (Object)null || Plugin.TrimShadows == null || !Plugin.TrimShadows.Value)
			{
				return;
			}
			try
			{
				GameObject gameObject = ((Component)__instance).gameObject;
				if (measuredAt != SmallerThan)
				{
					measuredAt = SmallerThan;
					Decided.Clear();
				}
				if (!IsSmallPiece(gameObject))
				{
					Kept++;
					return;
				}
				MeshRenderer[] componentsInChildren = gameObject.GetComponentsInChildren<MeshRenderer>(true);
				foreach (MeshRenderer val in componentsInChildren)
				{
					if (!((Object)(object)val == (Object)null) && (int)((Renderer)val).shadowCastingMode == 1)
					{
						((Renderer)val).shadowCastingMode = (ShadowCastingMode)0;
					}
				}
				Silenced++;
			}
			catch (Exception ex)
			{
				broken = true;
				Plugin.Log.LogWarning((object)("BiomeBlueprints: shadow trimming disabled - " + ex.Message));
			}
		}

		internal static bool IsSmallPiece(GameObject go)
		{
			if ((Object)(object)go == (Object)null)
			{
				return false;
			}
			string key = PrefabName(((Object)go).name);
			if (Decided.TryGetValue(key, out var value))
			{
				return value;
			}
			value = IsSmall(go);
			Decided[key] = value;
			return value;
		}

		private static bool IsSmall(GameObject go)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			Bounds val = default(Bounds);
			((Bounds)(ref val))..ctor(Vector3.zero, Vector3.zero);
			MeshFilter[] componentsInChildren = go.GetComponentsInChildren<MeshFilter>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Mesh sharedMesh = componentsInChildren[i].sharedMesh;
				if (!((Object)(object)sharedMesh == (Object)null))
				{
					Bounds bounds = sharedMesh.bounds;
					if (!flag)
					{
						val = bounds;
						flag = true;
					}
					else
					{
						((Bounds)(ref val)).Encapsulate(bounds);
					}
				}
			}
			if (!flag)
			{
				return false;
			}
			Vector3 size = ((Bounds)(ref val)).size;
			return size.x * size.y * size.z < SmallerThan;
		}

		internal static string State()
		{
			if (Plugin.TrimShadows != null && !Plugin.TrimShadows.Value)
			{
				return "  shadow trimming is OFF in the config";
			}
			return "  shadow trimming: " + Silenced + " pieces silenced, " + Kept + " kept theirs (" + Decided.Count + " kinds measured, under " + SmallerThan + " m3 loses its shadow)";
		}

		internal static string PrefabName(string name)
		{
			int num = name.IndexOf('(');
			if (num > 0)
			{
				name = name.Substring(0, num);
			}
			return name.Trim();
		}
	}
	public static class PlaceGuard
	{
		private const string Placeholder = "piece_bpplaceholder";

		private const float WarnSeconds = 10f;

		private const float FlashInterval = 0.35f;

		private static readonly int ObstacleLayers = LayerMask.GetMask(new string[3] { "Default", "static_solid", "Default_small" });

		private static readonly FieldRef<Player, GameObject> GhostOf = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost");

		private static readonly FieldRef<Player, GameObject> MarkerOf = AccessTools.FieldRefAccess<Player, GameObject>("m_placementMarkerInstance");

		private static float warnedAt = float.NegativeInfinity;

		private static bool flashing;

		private static int lastRefused = -1;

		private static bool blocked;

		private static bool broken;

		internal static void Apply(Harmony harmony)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected O, but got Unknown
			//IL_007e: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("PlanBuild.Blueprints.Components.PlacementComponent");
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "OnPlacePiece", (Type[])null, (Type[])null));
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)"BiomeBlueprints: PlanBuild's PlacementComponent.OnPlacePiece was not found, so the obstacle warning and the auto-deselect after placing are both off. PlanBuild has probably changed; the rest of the mod is unaffected.");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PlaceGuard).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(PlaceGuard).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static bool Prefix(Player __0)
		{
			blocked = false;
			lastRefused = -1;
			if (broken || Plugin.WarnObstacles == null || !Plugin.WarnObstacles.Value)
			{
				return true;
			}
			if (!Placing(__0))
			{
				return true;
			}
			if (Plugin.PlaceOverObstacles != null && Plugin.PlaceOverObstacles.Value && Time.time - warnedAt < 10f)
			{
				warnedAt = float.NegativeInfinity;
				return true;
			}
			try
			{
				List<GameObject> list = Obstructions(GhostOf.Invoke(__0));
				if (list.Count == 0)
				{
					return true;
				}
				warnedAt = Time.time;
				blocked = true;
				Announce(list);
				if (list.Count != lastRefused)
				{
					lastRefused = list.Count;
					Plugin.Log.LogInfo((object)("BiomeBlueprints: refused to place - " + list.Count + " obstacle(s) inside the design" + ((Plugin.PlaceOverObstacles != null && Plugin.PlaceOverObstacles.Value) ? (" (a second click within " + 10f + "s will place anyway)") : "")));
				}
				if ((Object)(object)Plugin.Instance != (Object)null && !flashing)
				{
					flashing = true;
					((MonoBehaviour)Plugin.Instance).StartCoroutine(Flash(list));
				}
				return false;
			}
			catch (Exception ex)
			{
				broken = true;
				Plugin.Log.LogWarning((object)("BiomeBlueprints: obstacle warning disabled - " + ex.Message));
				return true;
			}
		}

		private static void Postfix(Player __0)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			if (blocked)
			{
				blocked = false;
			}
			else
			{
				if (broken || !Placing(__0))
				{
					return;
				}
				try
				{
					List<Piece> buildPieces = __0.GetBuildPieces();
					if (buildPieces == null)
					{
						return;
					}
					for (int i = 0; i < buildPieces.Count; i++)
					{
						if (!((Object)(object)buildPieces[i] == (Object)null) && !(((Object)((Component)buildPieces[i]).gameObject).name != "piece_bpplaceholder"))
						{
							__0.SetSelectedPiece(new Vector2Int(i % 15, i / 15));
							break;
						}
					}
				}
				catch (Exception ex)
				{
					broken = true;
					Plugin.Log.LogWarning((object)("BiomeBlueprints: auto-deselect disabled - " + ex.Message));
				}
			}
		}

		private static bool Placing(Player player)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Invalid comparison between Unknown and I4
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			GameObject val = MarkerOf.Invoke(player);
			if ((Object)(object)GhostOf.Invoke(player) != (Object)null && (Object)(object)val != (Object)null && val.activeSelf)
			{
				return (int)player.GetPlacementStatus() == 0;
			}
			return false;
		}

		private static List<GameObject> Obstructions(GameObject ghost)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			List<GameObject> list = new List<GameObject>();
			Bounds val = Volume(ghost);
			if (((Bounds)(ref val)).size == Vector3.zero)
			{
				return list;
			}
			Collider[] array = Physics.OverlapBox(((Bounds)(ref val)).center, ((Bounds)(ref val)).extents, Quaternion.identity, ObstacleLayers, (QueryTriggerInteraction)1);
			if (array == null)
			{
				return list;
			}
			HashSet<int> hashSet = new HashSet<int>();
			Collider[] array2 = array;
			foreach (Collider val2 in array2)
			{
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				ZNetView componentInParent = ((Component)val2).GetComponentInParent<ZNetView>();
				if (!((Object)(object)componentInParent == (Object)null))
				{
					GameObject gameObject = ((Component)componentInParent).gameObject;
					if (hashSet.Add(((Object)gameObject).GetInstanceID()) && IsObstacle(gameObject))
					{
						list.Add(gameObject);
					}
				}
			}
			return list;
		}

		private static Bounds Volume(GameObject ghost)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			Bounds bounds = default(Bounds);
			((Bounds)(ref bounds))..ctor(Vector3.zero, Vector3.zero);
			Renderer[] componentsInChildren = ghost.GetComponentsInChildren<Renderer>();
			foreach (Renderer val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && val.enabled)
				{
					if (!flag)
					{
						bounds = val.bounds;
						flag = true;
					}
					else
					{
						((Bounds)(ref bounds)).Encapsulate(val.bounds);
					}
				}
			}
			if (!flag)
			{
				return new Bounds(Vector3.zero, Vector3.zero);
			}
			return bounds;
		}

		private static bool IsObstacle(GameObject go)
		{
			if ((Object)(object)go.GetComponent<Piece>() != (Object)null)
			{
				return false;
			}
			if ((Object)(object)go.GetComponent<Character>() != (Object)null)
			{
				return false;
			}
			if ((Object)(object)go.GetComponent<ItemDrop>() != (Object)null)
			{
				return false;
			}
			if (!((Object)(object)go.GetComponent<TreeBase>() != (Object)null) && !((Object)(object)go.GetComponent<TreeLog>() != (Object)null) && !((Object)(object)go.GetComponent<MineRock>() != (Object)null) && !((Object)(object)go.GetComponent<MineRock5>() != (Object)null))
			{
				return (Object)(object)go.GetComponent<Destructible>() != (Object)null;
			}
			return true;
		}

		private static void Announce(List<GameObject> found)
		{
			int num = 0;
			foreach (GameObject item in found)
			{
				if (IsTree(item))
				{
					num++;
				}
			}
			int num2 = found.Count - num;
			string text = ((num > 0 && num2 > 0) ? (Count(num, "tree") + " and " + Count(num2, "rock")) : ((num > 0) ? Count(num, "tree") : Count(num2, "rock")));
			MessageHud instance = MessageHud.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				bool flag = Plugin.PlaceOverObstacles != null && Plugin.PlaceOverObstacles.Value;
				instance.ShowMessage((MessageType)2, text + " in the way, flashing red\n" + (flag ? "Clear them, or press place again to build anyway" : "Clear them before placing here"), 0, (Sprite)null, false);
			}
		}

		private static bool IsTree(GameObject go)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Invalid comparison between Unknown and I4
			if ((Object)(object)go.GetComponent<TreeBase>() != (Object)null || (Object)(object)go.GetComponent<TreeLog>() != (Object)null)
			{
				return true;
			}
			Destructible component = go.GetComponent<Destructible>();
			if ((Object)(object)component != (Object)null)
			{
				return (int)component.m_destructibleType == 2;
			}
			return false;
		}

		private static string Count(int n, string noun)
		{
			return n + " " + noun + ((n == 1) ? "" : "s");
		}

		private static IEnumerator Flash(List<GameObject> targets)
		{
			MaterialMan man = MaterialMan.instance;
			if ((Object)(object)man == (Object)null)
			{
				flashing = false;
				yield break;
			}
			float until = Time.time + 10f;
			bool lit = false;
			while (Time.time < until)
			{
				lit = !lit;
				foreach (GameObject target in targets)
				{
					if (!((Object)(object)target == (Object)null))
					{
						if (lit)
						{
							man.SetValue<Color>(target, ShaderProps._Color, Color.red);
							man.SetValue<Color>(target, ShaderProps._EmissionColor, Color.red * 0.7f);
						}
						else
						{
							man.ResetValue(target, ShaderProps._Color);
							man.ResetValue(target, ShaderProps._EmissionColor);
						}
					}
				}
				yield return (object)new WaitForSeconds(0.35f);
			}
			foreach (GameObject target2 in targets)
			{
				if (!((Object)(object)target2 == (Object)null))
				{
					man.ResetValue(target2, ShaderProps._Color);
					man.ResetValue(target2, ShaderProps._EmissionColor);
				}
			}
			flashing = false;
		}
	}
	public static class PlanPieceThrottle
	{
		public const int Slots = 10;

		public const int Floor = 200;

		private static FieldInfo placedPieces;

		private static bool broken;

		private static int lastFrame = -1;

		private static int placedNow;

		private static float nextReport;

		private static long ran;

		private static long skipped;

		internal static void Apply(Harmony harmony)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("PlanBuild.Plans.PlanPiece");
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "Update", (Type[])null, (Type[])null));
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)"BiomeBlueprints: PlanBuild's PlanPiece.Update was not found, so placed plan pieces keep checking their support every frame. Large placed designs will cost more than they need to; nothing else is affected.");
				return;
			}
			placedPieces = AccessTools.Field(type, "m_planPieces");
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PlanPieceThrottle).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static bool Prefix(MonoBehaviour __instance)
		{
			if (broken || (Object)(object)__instance == (Object)null)
			{
				return true;
			}
			try
			{
				int frameCount = Time.frameCount;
				if (frameCount != lastFrame)
				{
					lastFrame = frameCount;
					placedNow = Placed();
					Report(placedNow);
				}
				if (placedNow < 200)
				{
					return true;
				}
				bool num = (frameCount + ((Object)__instance).GetInstanceID()) % 10 == 0;
				if (num)
				{
					ran++;
				}
				else
				{
					skipped++;
				}
				return num;
			}
			catch (Exception ex)
			{
				broken = true;
				Plugin.Log.LogWarning((object)("BiomeBlueprints: plan-piece throttle disabled - " + ex.Message));
				return true;
			}
		}

		private static void Report(int placed)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (nextReport == 0f)
			{
				nextReport = realtimeSinceStartup + 60f;
			}
			else if (!(realtimeSinceStartup < nextReport))
			{
				nextReport = realtimeSinceStartup + 60f;
				if (placed >= 200 || ran + skipped != 0L)
				{
					Plugin.Log.LogInfo((object)("BiomeBlueprints: " + placed + " placed plan pieces nearby; their per-frame support check ran " + ran + " times and was deferred " + skipped + " times in the last minute."));
					ran = 0L;
					skipped = 0L;
				}
			}
		}

		private static int Placed()
		{
			if (placedPieces == null)
			{
				return int.MaxValue;
			}
			if (placedPieces.GetValue(null) is ICollection collection)
			{
				return collection.Count;
			}
			return int.MaxValue;
		}
	}
	[BepInPlugin("OverDrive.BiomeBlueprints", "BiomeBlueprints", "1.0.3")]
	[BepInProcess("valheim.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "OverDrive.BiomeBlueprints";

		public const string PluginName = "BiomeBlueprints";

		public const string PluginVersion = "1.0.3";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> ShowWelcome;

		internal static ConfigEntry<bool> WarnObstacles;

		internal static ConfigEntry<bool> PlaceOverObstacles;

		internal static ConfigEntry<bool> TrimShadows;

		internal static ConfigEntry<float> ShadowVolume;

		internal static ConfigEntry<float> CullSmallBeyond;

		internal static ConfigEntry<int> SmokeCap;

		internal static ConfigEntry<bool> FlattenGround;

		internal static ConfigEntry<float> FlattenWhenUneven;

		internal static Plugin Instance;

		private Harmony harmony;

		private void Awake()
		{
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			ShowWelcome = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Show welcome tips", true, "Show a short greeting and performance tips the first time you spawn each session.");
			WarnObstacles = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Warn about obstacles", true, "Flash trees and rocks standing inside a design red, and refuse to place until they are cleared. Turn off to place over them without a word.");
			PlaceOverObstacles = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Let a second click place over obstacles", false, "With this on, pressing place again within ten seconds of the warning builds anyway. Off by default: clicking twice is what a player does to check whether the first click registered, and the design would go down through the tree it was warned about.");
			ShadowVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "Keep shadows on pieces bigger than (m3)", 1.2f, "Build pieces with a bounding volume under this stop casting shadows. Measured in a 39,000-piece base: 1.2 silences pillar bases, arches, 1x1 blocks, beams and item stands while walls (3.6), roofs (13.2) and 2x1 blocks (2.3) keep theirs - that halved the shadow casters and gained a third of the frame rate. Raise it for more speed and flatter lighting; 0 keeps every shadow.");
			FlattenGround = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Flatten the ground under a design", true, "Level the plot when a design is placed. On a hillside this is what makes a design usable; on ground you have already levelled yourself it is a change to your world you did not ask for. Turn it off and designs are placed without touching the terrain at all. Takes effect within a couple of seconds, no restart needed.");
			FlattenWhenUneven = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Only flatten ground more uneven than (m)", 0.5f, "Before placing, the ground under the design is measured. If the highest and lowest points are closer together than this, the plot is already flat enough and is left exactly as it is - so a design placed on ground you levelled yourself does not change it. On a slope the design still levels its plot as before. Set to 0 to level every time regardless.");
			SmokeCap = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "Most smoke particles at once", 40, "Every smoke particle in Valheim is a physics body that pushes against the others. The game's own limit is 100 and is not really enforced - it retires one particle per spawn while every fire, torch and kiln nearby keeps spawning - so a base with many fires runs far past it. This holds the real number down. Set to 0 to leave the game's smoke exactly as it is.");
			CullSmallBeyond = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "Stop drawing small pieces beyond (m)", 30f, "Small pieces - pillar bases, blocks, beams, item stands - stop being drawn past roughly this distance. Walls, roofs and floors are never affected, so a building keeps its shape from any range. This is what makes a very large base playable: a 39,000-piece build carries 72 million vertices, and most of them are detail no one can resolve. Set to 0 to draw everything at every distance.");
			TrimShadows = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "Trim shadows on small pieces", true, "Stop build pieces under a metre casting shadows. In a large base most pieces are small blocks and beams whose shadows nobody can pick out, and each one is drawn again for every shadow cascade. Turn off to restore vanilla shadows.");
			PrefabDump.Register();
			SupportProbe.Register();
			Thumbnails.Register();
			SceneCensus.Register();
			harmony = new Harmony("OverDrive.BiomeBlueprints");
			harmony.PatchAll();
			PlaceGuard.Apply(harmony);
			FlattenWhenNeeded.Apply(harmony);
			SortByCost.Apply(harmony);
			PlanPieceThrottle.Apply(harmony);
			Log.LogInfo((object)"BiomeBlueprints 1.0.3 loaded.");
		}

		private void OnDestroy()
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	public static class PrefabDump
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__1_0;

			internal void <Register>b__1_0(ConsoleEventArgs args)
			{
				try
				{
					string text = Dump();
					args.Context.AddString("BiomeBlueprints: wrote " + text);
				}
				catch (Exception ex)
				{
					args.Context.AddString("BiomeBlueprints: dump FAILED - " + ex.Message);
					Plugin.Log.LogError((object)ex);
				}
			}
		}

		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		public static void Register()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			object obj = <>c.<>9__1_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					try
					{
						string text = Dump();
						args.Context.AddString("BiomeBlueprints: wrote " + text);
					}
					catch (Exception ex)
					{
						args.Context.AddString("BiomeBlueprints: dump FAILED - " + ex.Message);
						Plugin.Log.LogError((object)ex);
					}
				};
				<>c.<>9__1_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("biomeblueprints.dumpprefabs", "Dump every buildable piece prefab with its measured size and build cost to CSV", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		public static string Dump()
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				throw new InvalidOperationException("ZNetScene is not loaded - join a world first.");
			}
			List<string> list = new List<string>();
			list.Add("prefab,display,category,sizeX,sizeY,sizeZ,centerX,centerY,centerZ,comfort,requirements");
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			int num = 0;
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if (!((Object)(object)prefab == (Object)null) && hashSet.Add(((Object)prefab).name))
				{
					Piece component = prefab.GetComponent<Piece>();
					if (!((Object)(object)component == (Object)null))
					{
						num++;
						Bounds val = LocalBounds(prefab);
						StringBuilder stringBuilder = new StringBuilder();
						stringBuilder.Append(Csv(((Object)prefab).name)).Append(',');
						stringBuilder.Append(Csv(component.m_name)).Append(',');
						stringBuilder.Append(Csv(((object)Unsafe.As<PieceCategory, PieceCategory>(ref component.m_category)/*cast due to .constrained prefix*/).ToString())).Append(',');
						stringBuilder.Append(Num(((Bounds)(ref val)).size.x)).Append(',');
						stringBuilder.Append(Num(((Bounds)(ref val)).size.y)).Append(',');
						stringBuilder.Append(Num(((Bounds)(ref val)).size.z)).Append(',');
						stringBuilder.Append(Num(((Bounds)(ref val)).center.x)).Append(',');
						stringBuilder.Append(Num(((Bounds)(ref val)).center.y)).Append(',');
						stringBuilder.Append(Num(((Bounds)(ref val)).center.z)).Append(',');
						stringBuilder.Append(component.m_comfort.ToString(Inv)).Append(',');
						stringBuilder.Append(Csv(Requirements(component)));
						list.Add(stringBuilder.ToString());
					}
				}
			}
			if (num == 0)
			{
				throw new InvalidOperationException("ZNetScene held " + instance.m_prefabs.Count + " prefabs but none had a Piece component.");
			}
			string text = Path.Combine(Paths.ConfigPath, "BiomeBlueprints");
			Directory.CreateDirectory(text);
			string text2 = Path.Combine(text, "prefabs.csv");
			File.WriteAllText(text2, string.Join("\n", list.ToArray()) + "\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			List<string> list2 = new List<string>(hashSet);
			list2.Sort(StringComparer.Ordinal);
			string text3 = Path.Combine(text, "game-prefabs.txt");
			File.WriteAllText(text3, "# Every prefab name in ZNetScene, from the live game with its mods loaded.\n# A blueprint naming anything outside this list cannot be built here.\n" + string.Join("\n", list2.ToArray()) + "\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			Plugin.Log.LogInfo((object)("Dumped " + num + " piece prefabs to " + text2 + " and " + list2.Count + " prefab names to " + text3));
			return text2 + " and " + text3;
		}

		private static Bounds LocalBounds(GameObject prefab)
		{
			//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_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: 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_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			Matrix4x4 worldToLocalMatrix = prefab.transform.worldToLocalMatrix;
			bool flag = false;
			Bounds result = default(Bounds);
			((Bounds)(ref result))..ctor(Vector3.zero, Vector3.zero);
			MeshFilter[] componentsInChildren = prefab.GetComponentsInChildren<MeshFilter>(true);
			Vector3 val3 = default(Vector3);
			foreach (MeshFilter val in componentsInChildren)
			{
				Mesh sharedMesh = val.sharedMesh;
				if ((Object)(object)sharedMesh == (Object)null)
				{
					continue;
				}
				Matrix4x4 val2 = worldToLocalMatrix * ((Component)val).transform.localToWorldMatrix;
				Bounds bounds = sharedMesh.bounds;
				Vector3 min = ((Bounds)(ref bounds)).min;
				bounds = sharedMesh.bounds;
				Vector3 max = ((Bounds)(ref bounds)).max;
				for (int j = 0; j < 8; j++)
				{
					((Vector3)(ref val3))..ctor(((j & 1) == 0) ? min.x : max.x, ((j & 2) == 0) ? min.y : max.y, ((j & 4) == 0) ? min.z : max.z);
					Vector3 val4 = ((Matrix4x4)(ref val2)).MultiplyPoint3x4(val3);
					if (!flag)
					{
						((Bounds)(ref result))..ctor(val4, Vector3.zero);
						flag = true;
					}
					else
					{
						((Bounds)(ref result)).Encapsulate(val4);
					}
				}
			}
			return result;
		}

		private static string Requirements(Piece piece)
		{
			if (piece.m_resources == null || piece.m_resources.Length == 0)
			{
				return "";
			}
			List<string> list = new List<string>();
			Requirement[] resources = piece.m_resources;
			foreach (Requirement val in resources)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null))
				{
					list.Add(((Object)val.m_resItem).name + "x" + val.m_amount.ToString(Inv));
				}
			}
			return string.Join(" ", list.ToArray());
		}

		private static string Num(float value)
		{
			return value.ToString("0.###", Inv);
		}

		private static string Csv(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "";
			}
			if (value.IndexOf(',') < 0 && value.IndexOf('"') < 0 && value.IndexOf('\n') < 0)
			{
				return value;
			}
			return "\"" + value.Replace("\"", "\"\"") + "\"";
		}
	}
	public static class SceneCensus
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__9_0;

			public static Comparison<KeyValuePair<string, int>> <>9__10_0;

			internal void <Register>b__9_0(ConsoleEventArgs args)
			{
				try
				{
					float radius = 64f;
					if (args.Length > 1 && float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
					{
						radius = result;
					}
					foreach (string item in Census(radius))
					{
						args.Context.AddString(item);
					}
				}
				catch (Exception ex)
				{
					args.Context.AddString("BiomeBlueprints: census FAILED - " + ex.Message);
					Plugin.Log.LogError((object)ex);
				}
			}

			internal int <Census>b__10_0(KeyValuePair<string, int> a, KeyValuePair<string, int> b)
			{
				return b.Value.CompareTo(a.Value);
			}
		}

		public const float DefaultRadius = 64f;

		public const float SlowFps = 30f;

		public const float SlowSeconds = 5f;

		public const int MaxAutoReports = 3;

		public const float SettleSeconds = 45f;

		public const float StillLoadingGrowth = 0.02f;

		private static readonly FieldRef<ZNetScene, IDictionary> Instances = AccessTools.FieldRefAccess<ZNetScene, IDictionary>("m_instances");

		private static readonly Dictionary<Type, bool> Updaters = new Dictionary<Type, bool>();

		internal static IEnumerator Watch()
		{
			float slowFor = 0f;
			int reports = 0;
			int lastCount = -1;
			yield return (object)new WaitForSeconds(45f);
			while (reports < 3)
			{
				yield return (object)new WaitForSeconds(1f);
				if ((Object)(object)Player.m_localPlayer == (Object)null)
				{
					slowFor = 0f;
					continue;
				}
				float num = ((Time.unscaledDeltaTime > 0f) ? (1f / Time.unscaledDeltaTime) : 999f);
				if (num >= 30f)
				{
					slowFor = 0f;
					continue;
				}
				slowFor += 1f;
				if (slowFor < 5f)
				{
					continue;
				}
				int num2 = Loaded();
				bool num3 = lastCount > 0 && (float)num2 > (float)lastCount * 1.02f;
				lastCount = num2;
				if (num3)
				{
					Plugin.Log.LogInfo((object)("BiomeBlueprints: slow, but the world is still loading (" + num2 + " objects and climbing) - waiting for it to settle."));
					slowFor = 0f;
					continue;
				}
				slowFor = 0f;
				reports++;
				Plugin.Log.LogInfo((object)("BiomeBlueprints: " + num.ToString("0") + " fps for " + 5f + "s - taking a census of what is around you (" + reports + " of " + 3 + " this session)."));
				try
				{
					Census(64f);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("BiomeBlueprints: census failed - " + ex.Message));
					reports = 3;
				}
				yield return (object)new WaitForSeconds(60f);
			}
		}

		private static int Loaded()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return 0;
			}
			try
			{
				return Instances.Invoke(instance)?.Count ?? 0;
			}
			catch
			{
				return -1;
			}
		}

		public static void Register()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			object obj = <>c.<>9__9_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					try
					{
						float radius = 64f;
						if (args.Length > 1 && float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
						{
							radius = result;
						}
						foreach (string item in Census(radius))
						{
							args.Context.AddString(item);
						}
					}
					catch (Exception ex)
					{
						args.Context.AddString("BiomeBlueprints: census FAILED - " + ex.Message);
						Plugin.Log.LogError((object)ex);
					}
				};
				<>c.<>9__9_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("biomeblueprints.whatsaround", "Count what is standing near you and where the frame is going (optional radius, default 64)", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		public static List<string> Census(float radius)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			Player localPlayer = Player.m