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 Biomes = new Dictionary(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 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 KnownTokens() { return Biomes.Keys; } } public static class FlattenToggle { private static bool applied; private static bool broken; private static readonly Dictionary Saved = new Dictionary(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 GhostOf = AccessTools.FieldRefAccess("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 list = new List(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 DoomedAt; public List> Partial; public readonly List Materials = new List(); public readonly List Meshes = new List(); } private sealed class Group { public Material Material; public readonly List Parts = new List(); } private const int MinRenderers = 200; private const int MinPieces = 100; private static readonly FieldRef GhostOf = AccessTools.FieldRefAccess("m_placementGhost"); private static readonly FieldRef BuildPiecesOf = AccessTools.FieldRefAccess("m_buildPieces"); private static readonly Dictionary Canonical = new Dictionary(StringComparer.Ordinal); private static bool failed; private static bool dumped; private const string MergedName = "HomesteadsMerged"; private static readonly HashSet Welding = new HashSet(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(true).Length); stringBuilder.AppendLine("pieces(direct children)=" + ghost.transform.childCount); stringBuilder.AppendLine("lodGroups=" + ghost.GetComponentsInChildren(true).Length); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; long num5 = 0L; MeshFilter[] componentsInChildren = ghost.GetComponentsInChildren(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(); 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(); MeshRenderer component2 = ((Component)t).GetComponent(); 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() != (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 list = new List(); try { Transform transform = prefab.transform; int childCount = transform.childCount; Matrix4x4 worldToLocalMatrix = transform.worldToLocalMatrix; Dictionary dictionary = new Dictionary(StringComparer.Ordinal); List list2 = new List(); List list3 = new List(); List list4 = new List(); List> list5 = new List>(); Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < childCount; i++) { GameObject gameObject = ((Component)transform.GetChild(i)).gameObject; if (IsScaffolding(gameObject)) { continue; } List 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(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 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 drawn) { foreach (MeshRenderer item in drawn) { if ((Object)(object)ReadableMesh(item) != (Object)null) { return true; } } return false; } private static IEnumerable RenderersToHide(Transform root, KeyValuePair entry) { if (entry.Key >= root.childCount) { yield break; } MeshRenderer[] renderers = ((Component)root.GetChild(entry.Key)).GetComponentsInChildren(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().sharedMesh = mesh; MeshRenderer component = val.GetComponent(); ((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 hashSet = new HashSet(plan.DoomedAt); List list = new List(plan.DoomedAt.Count); List list2 = new List(); 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 item2 in plan.Partial) { if (item2.Key >= plan.ChildCount) { continue; } MeshRenderer[] componentsInChildren = ((Component)transform.GetChild(item2.Key)).GetComponentsInChildren(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 Drawn(GameObject piece) { List list = new List(); HashSet hashSet = new HashSet(); LODGroup[] componentsInChildren = piece.GetComponentsInChildren(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(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 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(); 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 drawn, Dictionary groups, Matrix4x4 toRoot) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) MeshRenderer[] componentsInChildren = piece.GetComponentsInChildren(true); List list = new List(); 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 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(); 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 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 pieces) { if (pieces.Count == 0) { return; } List list = new List(); foreach (GameObject piece in pieces) { if ((Object)(object)piece != (Object)null) { list.AddRange(piece.GetComponentsInChildren(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>> AvailablePieces = AccessTools.FieldRefAccess>>("m_availablePieces"); private static int calls; private static double totalMs; private static double worstMs; private static float nextReport; private static readonly Dictionary> Gates = new Dictionary>(StringComparer.Ordinal); private static readonly Dictionary Names = new Dictionary(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; 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 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> 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 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 GateOf(string key, string description) { if (Gates.TryGetValue(key ?? "", out var value)) { return value; } List list = new List(); List> list2 = new List>(); foreach (MaterialList.Entry item in MaterialList.Parse(description, 0)) { list2.Add(new KeyValuePair(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(); 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 Cells = new List(); private static readonly Dictionary Plans = new Dictionary(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(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 list = new List(); 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 + "
+ " + 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(); } 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() != (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
" + 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 Parse(string markup, int max) { List list = new List(); 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] { "
" }, 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()); if (!text2.EndsWith("
", 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 Tiers = new Dictionary(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 Significant(IEnumerable> cost) { List list = new List(); if (cost == null) { return list; } List> list2 = new List>(); long num = 0L; foreach (KeyValuePair item in cost) { if (item.Value > 0) { list2.Add(item); num += item.Value; } } if (num <= 0) { return list; } list2.Sort(delegate(KeyValuePair a, KeyValuePair 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 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 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 Decided = new Dictionary(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(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(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 GhostOf = AccessTools.FieldRefAccess("m_placementGhost"); private static readonly FieldRef MarkerOf = AccessTools.FieldRefAccess("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 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 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 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 list = new List(); 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 hashSet = new HashSet(); Collider[] array2 = array; foreach (Collider val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } ZNetView componentInParent = ((Component)val2).GetComponentInParent(); 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(); 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() != (Object)null) { return false; } if ((Object)(object)go.GetComponent() != (Object)null) { return false; } if ((Object)(object)go.GetComponent() != (Object)null) { return false; } if (!((Object)(object)go.GetComponent() != (Object)null) && !((Object)(object)go.GetComponent() != (Object)null) && !((Object)(object)go.GetComponent() != (Object)null) && !((Object)(object)go.GetComponent() != (Object)null)) { return (Object)(object)go.GetComponent() != (Object)null; } return true; } private static void Announce(List 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() != (Object)null || (Object)(object)go.GetComponent() != (Object)null) { return true; } Destructible component = go.GetComponent(); 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 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(target, ShaderProps._Color, Color.red); man.SetValue(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 ShowWelcome; internal static ConfigEntry WarnObstacles; internal static ConfigEntry PlaceOverObstacles; internal static ConfigEntry TrimShadows; internal static ConfigEntry ShadowVolume; internal static ConfigEntry CullSmallBeyond; internal static ConfigEntry SmokeCap; internal static ConfigEntry FlattenGround; internal static ConfigEntry 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("General", "Show welcome tips", true, "Show a short greeting and performance tips the first time you spawn each session."); WarnObstacles = ((BaseUnityPlugin)this).Config.Bind("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("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("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("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("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("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("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("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 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 list = new List(); list.Add("prefab,display,category,sizeX,sizeY,sizeZ,centerX,centerY,centerZ,comfort,requirements"); HashSet hashSet = new HashSet(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(); 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(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 list2 = new List(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(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 list = new List(); 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> <>9__10_0; internal void 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 b__10_0(KeyValuePair a, KeyValuePair 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 Instances = AccessTools.FieldRefAccess("m_instances"); private static readonly Dictionary Updaters = new Dictionary(); 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 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 list = new List(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { list.Add("BiomeBlueprints: no local player."); return list; } Vector3 position = ((Component)localPlayer).transform.position; ZNetView[] array = Object.FindObjectsOfType(); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; long num9 = 0L; HashSet hashSet = new HashSet(); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); ZNetView[] array2 = array; foreach (ZNetView val in array2) { if ((Object)(object)val == (Object)null) { continue; } GameObject gameObject = ((Component)val).gameObject; if (Vector3.Distance(position, gameObject.transform.position) > radius) { continue; } num++; if ((Object)(object)gameObject.GetComponent() != (Object)null) { num2++; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { num4++; } MonoBehaviour[] components = gameObject.GetComponents(); foreach (MonoBehaviour val2 in components) { if (!((Object)(object)val2 == (Object)null)) { if (((object)val2).GetType().Name == "PlanPiece") { num3++; } if (HasUpdate(((object)val2).GetType())) { num7++; } } } HashSet hashSet2 = new HashSet(); LODGroup[] componentsInChildren = gameObject.GetComponentsInChildren(true); Renderer[] array3; foreach (LODGroup val3 in componentsInChildren) { if ((Object)(object)val3 == (Object)null) { continue; } LOD[] lODs = val3.GetLODs(); for (int k = 1; k < lODs.Length; k++) { Renderer[] renderers = lODs[k].renderers; if (renderers == null) { continue; } array3 = renderers; foreach (Renderer val4 in array3) { if ((Object)(object)val4 != (Object)null) { hashSet2.Add(((Object)val4).GetInstanceID()); } } } } array3 = gameObject.GetComponentsInChildren(true); foreach (Renderer val5 in array3) { if ((Object)(object)val5 == (Object)null) { continue; } num5++; if (!val5.enabled || hashSet2.Contains(((Object)val5).GetInstanceID())) { continue; } num6++; if ((int)val5.shadowCastingMode != 0) { num8++; } MeshFilter component = ((Component)val5).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.sharedMesh != (Object)null) { num9 += component.sharedMesh.vertexCount; } Material[] sharedMaterials = val5.sharedMaterials; if (sharedMaterials == null) { continue; } Material[] array4 = sharedMaterials; foreach (Material val6 in array4) { if ((Object)(object)val6 != (Object)null) { hashSet.Add(((Object)val6).GetInstanceID()); } } } string key = Prefab(((Object)gameObject).name); dictionary.TryGetValue(key, out var value); dictionary[key] = value + 1; } list.Add("BiomeBlueprints: within " + radius + "m of you --"); list.Add(" " + num + " world objects, " + num2 + " build pieces, " + num3 + " PLAN pieces, " + num4 + " with WearNTear"); list.Add(" " + num6 + " renderers at LOD0 (" + num5 + " counting every LOD level), " + hashSet.Count + " distinct materials"); list.Add(" " + num8 + " of those cast shadows, drawn again per shadow cascade"); list.Add(" " + num9 / 1000 + "k vertices at LOD0"); list.Add(" " + num7 + " components with a per-frame Update"); list.Add(PieceShadows.State()); list.Add(SmallPieceCulling.State()); list.Add(SmokeCap.State()); if (hashSet.Count > 0) { list.Add(" " + num6 / hashSet.Count + " renderers per material (higher batches better)"); } List> list2 = new List>(dictionary); list2.Sort((KeyValuePair a, KeyValuePair b) => b.Value.CompareTo(a.Value)); StringBuilder stringBuilder = new StringBuilder(" most of it: "); for (int num10 = 0; num10 < list2.Count && num10 < 6; num10++) { if (num10 > 0) { stringBuilder.Append(", "); } stringBuilder.Append(list2[num10].Key).Append(" x").Append(list2[num10].Value); } list.Add(stringBuilder.ToString()); foreach (string item in list) { Plugin.Log.LogInfo((object)item); } return list; } private static bool HasUpdate(Type type) { if (Updaters.TryGetValue(type, out var value)) { return value; } bool flag = type.GetMethod("Update", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null; Updaters[type] = flag; return flag; } private static string Prefab(string name) { int num = name.IndexOf('('); if (num > 0) { name = name.Substring(0, num); } return name.Trim(); } } [HarmonyPatch(typeof(Player), "UpdateBuildGuiInput")] public static class ScrollTheList { private const float Notch = 0.1f; private static readonly FieldRef Scrolled = AccessTools.FieldRefAccess("m_scrollCurrAmount"); private static readonly FieldRef BuildPieces = AccessTools.FieldRefAccess("m_buildPieces"); private static readonly MethodInfo SetupGhost = AccessTools.Method(typeof(Player), "SetupPlacementGhost", (Type[])null, (Type[])null); private static bool failed; private static void Prefix(Player __instance, out float __state) { __state = (((Object)(object)__instance == (Object)null) ? 0f : __instance.m_scrollAmountThreshold); if (!failed && !((Object)(object)__instance == (Object)null) && Hud.IsPieceSelectionVisible()) { __instance.m_scrollAmountThreshold = float.MaxValue; } } private static void Postfix(Player __instance, float __state) { if ((Object)(object)__instance == (Object)null) { return; } __instance.m_scrollAmountThreshold = __state; if (failed || !Hud.IsPieceSelectionVisible()) { return; } try { float num = Scrolled.Invoke(__instance); if (Mathf.Abs(num) < 0.1f) { return; } Scrolled.Invoke(__instance) = 0f; PieceTable val = BuildPieces.Invoke(__instance); if (!((Object)(object)val == (Object)null)) { if (num > 0f) { val.UpPiece(); } else { val.DownPiece(); } if (SetupGhost != null) { SetupGhost.Invoke(__instance, null); } } } catch (Exception ex) { failed = true; Plugin.Log.LogWarning((object)("BiomeBlueprints: wheel-scrolls-the-list disabled - " + ex.Message)); } } } [HarmonyPatch(typeof(WearNTear), "Awake")] public static class SmallPieceCulling { public const float DefaultBeyond = 48f; private const float FieldOfView = (float)Math.PI * 13f / 36f; private static readonly Dictionary Sizes = new Dictionary(StringComparer.Ordinal); internal static int Culled; private static bool broken; private static float Beyond { get { if (Plugin.CullSmallBeyond != null) { return Plugin.CullSmallBeyond.Value; } return 48f; } } private static void Postfix(WearNTear __instance) { //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) if (broken || (Object)(object)__instance == (Object)null || Plugin.CullSmallBeyond == null || Plugin.CullSmallBeyond.Value <= 0f || Plugin.TrimShadows == null || !Plugin.TrimShadows.Value) { return; } try { GameObject gameObject = ((Component)__instance).gameObject; if (!PieceShadows.IsSmallPiece(gameObject) || SizeOf(gameObject) <= 0f) { return; } float num = 1f / (2f * Beyond * Mathf.Tan((float)Math.PI * 13f / 72f)); num = Mathf.Clamp(num, 0.001f, 0.05f); LODGroup componentInChildren = gameObject.GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { LOD[] lODs = componentInChildren.GetLODs(); if (lODs != null && lODs.Length != 0) { int num2 = lODs.Length - 1; if (!(lODs[num2].screenRelativeTransitionHeight >= num)) { lODs[num2].screenRelativeTransitionHeight = num; componentInChildren.SetLODs(lODs); Culled++; } } } else { Renderer[] componentsInChildren = gameObject.GetComponentsInChildren(true); if (componentsInChildren != null && componentsInChildren.Length != 0) { LODGroup val = gameObject.AddComponent(); val.SetLODs((LOD[])(object)new LOD[1] { new LOD(num, componentsInChildren) }); val.RecalculateBounds(); Culled++; } } } catch (Exception ex) { broken = true; Plugin.Log.LogWarning((object)("BiomeBlueprints: small-piece culling disabled - " + ex.Message)); } } private static float SizeOf(GameObject go) { //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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //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) string key = PieceShadows.PrefabName(((Object)go).name); if (Sizes.TryGetValue(key, out var value)) { return value; } bool flag = false; Bounds bounds = default(Bounds); ((Bounds)(ref bounds))..ctor(Vector3.zero, Vector3.zero); MeshFilter[] componentsInChildren = go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Mesh sharedMesh = componentsInChildren[i].sharedMesh; if (!((Object)(object)sharedMesh == (Object)null)) { if (!flag) { bounds = sharedMesh.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(sharedMesh.bounds); } } } float num = (flag ? Mathf.Max(((Bounds)(ref bounds)).size.x, Mathf.Max(((Bounds)(ref bounds)).size.y, ((Bounds)(ref bounds)).size.z)) : 0f); Sizes[key] = num; return num; } internal static string State() { if (Plugin.CullSmallBeyond == null || Plugin.CullSmallBeyond.Value <= 0f) { return " small-piece culling is OFF"; } return " small-piece culling: " + Culled + " pieces given a cull distance (~" + Beyond.ToString("0") + "m)"; } } public static class SmokeCap { public const int DefaultCap = 40; private const float Interval = 0.5f; internal static int Trimmed; internal static int Peak; private static bool broken; private static int Cap { get { if (Plugin.SmokeCap != null) { return Plugin.SmokeCap.Value; } return 40; } } internal static IEnumerator Watch() { while (true) { yield return (object)new WaitForSeconds(0.5f); if (broken) { break; } int cap = Cap; if (cap <= 0) { continue; } try { int totalSmoke = Smoke.GetTotalSmoke(); if (totalSmoke > Peak) { Peak = totalSmoke; } if (totalSmoke > cap) { int num = Mathf.Min(totalSmoke - cap, 25); for (int i = 0; i < num; i++) { Smoke.FadeOldest(); Trimmed++; } } } catch (Exception ex) { broken = true; Plugin.Log.LogWarning((object)("BiomeBlueprints: smoke cap disabled - " + ex.Message)); break; } } } internal static string State() { if (broken) { return " smoke cap is off - the game's smoke API changed"; } if (Cap <= 0) { return " smoke cap is OFF in the config"; } int num = 0; try { num = Smoke.GetTotalSmoke(); } catch { } return " smoke: " + num + " live now, peaked at " + Peak + ", " + Trimmed + " retired to hold the cap of " + Cap + " (each one is a rigidbody; the game's own limit is 100 and unenforced)"; } } public static class SortByCost { private const string Placeholder = "piece_bpplaceholder"; private static readonly FieldRef>> AvailablePieces = AccessTools.FieldRefAccess>>("m_availablePieces"); private static readonly Dictionary Costs = new Dictionary(StringComparer.Ordinal); private static bool broken; private static readonly FieldRef BuildPiecesOf = AccessTools.FieldRefAccess("m_buildPieces"); internal static void Apply(Harmony harmony) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)"BiomeBlueprints: Player.UpdateAvailablePiecesList was not found, so the designs stay in alphabetical order instead of cheapest-first."); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SortByCost).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void Postfix(Player __instance) { if (broken || (Object)(object)__instance == (Object)null) { return; } try { PieceTable val = BuildPiecesOf.Invoke(__instance); if ((Object)(object)val == (Object)null) { return; } List> list = AvailablePieces.Invoke(val); if (list == null) { return; } for (int i = 0; i < list.Count; i++) { List list2 = list[i]; if (list2 != null && list2.Count >= 2 && AnyOfOurs(list2)) { list2.Sort(Cheapest); } } } catch (Exception ex) { broken = true; Plugin.Log.LogWarning((object)("BiomeBlueprints: cost ordering disabled - " + ex.Message)); } } private static bool AnyOfOurs(List pieces) { for (int i = 0; i < pieces.Count; i++) { if (!((Object)(object)pieces[i] == (Object)null) && CatalogueId.IsOurs(CatalogueId.IdFromPieceName(((Object)((Component)pieces[i]).gameObject).name))) { return true; } } return false; } private static int Cheapest(Piece a, Piece b) { if ((Object)(object)a == (Object)null) { return (!((Object)(object)b == (Object)null)) ? 1 : 0; } if ((Object)(object)b == (Object)null) { return -1; } bool flag = ((Object)((Component)a).gameObject).name == "piece_bpplaceholder"; bool flag2 = ((Object)((Component)b).gameObject).name == "piece_bpplaceholder"; if (flag || flag2) { if (flag != flag2) { if (!flag) { return 1; } return -1; } return 0; } int num = CostOf(a).CompareTo(CostOf(b)); if (num != 0) { return num; } return string.Compare(a.m_name, b.m_name, StringComparison.Ordinal); } private static int CostOf(Piece piece) { string name = ((Object)((Component)piece).gameObject).name; if (Costs.TryGetValue(name, out var value)) { return value; } int num = MaterialList.PiecesIn(piece.m_description); if (num <= 0) { return int.MaxValue; } Costs[name] = num; return num; } } public static class SupportProbe { private sealed class Tally { public string Name; public float RelativeY; public int Count; public int Unsupported; public float MinSupport; public float MaxNeeded; public bool Read; } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__3_0; internal void b__3_0(ConsoleEventArgs args) { try { float radius = 20f; if (args.Length > 1 && float.TryParse(args[1], NumberStyles.Float, Inv, out var result)) { radius = result; } foreach (string item in Probe(radius)) { args.Context.AddString(item); } } catch (Exception ex) { args.Context.AddString("BiomeBlueprints: probe FAILED - " + ex.Message); Plugin.Log.LogError((object)ex); } } } private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; private static readonly MethodInfo GetSupportMethod = AccessTools.Method(typeof(WearNTear), "GetSupport", (Type[])null, (Type[])null); private static readonly MethodInfo GetMaxSupportMethod = AccessTools.Method(typeof(WearNTear), "GetMaxSupport", (Type[])null, (Type[])null); 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__3_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { try { float radius = 20f; if (args.Length > 1 && float.TryParse(args[1], NumberStyles.Float, Inv, out var result)) { radius = result; } foreach (string item in Probe(radius)) { args.Context.AddString(item); } } catch (Exception ex) { args.Context.AddString("BiomeBlueprints: probe FAILED - " + ex.Message); Plugin.Log.LogError((object)ex); } }; <>c.<>9__3_0 = val; obj = (object)val; } new ConsoleCommand("biomeblueprints.checksupport", "Report support values for building pieces near you (optional radius, default 20)", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } public static List Probe(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_0066: 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_00a8: 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) List list = new List(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { list.Add("BiomeBlueprints: no local player."); return list; } Vector3 position = ((Component)localPlayer).transform.position; WearNTear[] array = Object.FindObjectsOfType(); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); int num = 0; WearNTear[] array2 = array; foreach (WearNTear val in array2) { if ((Object)(object)val == (Object)null || Vector3.Distance(((Component)val).transform.position, position) > radius) { continue; } string text = ((Object)((Component)val).gameObject).name; int num2 = text.IndexOf('('); if (num2 > 0) { text = text.Substring(0, num2); } float relativeY = ((Component)val).transform.position.y - position.y; string key = text + "@" + relativeY.ToString("0.0", Inv); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new Tally { Name = text, RelativeY = relativeY }); } value.Count++; num++; float num3 = 0f; float val2 = 0f; bool flag = false; try { if (GetSupportMethod != null && GetMaxSupportMethod != null) { num3 = (float)GetSupportMethod.Invoke(val, null); val2 = (float)GetMaxSupportMethod.Invoke(val, null); flag = true; } } catch (Exception) { } if (flag) { value.Read = true; value.MinSupport = ((value.Count == 1) ? num3 : Math.Min(value.MinSupport, num3)); value.MaxNeeded = Math.Max(value.MaxNeeded, val2); if (num3 <= 0f) { value.Unsupported++; } } } if (num == 0) { list.Add("BiomeBlueprints: no building pieces within " + radius.ToString("0", Inv) + "m."); return list; } list.Add("BiomeBlueprints support probe - " + num + " pieces within " + radius.ToString("0", Inv) + "m (Y is relative to you)"); List list2 = new List(dictionary.Keys); list2.Sort(StringComparer.Ordinal); foreach (string item in list2) { Tally tally2 = dictionary[item]; string text2 = " " + tally2.Name.PadRight(18) + "Y" + tally2.RelativeY.ToString("0.0", Inv).PadLeft(6) + " x" + tally2.Count.ToString().PadLeft(3); if (tally2.Read) { text2 = text2 + " support " + tally2.MinSupport.ToString("0.0", Inv) + " / need " + tally2.MaxNeeded.ToString("0.0", Inv); if (tally2.Unsupported > 0) { text2 = text2 + " <-- " + tally2.Unsupported + " UNSUPPORTED"; } } else { text2 += " (support unreadable)"; } list.Add(text2); } return list; } } public static class Thumbnails { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__2_0; internal void b__2_0(ConsoleEventArgs args) { bool all = args.Length > 1 && args[1].Equals("all", StringComparison.OrdinalIgnoreCase); if ((Object)(object)Plugin.Instance == (Object)null) { args.Context.AddString("BiomeBlueprints: plugin not loaded."); } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(Run(args.Context, all)); } } } private const int SettleFrames = 2; private const int UnloadEvery = 25; 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__2_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { bool all = args.Length > 1 && args[1].Equals("all", StringComparison.OrdinalIgnoreCase); if ((Object)(object)Plugin.Instance == (Object)null) { args.Context.AddString("BiomeBlueprints: plugin not loaded."); } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(Run(args.Context, all)); } }; <>c.<>9__2_0 = val; obj = (object)val; } new ConsoleCommand("biomeblueprints.thumbnails", "Generate the missing design thumbnails (add 'all' to redo every one)", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static IEnumerator Run(Terminal console, bool all) { KeyValuePair>, Type>? keyValuePair = Catalogue(); if (!keyValuePair.HasValue) { Say(console, "BiomeBlueprints: PlanBuild's blueprint list was not found - it has probably changed. Nothing was done."); yield break; } MethodInfo create = AccessTools.Method(keyValuePair.Value.Value, "CreateThumbnail", new Type[2] { typeof(int), typeof(bool) }, (Type[])null); MethodInfo destroy = AccessTools.Method(keyValuePair.Value.Value, "DestroyGhost", (Type[])null, (Type[])null); FieldInfo fieldInfo = AccessTools.Field(keyValuePair.Value.Value, "ThumbnailLocation"); if (create == null || destroy == null || fieldInfo == null) { Say(console, "BiomeBlueprints: PlanBuild's Blueprint type is missing " + ((create == null) ? "CreateThumbnail " : "") + ((destroy == null) ? "DestroyGhost " : "") + ((fieldInfo == null) ? "ThumbnailLocation " : "") + "- nothing was done."); yield break; } List> todo = new List>(); foreach (KeyValuePair item in keyValuePair.Value.Key) { if (!CatalogueId.IsOurs(item.Key)) { continue; } if (!all) { string text = fieldInfo.GetValue(item.Value) as string; if (!string.IsNullOrEmpty(text) && File.Exists(text)) { continue; } } todo.Add(item); } if (todo.Count == 0) { Say(console, "BiomeBlueprints: every design already has a thumbnail. Use 'biomeblueprints.thumbnails all' to redo them anyway."); yield break; } Say(console, "BiomeBlueprints: rendering " + todo.Count + " thumbnail(s). The game will stutter; it should not run out of memory."); int done = 0; int failed = 0; for (int i = 0; i < todo.Count; i++) { string key = todo[i].Key; object value = todo[i].Value; bool flag = false; try { flag = create.Invoke(value, new object[2] { 0, true }) as bool? == true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("BiomeBlueprints: " + key + " would not render - " + ex.Message)); } try { destroy.Invoke(value, null); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("BiomeBlueprints: could not free " + key + "'s ghost - " + ex2.Message)); } if (flag) { done++; } else { failed++; } for (int frame = 0; frame < 2; frame++) { yield return null; } if ((i + 1) % 25 == 0) { Say(console, " " + (i + 1) + " of " + todo.Count + "..."); yield return Resources.UnloadUnusedAssets(); } } yield return Resources.UnloadUnusedAssets(); Say(console, "BiomeBlueprints: " + done + " thumbnail(s) written" + ((failed > 0) ? (", " + failed + " failed (see the log)") : "") + ". They are the .png files beside the blueprints in this mod's folder."); } private static KeyValuePair>, Type>? Catalogue() { Type type = AccessTools.TypeByName("PlanBuild.Blueprints.BlueprintManager"); if (type == null) { return null; } FieldInfo fieldInfo = AccessTools.Field(type, "LocalBlueprints") ?? AccessTools.Field(type, "m_localBlueprints"); if (fieldInfo == null) { return null; } if (!(fieldInfo.GetValue(null) is IDictionary dictionary)) { return null; } List> list = new List>(); Type type2 = null; foreach (DictionaryEntry item in dictionary) { if (item.Key is string key && item.Value != null) { if (type2 == null) { type2 = item.Value.GetType(); } list.Add(new KeyValuePair(key, item.Value)); } } if (type2 == null) { return null; } return new KeyValuePair>, Type>(list, type2); } private static void Say(Terminal console, string message) { if ((Object)(object)console != (Object)null) { console.AddString(message); } Plugin.Log.LogInfo((object)message); } } [HarmonyPatch(typeof(Player), "OnSpawned")] public static class Welcome { private static bool greeted; private static void Postfix(Player __instance) { if (greeted || (Object)(object)__instance == (Object)null || (Object)(object)Player.m_localPlayer != (Object)(object)__instance) { return; } greeted = true; if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(SceneCensus.Watch()); ((MonoBehaviour)Plugin.Instance).StartCoroutine(SmokeCap.Watch()); ((MonoBehaviour)Plugin.Instance).StartCoroutine(FlattenToggle.Watch()); } try { if (Plugin.ShowWelcome.Value) { MessageHud instance = MessageHud.instance; if (!((Object)(object)instance == (Object)null)) { instance.ShowMessage((MessageType)2, "Biome Blueprints\nHouse designs in the Blueprint Rune, by biome", 0, (Sprite)null, false); instance.ShowMessage((MessageType)1, "Designs unlock as you find the materials they are made of", 0, (Sprite)null, false); instance.ShowMessage((MessageType)1, "Big designs pause once when first selected, then run smoothly", 0, (Sprite)null, false); instance.ShowMessage((MessageType)1, "Low-end PC? Prefer designs under ~1000 pieces", 0, (Sprite)null, false); instance.ShowMessage((MessageType)1, "and lower Shadow Quality, Vegetation and Draw Distance", 0, (Sprite)null, false); instance.ShowMessage((MessageType)1, "Built something good? The mod page has a form to get it added", 0, (Sprite)null, false); Plugin.Log.LogInfo((object)"BiomeBlueprints tips - the placement preview is the expensive part of this mod. A large base is welded into a few meshes the first time you select it, so the first pick pauses briefly and later ones do not. On a low-end machine: prefer designs under about 1000 pieces, and turn down Shadow Quality, Vegetation and Draw Distance in Settings > Graphics. Set 'Show welcome tips' to false in the config to hide this greeting."); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("BiomeBlueprints: could not show the welcome - " + ex.Message)); } } } }