Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of NarrowChimneysMaxVlasov v0.5.5
BepInEx/plugins/NarrowChimneys.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Numerics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("NarrowChimneys")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.3.1")] [assembly: AssemblyInformationalVersion("0.5.3.1")] [assembly: AssemblyProduct("NarrowChimneys")] [assembly: AssemblyTitle("NarrowChimneys")] [assembly: AssemblyVersion("0.5.3.1")] [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 NarrowChimneys { internal static class ChimneyDiagnostics { private sealed class Record { internal float LastLog; internal string Evidence = "none"; } private static readonly ConditionalWeakTable<WearNTear, Record> records = new ConditionalWeakTable<WearNTear, Record>(); private static readonly MethodInfo minimum = AccessTools.Method(typeof(WearNTear), "GetMinSupport", (Type[])null, (Type[])null); private static readonly MethodInfo currentSupport = AccessTools.Method(typeof(WearNTear), "GetSupport", (Type[])null, (Type[])null); internal static bool IsChimney(WearNTear wear) { if (Object.op_Implicit((Object)(object)wear)) { return ModuleCatalog.Find(((Object)wear).name) != null; } return false; } internal static void Evidence(WearNTear wear, string evidence) { if (IsChimney(wear)) { records.GetValue(wear, (WearNTear _) => new Record()).Evidence = evidence; } } internal static void Report(WearNTear wear, string phase, float support, bool force = false) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) if (!IsChimney(wear)) { return; } try { ZNetView component = ((Component)wear).GetComponent<ZNetView>(); if (!Object.op_Implicit((Object)(object)component) || !component.IsValid()) { return; } Record value = records.GetValue(wear, (WearNTear _) => new Record()); float num = (float)minimum.Invoke(wear, null); if (!force && (!NarrowChimneysPlugin.Diagnostics.Value || Time.time < value.LastLog + 2f)) { return; } value.LastLog = Time.time; Collider[] componentsInChildren = ((Component)wear).GetComponentsInChildren<Collider>(true); string text = ""; Collider[] array = componentsInChildren; foreach (Collider val in array) { string text2 = text; object[] obj = new object[7] { ((Object)val).name, ((Component)val).gameObject.layer, val.enabled, val.isTrigger, Object.op_Implicit((Object)(object)val.attachedRigidbody) ? ((Object)val.attachedRigidbody).name : "none", null, null }; Bounds bounds = val.bounds; obj[5] = ((Bounds)(ref bounds)).center; bounds = val.bounds; obj[6] = ((Bounds)(ref bounds)).size; text = text2 + string.Format(" [{0}:layer={1},enabled={2},trigger={3},body={4},centre={5},size={6}]", obj); } string text3 = $"Chimney {phase}: id={((Object)wear).GetInstanceID()}, pos={((Component)wear).transform.position}, owner={component.IsOwner()}, active={((Component)wear).gameObject.activeInHierarchy}, healthFraction={wear.GetHealthPercentage()}, maxHealth={wear.m_health}, support={support}, minimum={num}, material={wear.m_materialType}, COM={wear.m_comOffset}, supportWear={wear.m_noSupportWear}, lastEvidence={value.Evidence}; colliders={componentsInChildren.Length}{text}"; if (force) { NarrowChimneysPlugin.Log.LogWarning((object)text3); } else { NarrowChimneysPlugin.Log.LogInfo((object)text3); } if (!NarrowChimneysPlugin.Diagnostics.Value || !(support < num)) { return; } array = Physics.OverlapBox(((Component)wear).transform.position + Vector3.up * 0.5f, new Vector3(0.65f, 0.65f, 0.65f), ((Component)wear).transform.rotation, LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "terrain" }), (QueryTriggerInteraction)1); foreach (Collider val2 in array) { if (!((Object)(object)((Component)val2).GetComponentInParent<WearNTear>() == (Object)(object)wear)) { WearNTear componentInParent = ((Component)val2).GetComponentInParent<WearNTear>(); ZNetView val3 = (Object.op_Implicit((Object)(object)componentInParent) ? ((Component)componentInParent).GetComponent<ZNetView>() : null); string text4 = ((Object.op_Implicit((Object)(object)componentInParent) && Object.op_Implicit((Object)(object)val3) && val3.IsValid()) ? currentSupport.Invoke(componentInParent, null).ToString() : "not available"); NarrowChimneysPlugin.Log.LogInfo((object)string.Format("Support neighbour: {0}, layer={1}, body={2}, supports={3}, material={4}, actualSupport={5}", ((Object)val2).name, ((Component)val2).gameObject.layer, Object.op_Implicit((Object)(object)val2.attachedRigidbody) ? ((Object)val2.attachedRigidbody).name : "none", Object.op_Implicit((Object)(object)componentInParent) ? componentInParent.m_supports.ToString() : "world solid", Object.op_Implicit((Object)(object)componentInParent) ? ((object)Unsafe.As<MaterialType, MaterialType>(ref componentInParent.m_materialType)/*cast due to .constrained prefix*/).ToString() : "terrain/static", text4)); } } } catch (Exception ex) { NarrowChimneysPlugin.ErrorOnce("Chimney diagnostics failed", ex); } } } [HarmonyPatch(typeof(WearNTear), "Awake")] internal static class ChimneyWearAwakePatch { private static void Postfix(WearNTear __instance, float ___m_support) { ChimneyDiagnostics.Report(__instance, "Awake", ___m_support); } } [HarmonyPatch(typeof(WearNTear), "UpdateSupport")] internal static class ChimneySupportPatch { private static void Postfix(WearNTear __instance, float ___m_support) { ChimneyDiagnostics.Report(__instance, "support update", ___m_support); } } [HarmonyPatch(typeof(WearNTear), "OnPlaced")] internal static class ChimneyPlacedPatch { private static void Postfix(WearNTear __instance, float ___m_support) { ChimneyDiagnostics.Report(__instance, "OnPlaced", ___m_support, force: true); } } [HarmonyPatch(typeof(WearNTear), "ApplyDamage")] internal static class ChimneyDamagePatch { private static void Prefix(WearNTear __instance, float damage, HitData hitData, float ___m_support) { ChimneyDiagnostics.Evidence(__instance, string.Format("ApplyDamage={0}, hit={1}", damage, (hitData == null) ? "null (wear/environment possible)" : ((object)Unsafe.As<HitType, HitType>(ref hitData.m_hitType)/*cast due to .constrained prefix*/).ToString())); if (ChimneyDiagnostics.IsChimney(__instance)) { ChimneyDiagnostics.Report(__instance, "before damage", ___m_support, force: true); } } } [HarmonyPatch(typeof(WearNTear), "RPC_Remove")] internal static class ChimneyRemovalPatch { private static void Prefix(WearNTear __instance) { ChimneyDiagnostics.Evidence(__instance, "RPC_Remove requested"); } } [HarmonyPatch(typeof(WearNTear), "Destroy")] internal static class ChimneyDestroyPatch { private static void Prefix(WearNTear __instance, float ___m_support) { ChimneyDiagnostics.Report(__instance, "BEFORE DESTROY", ___m_support, force: true); } } internal static class ChimneyFactory { internal const string PrefabName = "NC_Chimney"; private static readonly Dictionary<ModuleKind, Mesh> models = new Dictionary<ModuleKind, Mesh>(); internal static void ReleaseVisualCache() { foreach (Mesh value in models.Values) { if (Object.op_Implicit((Object)(object)value)) { Object.Destroy((Object)(object)value); } } models.Clear(); } internal static GameObject Create(GameObject template, Transform storage, ModuleDefinition definition) { //IL_001b: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Expected O, but got Unknown //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Expected O, but got Unknown //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_03a7: Unknown result type (might be due to invalid IL or missing references) //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_041b: Unknown result type (might be due to invalid IL or missing references) //IL_046a: Unknown result type (might be due to invalid IL or missing references) //IL_04bc: Unknown result type (might be due to invalid IL or missing references) //IL_04c4: Unknown result type (might be due to invalid IL or missing references) //IL_05f1: Unknown result type (might be due to invalid IL or missing references) //IL_05ff: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate<GameObject>(template, storage, false); try { ((Object)val).name = definition.Id; val.transform.localPosition = Vector3.zero; val.transform.localRotation = Quaternion.identity; val.transform.localScale = Vector3.one; val.SetActive(true); Piece component = val.GetComponent<Piece>(); ZNetView component2 = val.GetComponent<ZNetView>(); WearNTear component3 = val.GetComponent<WearNTear>(); if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)component2) || !Object.op_Implicit((Object)(object)component3) || component2.GetZDO() != null) { throw new InvalidOperationException("Unsafe stone template or live ZDO during factory setup"); } int num = LayerMask.NameToLayer("piece"); if (num < 0) { throw new InvalidOperationException("Physics layer 'piece' missing"); } val.layer = num; Material[] array = StoneMaterials.Create(template); ModuleGeometry moduleGeometry = ModuleGeometry.Build(definition.Kind); Component[] components = val.GetComponents<Component>(); foreach (Component val2 in components) { if (!(val2 is Transform) && !(val2 is Piece) && !(val2 is ZNetView) && !(val2 is WearNTear)) { NarrowChimneysPlugin.Detail("Removing inherited chimney helper: " + ((object)val2).GetType().Name); Object.DestroyImmediate((Object)(object)val2); } } while (val.transform.childCount > 0) { Object.DestroyImmediate((Object)(object)((Component)val.transform.GetChild(0)).gameObject); } GameObject val3 = new GameObject("NC_Masonry"); val3.layer = num; val3.transform.SetParent(val.transform, false); if (!models.TryGetValue(definition.Kind, out var value) || !Object.op_Implicit((Object)(object)value)) { value = CreateMesh(moduleGeometry.Model); models[definition.Kind] = value; } val3.AddComponent<MeshFilter>().sharedMesh = value; MeshRenderer val4 = val3.AddComponent<MeshRenderer>(); ((Renderer)val4).sharedMaterials = array; ((Renderer)val4).shadowCastingMode = (ShadowCastingMode)1; ((Renderer)val4).receiveShadows = true; val3.AddComponent<SurfaceDisplay>().Initialize(val4, definition.Kind); for (int j = 0; j < moduleGeometry.Collisions.Count; j++) { ModuleGeometry.Collision collision = moduleGeometry.Collisions[j]; GameObject val5 = new GameObject("NC_Collision" + j); val5.layer = num; val5.transform.SetParent(val.transform, false); if (collision.Mesh != null) { MeshCollider obj = val5.AddComponent<MeshCollider>(); obj.sharedMesh = CreateMesh(collision.Mesh, collider: true); obj.convex = false; } else { val5.transform.localPosition = new Vector3(collision.Centre.X, collision.Centre.Y, collision.Centre.Z); val5.transform.localRotation = new Quaternion(collision.Rotation.X, collision.Rotation.Y, collision.Rotation.Z, collision.Rotation.W); val5.AddComponent<BoxCollider>().size = new Vector3(collision.Size.X, collision.Size.Y, collision.Size.Z); } } component3.m_new = (component3.m_worn = (component3.m_broken = val3)); component3.m_wet = null; component3.m_snow = (component3.m_snowWorn = (component3.m_snowBroken = null)); component3.m_nonSolidRenderers = new List<Renderer>(); component3.m_fragmentRoots = (GameObject[])(object)new GameObject[1] { val3 }; component3.m_materialType = (MaterialType)((definition.Kind != ModuleKind.Bracket) ? 1 : 2); component3.m_supports = true; component3.m_noSupportWear = true; component3.m_noRoofWear = false; component3.m_burnable = false; Vector3 vector = SurfacePalette.SupportCentre(definition.Kind); component3.m_comOffset = new Vector3(vector.X, vector.Y, vector.Z); component3.m_forceCorrectCOMCalculation = true; component3.m_staticPosition = true; component3.m_health = 1500f; component3.m_requiredPersistentEvent = ""; component3.m_requiredBiome = (Biome)0; foreach (ModuleGeometry.Snap snap in moduleGeometry.Snaps) { Snap(val.transform, snap.Name, new Vector3(snap.Position.X, snap.Position.Y, snap.Position.Z)); } component.m_name = definition.Name; component.m_description = definition.Description; component.m_enabled = true; component.m_groundOnly = false; component.m_groundPiece = false; component.m_category = (PieceCategory)3; component.m_usage = (UsageTagFlags)20; component.m_icon = CreateIcon(moduleGeometry.Model, definition.Id); Requirement[] resources = component.m_resources; for (int i = 0; i < resources.Length; i++) { resources[i].m_amount = definition.Stone; } if (definition.Kind == ModuleKind.Roof || definition.Kind == ModuleKind.Arch || definition.Kind == ModuleKind.Grate) { val.AddComponent<ChimneyVent>().Kind = definition.Kind; } component2.m_persistent = true; component2.m_syncInitialScale = false; ClearDestroyedReferences((Component)(object)component); ClearDestroyedReferences((Component)(object)component3); ClearDestroyedReferences((Component)(object)component2); Validate(val, moduleGeometry.Collisions.Count); NarrowChimneysPlugin.Log.LogInfo((object)$"Module {definition.Id} ready: shader={((Object)array[0].shader).name}, mesh={val3.GetComponent<MeshFilter>().sharedMesh.vertexCount} vertices, colliders={moduleGeometry.Collisions.Count}, snaps={moduleGeometry.Snaps.Count}, layer={num}, health={component3.m_health}, COM={component3.m_comOffset}, support={component3.m_materialType}"); return val; } catch { Object.Destroy((Object)(object)val); throw; } } private static Mesh CreateMesh(ChimneyGeometry data, bool collider = false) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) List<Vector3> list = new List<Vector3>(); List<Vector3> list2 = new List<Vector3>(); List<Vector2> list3 = new List<Vector2>(); List<Color> list4 = new List<Color>(); for (int i = 0; i < data.Vertices.Count; i++) { Vector3 vector = data.Vertices[i]; Vector3 vector2 = data.Normals[i]; _ = data.UV[i]; list.Add(new Vector3(vector.X, vector.Y, vector.Z)); list2.Add(new Vector3(vector2.X, vector2.Y, vector2.Z)); Vector2 vector3 = (collider ? data.UV[i] : data.AtlasUV[i]); list3.Add(new Vector2(vector3.X, vector3.Y)); list4.Add(Color.white); } Mesh val = new Mesh { name = "NC_BevelledStoneShaft" }; val.SetVertices(list); val.SetNormals(list2); val.SetUVs(0, list3); val.SetUVs(1, list3); val.SetColors(list4); val.subMeshCount = (collider ? 4 : 6); List<int>[] array = new List<int>[val.subMeshCount]; for (int j = 0; j < array.Length; j++) { array[j] = new List<int>(); } for (int k = 0; k < data.Triangles.Count; k += 3) { int num = SurfacePalette.Slot(data, k); if (collider) { float num2 = data.Shades[data.Triangles[k]]; if (num2 < 0f) { num2 = 0.88f; } num = ((num2 < 0.5f) ? 3 : ((!(num2 < 0.9f)) ? ((num2 < 0.97f) ? 1 : 2) : 0)); } array[num].Add(data.Triangles[k]); array[num].Add(data.Triangles[k + 1]); array[num].Add(data.Triangles[k + 2]); } for (int l = 0; l < array.Length; l++) { val.SetTriangles(array[l], l); } val.RecalculateBounds(); val.RecalculateTangents(); return val; } private static void Snap(Transform parent, string name, Vector3 position) { //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_001d: 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) GameObject val = new GameObject("NC_Snap" + name); val.transform.SetParent(parent, false); val.transform.localPosition = position; val.tag = "snappoint"; } private static Sprite CreateIcon(ChimneyGeometry mesh, string id) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown SurfacePreview style = new SurfacePreview { Atlas = StoneMaterials.PreviewAtlas }; byte[] array = ChimneyIcon.Render(mesh, 128, null, night: false, fitSmall: false, style); Color32[] array2 = (Color32[])(object)new Color32[16384]; for (int i = 0; i < array2.Length; i++) { array2[i] = new Color32(array[i * 4], array[i * 4 + 1], array[i * 4 + 2], array[i * 4 + 3]); } Texture2D val = new Texture2D(128, 128, (TextureFormat)4, false) { name = id + "_icon" }; val.SetPixels32(array2); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 128f, 128f), new Vector2(0.5f, 0.5f), 128f); } private static void Validate(GameObject prefab, int expectedColliders) { if (!prefab.activeSelf || prefab.activeInHierarchy || prefab.GetComponent<ZNetView>().GetZDO() != null) { throw new InvalidOperationException("Unsafe prefab activation"); } Collider[] componentsInChildren = prefab.GetComponentsInChildren<Collider>(true); if (componentsInChildren.Length != expectedColliders) { throw new InvalidOperationException("Module collider count mismatch"); } Collider[] array = componentsInChildren; foreach (Collider val in array) { if ((!(val is BoxCollider) && !(val is MeshCollider)) || val.isTrigger || Object.op_Implicit((Object)(object)val.attachedRigidbody) || ((Component)val).gameObject.layer != prefab.layer) { throw new InvalidOperationException("Invalid chimney wall collider/support body"); } } Piece component = prefab.GetComponent<Piece>(); WearNTear component2 = prefab.GetComponent<WearNTear>(); if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)component2) || !Object.op_Implicit((Object)(object)prefab.GetComponent<ZNetView>()) || !Object.op_Implicit((Object)(object)component.m_icon) || !Object.op_Implicit((Object)(object)component2.m_new) || (Object)(object)component2.m_new != (Object)(object)component2.m_worn || (Object)(object)component2.m_new != (Object)(object)component2.m_broken || component2.m_health <= 0f || !component2.m_supports || !component2.m_noSupportWear) { throw new InvalidOperationException("Chimney lost required piece/wear/network/model references"); } if (component.m_resources == null || component.m_resources.Length == 0) { throw new InvalidOperationException("Stone template resource requirements missing"); } } internal static bool ConfigureResources(GameObject prefab, ModuleDefinition definition, ObjectDB db) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0070: 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_0088: Expected O, but got Unknown //IL_0092: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown GameObject itemPrefab = db.GetItemPrefab("Stone"); GameObject val = ((definition.Iron > 0) ? db.GetItemPrefab("Iron") : null); if (!Object.op_Implicit((Object)(object)itemPrefab) || !Object.op_Implicit((Object)(object)itemPrefab.GetComponent<ItemDrop>()) || (definition.Iron > 0 && (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.GetComponent<ItemDrop>())))) { return false; } List<Requirement> list = new List<Requirement> { new Requirement { m_resItem = itemPrefab.GetComponent<ItemDrop>(), m_amount = definition.Stone, m_recover = true } }; if (Object.op_Implicit((Object)(object)val)) { list.Add(new Requirement { m_resItem = val.GetComponent<ItemDrop>(), m_amount = definition.Iron, m_recover = true }); } prefab.GetComponent<Piece>().m_resources = list.ToArray(); return true; } private static void ClearDestroyedReferences(Component component) { FieldInfo[] fields = ((object)component).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (typeof(Object).IsAssignableFrom(fieldInfo.FieldType)) { object? value = fieldInfo.GetValue(component); Object val = (Object)((value is Object) ? value : null); if (val != null && !Object.op_Implicit(val)) { fieldInfo.SetValue(component, null); NarrowChimneysPlugin.Log.LogWarning((object)("Cleared destroyed template reference: " + ((object)component).GetType().Name + "." + fieldInfo.Name)); } } } } } internal sealed class ChimneyGeometry { internal readonly struct WallSpec { internal readonly Vector3 Centre; internal readonly Vector3 Size; internal WallSpec(Vector3 centre, Vector3 size) { Centre = centre; Size = size; } } internal static readonly WallSpec[] Walls = new WallSpec[4] { new WallSpec(new Vector3(0f, 0.5f, 0.375f), new Vector3(1f, 1f, 0.25f)), new WallSpec(new Vector3(0f, 0.5f, -0.375f), new Vector3(1f, 1f, 0.25f)), new WallSpec(new Vector3(0.375f, 0.5f, 0f), new Vector3(0.25f, 1f, 0.5f)), new WallSpec(new Vector3(-0.375f, 0.5f, 0f), new Vector3(0.25f, 1f, 0.5f)) }; internal readonly List<Vector3> Vertices = new List<Vector3>(); internal readonly List<Vector3> Normals = new List<Vector3>(); internal readonly List<Vector2> UV = new List<Vector2>(); internal readonly List<Vector2> AtlasUV = new List<Vector2>(); internal readonly List<float> Shades = new List<float>(); internal readonly List<int> Triangles = new List<int>(); internal static ChimneyGeometry Build() { ChimneyGeometry chimneyGeometry = new ChimneyGeometry(); for (int i = 0; i < 6; i++) { float y = ((float)i + 0.5f) / 6f; float[] array = ((i % 2 != 0) ? new float[4] { -0.5f, -0.25f, 0.25f, 0.5f } : new float[3] { -0.5f, 0f, 0.5f }); int[] array2; for (int j = 0; j < array.Length - 1; j++) { array2 = new int[2] { -1, 1 }; foreach (int num in array2) { chimneyGeometry.Brick(new Vector3((array[j] + array[j + 1]) / 2f, y, (float)num * 0.375f), new Vector3(array[j + 1] - array[j] - 0.014f, 0.15466668f, 0.24f), 0.82f + (float)((i + j) % 5) * 0.045f); } } array2 = new int[2] { -1, 1 }; foreach (int num2 in array2) { chimneyGeometry.Brick(new Vector3((float)num2 * 0.375f, y, 0f), new Vector3(0.24f, 0.15466668f, 0.49f), 0.84f + (float)(i % 4) * 0.05f); } } chimneyGeometry.MortarRing(0f, 1f, 0.45f, 0.29f); return chimneyGeometry; } internal void MortarRing(float bottom, float height, float outer, float inner) { Vector3[] array = new Vector3[4] { new Vector3(0f - outer, 0f, 0f - outer), new Vector3(outer, 0f, 0f - outer), new Vector3(outer, 0f, outer), new Vector3(0f - outer, 0f, outer) }; Vector3[] array2 = new Vector3[4] { new Vector3(0f - inner, 0f, 0f - inner), new Vector3(inner, 0f, 0f - inner), new Vector3(inner, 0f, inner), new Vector3(0f - inner, 0f, inner) }; Vector3 vector = new Vector3(0f, bottom, 0f); Vector3 vector2 = new Vector3(0f, bottom + height, 0f); for (int i = 0; i < 4; i++) { int num = (i + 1) % 4; Vector3 vector3 = Vector3.Normalize(array[i] + array[num]); Face(new Vector3[4] { array[i] + vector, array[num] + vector, array[num] + vector2, array[i] + vector2 }, Vector3.Zero, 0.35f, vector3); Face(new Vector3[4] { array2[i] + vector, array2[num] + vector, array2[num] + vector2, array2[i] + vector2 }, Vector3.Zero, 0.35f, -vector3); Face(new Vector3[4] { array[i] + vector2, array[num] + vector2, array2[num] + vector2, array2[i] + vector2 }, Vector3.Zero, 0.35f, Vector3.UnitY); Face(new Vector3[4] { array[i] + vector, array[num] + vector, array2[num] + vector, array2[i] + vector }, Vector3.Zero, 0.35f, -Vector3.UnitY); } } internal void MortarBox(Vector3 centre, Vector3 size) { Vector3 vector = size / 2f; Vector3[] surface = new Vector3[4] { centre + new Vector3(0f - vector.X, 0f - vector.Y, 0f - vector.Z), centre + new Vector3(vector.X, 0f - vector.Y, 0f - vector.Z), centre + new Vector3(vector.X, vector.Y, 0f - vector.Z), centre + new Vector3(0f - vector.X, vector.Y, 0f - vector.Z) }; Add(Prism(surface, new Vector3(0f, 0f, size.Z), 0.35f), Matrix4x4.Identity); } internal void Brick(Vector3 centre, Vector3 size, float shade) { Vector3 v = size / 2f; float num = Math.Min(Math.Min(size.X, Math.Min(size.Y, size.Z)) * 0.15f, 0.018f + (float)((int)Math.Abs(centre.X * 711f + centre.Y * 113f + centre.Z * 257f) % 5) * 0.001f); int seed = (int)Math.Abs(centre.X * 13717f + centre.Y * 27191f + centre.Z * 7739f + size.X * 313f + size.Z * 521f); int[] array; for (int i = 0; i < 3; i++) { array = new int[2] { -1, 1 }; foreach (int num2 in array) { int axis = (i + 1) % 3; int axis2 = (i + 2) % 3; Vector3[] array2 = new Vector3[4]; int[,] array3 = new int[4, 2] { { -1, -1 }, { 1, -1 }, { 1, 1 }, { -1, 1 } }; for (int k = 0; k < 4; k++) { array2[k] = Axis(i, (float)num2 * At(v, i)) + Axis(axis, (float)array3[k, 0] * (At(v, axis) - num)) + Axis(axis2, (float)array3[k, 1] * (At(v, axis2) - num)); } StoneFace(array2); } } for (int l = 0; l < 3; l++) { array = new int[2] { -1, 1 }; foreach (int num3 in array) { int[] array4 = new int[2] { -1, 1 }; foreach (int num4 in array4) { int axis3 = (l + 1) % 3; int axis4 = (l + 2) % 3; Vector3 vector = Axis(axis3, (float)num3 * At(v, axis3)) + Axis(axis4, (float)num4 * (At(v, axis4) - num)); Vector3 vector2 = Axis(axis3, (float)num3 * (At(v, axis3) - num)) + Axis(axis4, (float)num4 * At(v, axis4)); Vector3 vector3 = Axis(l, At(v, l) - num); StoneFace(new Vector3[4] { vector - vector3, vector + vector3, vector2 + vector3, vector2 - vector3 }); } } } array = new int[2] { -1, 1 }; foreach (int num5 in array) { int[] array4 = new int[2] { -1, 1 }; foreach (int num6 in array4) { int[] array5 = new int[2] { -1, 1 }; foreach (int num7 in array5) { StoneFace(new Vector3[3] { new Vector3((float)num5 * v.X, (float)num6 * (v.Y - num), (float)num7 * (v.Z - num)), new Vector3((float)num5 * (v.X - num), (float)num6 * v.Y, (float)num7 * (v.Z - num)), new Vector3((float)num5 * (v.X - num), (float)num6 * (v.Y - num), (float)num7 * v.Z) }); } } } void StoneFace(Vector3[] points) { for (int num8 = 0; num8 < points.Length; num8++) { Vector3 vector4 = points[num8]; float num9 = (float)((((int)Math.Round(vector4.X * 100000f) * 73856093) ^ ((int)Math.Round(vector4.Y * 100000f) * 19349663) ^ ((int)Math.Round(vector4.Z * 100000f) * 83492791) ^ seed) & 0x3FF) / 1023f * 0.012f; points[num8] = vector4 * (1f - num9); } Face(points, centre, shade); } } private void Face(Vector3[] points, Vector3 centre, float shade, Vector3? outward = null) { Vector3 vector = Vector3.Normalize(Vector3.Cross(points[1] - points[0], points[2] - points[0])); Vector3 zero = Vector3.Zero; Vector3[] array = points; foreach (Vector3 vector2 in array) { zero += vector2; } if (Vector3.Dot(vector, outward ?? zero) < 0f) { Array.Reverse(points); vector = -vector; } Vector3 vector3 = Vector3.Normalize(Vector3.Cross((Math.Abs(vector.Y) > 0.95f) ? Vector3.UnitZ : Vector3.UnitY, vector)); Vector3 vector4 = Vector3.Cross(vector, vector3); Vector2 vector5 = new Vector2(float.MaxValue); Vector2 value = new Vector2(float.MinValue); array = points; foreach (Vector3 vector6 in array) { Vector3 vector7 = centre + vector6; Vector2 value2 = new Vector2(Vector3.Dot(vector7, vector3), Vector3.Dot(vector7, vector4)) * 1.6f; vector5 = Vector2.Min(vector5, value2); value = Vector2.Max(value, value2); } float num = Math.Min(1f, 0.94f / Math.Max(0.0001f, Math.Max(value.X - vector5.X, value.Y - vector5.Y))); int num2 = (int)((uint)((int)(centre.X * 1703f) ^ (int)(centre.Y * 2719f) ^ (int)(centre.Z * 1381f) ^ (int)(vector.X * 127f + vector.Z * 311f + shade * 101f)) % 16u); Vector2 vector8 = new Vector2(num2 % 4 * 64 + 9, num2 / 4 * 64 + 9) / 256f; for (int j = 1; j < points.Length - 1; j++) { Vector3 vector9 = points[0]; Vector3 vector10 = points[j]; Vector3 vector11 = points[j + 1]; Vector3 item = Vector3.Normalize(Vector3.Cross(vector10 - vector9, vector11 - vector9)); int count = Vertices.Count; array = new Vector3[3] { vector9, vector10, vector11 }; foreach (Vector3 vector12 in array) { Vector3 vector13 = centre + vector12; Vertices.Add(vector13); Normals.Add(item); UV.Add(new Vector2(Vector3.Dot(vector13, vector3), Vector3.Dot(vector13, vector4)) * 1.6f); AtlasUV.Add(vector8 + (UV[UV.Count - 1] - vector5) * num * (23f / 128f)); Shades.Add(shade); } Triangles.Add(count); Triangles.Add(count + 1); Triangles.Add(count + 2); } } private static Vector3 Axis(int axis, float value) { return axis switch { 1 => new Vector3(0f, value, 0f), 0 => new Vector3(value, 0f, 0f), _ => new Vector3(0f, 0f, value), }; } private static float At(Vector3 v, int axis) { return axis switch { 1 => v.Y, 0 => v.X, _ => v.Z, }; } internal void Add(ChimneyGeometry source, Matrix4x4 transform) { int count = Vertices.Count; if (!Matrix4x4.Invert(transform, out var result)) { throw new ArgumentException("Singular mesh transform"); } Matrix4x4 matrix = Matrix4x4.Transpose(result); for (int i = 0; i < source.Vertices.Count; i++) { Vertices.Add(Vector3.Transform(source.Vertices[i], transform)); Normals.Add(Vector3.Normalize(Vector3.TransformNormal(source.Normals[i], matrix))); UV.Add(source.UV[i] + new Vector2(transform.M41 * 0.73f + transform.M43 * 0.31f, transform.M42 * 0.83f + transform.M43 * 0.23f)); Shades.Add(source.Shades[i]); AtlasUV.Add(source.AtlasUV[i]); } foreach (int triangle in source.Triangles) { Triangles.Add(count + triangle); } } internal static ChimneyGeometry Frustum(float bottomHalf, float topHalf, float bottom, float top) { ChimneyGeometry mesh = new ChimneyGeometry(); Vector3[] array = new Vector3[4]; Vector3[] array2 = new Vector3[4]; for (int i = 0; i < 4; i++) { float num = ((i != 0 && i != 3) ? 1 : (-1)); float num2 = ((i >= 2) ? 1 : (-1)); array[i] = new Vector3(num * bottomHalf, bottom, num2 * bottomHalf); array2[i] = new Vector3(num * topHalf, top, num2 * topHalf); } Vector3 centre = new Vector3(0f, (top + bottom) / 2f, 0f); FaceWorld((Vector3[])array.Clone()); FaceWorld((Vector3[])array2.Clone()); for (int j = 0; j < 4; j++) { FaceWorld(new Vector3[4] { array[j], array[(j + 1) % 4], array2[(j + 1) % 4], array2[j] }); } return mesh; void FaceWorld(Vector3[] points) { for (int k = 0; k < points.Length; k++) { points[k] -= centre; } mesh.Face(points, centre, 0.95f); } } internal static ChimneyGeometry Prism(Vector3[] surface, Vector3 extrusion, float shade) { ChimneyGeometry mesh = new ChimneyGeometry(); Vector3 centre = Vector3.Zero; foreach (Vector3 vector in surface) { centre += vector; } centre = centre / surface.Length + extrusion / 2f; Vector3[] array = new Vector3[surface.Length]; for (int j = 0; j < array.Length; j++) { array[j] = surface[j] + extrusion; } AddFace((Vector3[])surface.Clone()); AddFace(array); for (int k = 0; k < surface.Length; k++) { AddFace(new Vector3[4] { surface[k], surface[(k + 1) % surface.Length], surface[(k + 1) % surface.Length] + extrusion, surface[k] + extrusion }); } return mesh; void AddFace(Vector3[] points) { for (int l = 0; l < points.Length; l++) { points[l] -= centre; } mesh.Face(points, centre, shade); } } internal static ChimneyGeometry BevelledPrism(Vector3[] surface, Vector3 extrusion, float shade, float bevel) { ChimneyGeometry mesh = new ChimneyGeometry(); Vector3 zero = Vector3.Zero; foreach (Vector3 vector in surface) { zero += vector; } zero /= (float)surface.Length; Vector3 centre = zero + extrusion / 2f; Vector3 vector2 = Vector3.Normalize(extrusion) * bevel; Vector3[][] array = new Vector3[4][]; for (int j = 0; j < 4; j++) { array[j] = new Vector3[surface.Length]; for (int k = 0; k < surface.Length; k++) { array[j][k] = j switch { 2 => surface[k] + extrusion - vector2, 1 => surface[k] + vector2, 0 => zero + (surface[k] - zero) * 0.94f, _ => zero + (surface[k] - zero) * 0.94f + extrusion, }; } } AddFace((Vector3[])array[0].Clone()); AddFace((Vector3[])array[3].Clone()); for (int l = 0; l < 3; l++) { for (int m = 0; m < surface.Length; m++) { AddFace(new Vector3[4] { array[l][m], array[l][(m + 1) % surface.Length], array[l + 1][(m + 1) % surface.Length], array[l + 1][m] }); } } return mesh; void AddFace(Vector3[] points) { for (int n = 0; n < points.Length; n++) { points[n] -= centre; } mesh.Face(points, centre, shade); } } } internal sealed class SurfacePreview { internal MasonryAtlas Atlas { get; set; } internal byte[] NativeColor { get; set; } internal byte[] NativeNormal { get; set; } internal int NativeSize { get; set; } = 128; internal bool Diagnostic { get; set; } internal bool Shadow { get; set; } } internal static class ChimneyIcon { internal static byte[] Render(ChimneyGeometry mesh, int size, Vector3? view = null, bool night = false, bool fitSmall = false, SurfacePreview style = null) { byte[] array = new byte[size * size * 4]; float[] array2 = new float[size * size]; for (int i = 0; i < array2.Length; i++) { array2[i] = float.NegativeInfinity; } Vector3 vector = new Vector3(float.MaxValue); Vector3 vector2 = new Vector3(float.MinValue); foreach (Vector3 vertex in mesh.Vertices) { vector = Vector3.Min(vector, vertex); vector2 = Vector3.Max(vector2, vertex); } Vector3 vector3 = (vector + vector2) / 2f; float num = Math.Max(fitSmall ? 0.1f : 1f, Math.Max(vector2.Y - vector.Y, Math.Max(vector2.X - vector.X, vector2.Z - vector.Z))); Vector3 vector4 = Vector3.Normalize(view ?? new Vector3(1f, 0.8f, 1f)); Vector3 vector5 = Vector3.Normalize(Vector3.Cross((Math.Abs(vector4.Y) > 0.99f) ? Vector3.UnitZ : Vector3.UnitY, vector4)); Vector3 up = Vector3.Normalize(Vector3.Cross(vector4, vector5)); Vector3 vector6 = Vector3.Normalize(night ? new Vector3(-0.4f, 0.15f, 0.8f) : new Vector3(-0.3f, 1f, 0.6f)); StoneMaps maps = StoneSurface.Maps; for (int j = 0; j < mesh.Triangles.Count; j += 3) { int index = mesh.Triangles[j]; int index2 = mesh.Triangles[j + 1]; int index3 = mesh.Triangles[j + 2]; int num2 = SurfacePalette.Slot(mesh, j); bool flag = (num2 < 3 || num2 == 4) && style?.Atlas != null; bool flag2 = (num2 < 3 || num2 == 4) && style?.NativeColor != null; List<Vector2> list = (flag ? mesh.AtlasUV : mesh.UV); if (Vector3.Dot(mesh.Normals[index], vector4) <= 0f) { continue; } Vector3 vector7 = Project((mesh.Vertices[index] - vector3) / num, vector5, up, vector4, size); Vector3 vector8 = Project((mesh.Vertices[index2] - vector3) / num, vector5, up, vector4, size); Vector3 vector9 = Project((mesh.Vertices[index3] - vector3) / num, vector5, up, vector4, size); float num3 = Edge(vector7, vector8, vector9.X, vector9.Y); if (Math.Abs(num3) < 0.0001f) { continue; } int num4 = Math.Max(0, (int)Math.Floor(Math.Min(vector7.X, Math.Min(vector8.X, vector9.X)))); int num5 = Math.Min(size - 1, (int)Math.Ceiling(Math.Max(vector7.X, Math.Max(vector8.X, vector9.X)))); int num6 = Math.Max(0, (int)Math.Floor(Math.Min(vector7.Y, Math.Min(vector8.Y, vector9.Y)))); int num7 = Math.Min(size - 1, (int)Math.Ceiling(Math.Max(vector7.Y, Math.Max(vector8.Y, vector9.Y)))); Vector3 vector10 = mesh.Vertices[index2] - mesh.Vertices[index]; Vector3 vector11 = mesh.Vertices[index3] - mesh.Vertices[index]; Vector2 vector12 = list[index2] - list[index]; Vector2 vector13 = list[index3] - list[index]; float num8 = vector12.X * vector13.Y - vector12.Y * vector13.X; Vector3 vector14 = ((Math.Abs(num8) > 1E-06f) ? Vector3.Normalize((vector10 * vector13.Y - vector11 * vector12.Y) / num8) : Vector3.UnitX); Vector3 vector15 = ((Math.Abs(num8) > 1E-06f) ? Vector3.Normalize((vector11 * vector12.X - vector10 * vector13.X) / num8) : Vector3.UnitY); for (int k = num6; k <= num7; k++) { for (int l = num4; l <= num5; l++) { float num9 = Edge(vector8, vector9, (float)l + 0.5f, (float)k + 0.5f) / num3; float num10 = Edge(vector9, vector7, (float)l + 0.5f, (float)k + 0.5f) / num3; float num11 = 1f - num9 - num10; if (num9 < 0f || num10 < 0f || num11 < 0f) { continue; } int num12 = k * size + l; float num13 = num9 * vector7.Z + num10 * vector8.Z + num11 * vector9.Z; if (num13 <= array2[num12]) { continue; } array2[num12] = num13; Vector2 vector16 = list[index] * num9 + list[index2] * num10 + list[index3] * num11; int num14 = (flag ? 256 : (flag2 ? style.NativeSize : 512)); int num15 = (int)((vector16.X - (float)Math.Floor(vector16.X)) * (float)(num14 - 1)); int num16 = (int)((vector16.Y - (float)Math.Floor(vector16.Y)) * (float)(num14 - 1)); int num17 = (num16 * num14 + num15) * 4; Vector3 vector17; if (flag || flag2) { byte[] array3 = (flag ? style.Atlas.Normal : style.NativeNormal); vector17 = SurfacePalette.DecodeNormal(array3[num17], array3[num17 + 1], array3[num17 + 3]); } else { vector17 = new Vector3((float)(int)maps.NormalRGB[num17] / 127.5f - 1f, (float)(int)maps.NormalRGB[num17 + 1] / 127.5f - 1f, (float)(int)maps.NormalRGB[num17 + 2] / 127.5f - 1f); } if (num2 == 3 || num2 == 5 || (style != null && style.Diagnostic)) { vector17 = Vector3.UnitZ; } Vector3 vector18 = Vector3.Normalize(vector14 * vector17.X + vector15 * vector17.Y + mesh.Normals[index] * vector17.Z); float num18 = Math.Max(0f, Vector3.Dot(vector18, vector6)); float num19 = ((flag || flag2) ? 1f : ((float)(int)maps.Occlusion[num17 + 1] / 255f)); float num20 = num2 switch { 1 => 1f, 0 => 0.96f, 4 => 0.94f, 5 => 0.65f, 3 => 0.7f, _ => 1.035f, }; for (int m = 0; m < 3; m++) { float num21 = ((!night) ? (0.28f + 0.85f * num18) : (m switch { 1 => 0.1f + 0.48f * num18, 0 => 0.13f + 0.95f * num18, _ => 0.16f + 0.2f * num18, })); float num22 = (flag ? ((float)(int)style.Atlas.Albedo[num17 + m] / 255f) : (flag2 ? ((float)(int)style.NativeColor[num17 + m] / 255f) : ((float)(int)maps.Albedo[num17 + m] / 255f))); if (num2 == 3 || num2 == 5) { num22 = 0.24f + 0.08f * (float)((num15 * 13 + num16 * 7) % 17) / 16f; } if (style != null && style.Diagnostic) { Vector3 vector19 = SurfacePalette.DebugColor(num2); num22 = m switch { 1 => vector19.Y, 0 => vector19.X, _ => vector19.Z, }; num20 = 1f; } if (style != null && style.Shadow) { num21 = 0.21f; } float num23 = (float)Math.Pow(Math.Pow(num22, 2.2) * (double)num21 * (double)num19 * (double)num20, 0.45454545454545453); array[num12 * 4 + m] = (byte)Math.Max(0f, Math.Min(255f, num23 * 255f)); } array[num12 * 4 + 3] = byte.MaxValue; } } } return array; } private static Vector3 Project(Vector3 v, Vector3 right, Vector3 up, Vector3 toward, int size) { return new Vector3((float)size * (0.5f + Vector3.Dot(v, right) * 0.62f), (float)size * (0.5f + Vector3.Dot(v, up) * 0.62f), Vector3.Dot(v, toward)); } private static float Edge(Vector3 a, Vector3 b, float x, float y) { return (b.X - a.X) * (y - a.Y) - (b.Y - a.Y) * (x - a.X); } } internal sealed class ChimneyRegistry { private GameObject storage; private readonly List<GameObject> prefabs = new List<GameObject>(); private ZNetScene readyScene; private readonly HashSet<PieceTable> loggedTables = new HashSet<PieceTable>(); private readonly HashSet<List<Piece>> caches = new HashSet<List<Piece>>(); internal void BeforeSceneAwake(ZNetScene scene) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown readyScene = null; if (!NarrowChimneysPlugin.BuildableChimney.Value) { return; } try { if (prefabs.Count == 0) { GameObject val = scene.m_prefabs.Find((GameObject p) => Object.op_Implicit((Object)(object)p) && ((Object)p).name == "stone_wall_1x1"); if (!Object.op_Implicit((Object)(object)val)) { throw new InvalidOperationException("stone_wall_1x1 not found"); } if (!Object.op_Implicit((Object)(object)storage)) { storage = new GameObject("NarrowChimneys_PrefabStorage"); storage.SetActive(false); Object.DontDestroyOnLoad((Object)(object)storage); } try { ModuleDefinition[] all = ModuleCatalog.All; foreach (ModuleDefinition definition in all) { prefabs.Add(ChimneyFactory.Create(val, storage.transform, definition)); } } catch { foreach (GameObject prefab2 in prefabs) { Object.Destroy((Object)(object)prefab2); } prefabs.Clear(); throw; } } HashSet<int> hashSet = new HashSet<int>(); foreach (GameObject prefab in prefabs) { int prefabHash = scene.GetPrefabHash(prefab); if (!hashSet.Add(prefabHash)) { throw new InvalidOperationException("Internal module hash collision: " + ((Object)prefab).name); } foreach (GameObject prefab3 in scene.m_prefabs) { CheckCollision(scene, prefab3, prefab, prefabHash); } foreach (GameObject nonNetViewPrefab in scene.m_nonNetViewPrefabs) { CheckCollision(scene, nonNetViewPrefab, prefab, prefabHash); } if (scene.m_prefabs.FindAll((GameObject p) => (Object)(object)p == (Object)(object)prefab).Count > 1) { throw new InvalidOperationException("Duplicate module in prefab list: " + ((Object)prefab).name); } } foreach (GameObject prefab4 in prefabs) { if (!scene.m_prefabs.Contains(prefab4)) { scene.m_prefabs.Add(prefab4); } NarrowChimneysPlugin.Detail($"Module queued {((Object)prefab4).name}, hash={scene.GetPrefabHash(prefab4)}, activeSelf={prefab4.activeSelf}, live={prefab4.activeInHierarchy}"); } NarrowChimneysPlugin.Log.LogInfo((object)"Queued 5 chimney modules, optional bracket and legacy NC_Chimney before ZNetScene.Awake"); } catch (Exception ex) { scene.m_prefabs.RemoveAll((GameObject p) => prefabs.Contains(p)); foreach (PieceTable loggedTable in loggedTables) { if (Object.op_Implicit((Object)(object)loggedTable)) { loggedTable.m_pieces.RemoveAll((GameObject p) => prefabs.Contains(p)); } } foreach (List<Piece> cache in caches) { cache.RemoveAll((Piece p) => prefabs.Exists((GameObject go) => Object.op_Implicit((Object)(object)go) && (Object)(object)go.GetComponent<Piece>() == (Object)(object)p)); } NarrowChimneysPlugin.Log.LogError((object)("Module registration aborted atomically: " + ex)); } } private static void CheckCollision(ZNetScene scene, GameObject candidate, GameObject prefab, int hash) { if (Object.op_Implicit((Object)(object)candidate) && (Object)(object)candidate != (Object)(object)prefab && scene.GetPrefabHash(candidate) == hash) { throw new InvalidOperationException(((Object)prefab).name + " hash/name conflict with " + ((Object)candidate).name); } } internal void AfterSceneAwake(ZNetScene scene) { if (!NarrowChimneysPlugin.BuildableChimney.Value || prefabs.Count == 0) { return; } foreach (GameObject prefab in prefabs) { if ((Object)(object)scene.GetPrefab(((Object)prefab).name) != (Object)(object)prefab || (Object)(object)scene.GetPrefab(scene.GetPrefabHash(prefab)) != (Object)(object)prefab) { NarrowChimneysPlugin.Log.LogError((object)("Module lookup validation failed: " + ((Object)prefab).name)); return; } } readyScene = scene; NarrowChimneysPlugin.Log.LogInfo((object)"All 7 prefab lookups validated, including legacy NC_Chimney"); RegisterHammer(ObjectDB.instance); } internal void RegisterHammer(ObjectDB db) { if (!Object.op_Implicit((Object)(object)readyScene) || !Object.op_Implicit((Object)(object)db)) { return; } try { GameObject itemPrefab = db.GetItemPrefab("Hammer"); PieceTable val = ((!Object.op_Implicit((Object)(object)itemPrefab)) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_buildPieces); if (!Object.op_Implicit((Object)(object)val)) { return; } foreach (GameObject piece in val.m_pieces) { if (Object.op_Implicit((Object)(object)piece) && ModuleCatalog.Find(((Object)piece).name) != null && !prefabs.Contains(piece)) { throw new InvalidOperationException("Foreign module in hammer table: " + ((Object)piece).name); } } List<Piece> allBuildPieces = db.GetAllBuildPieces(false); caches.Add(allBuildPieces); foreach (GameObject prefab in prefabs) { ModuleDefinition moduleDefinition = ModuleCatalog.Find(((Object)prefab).name); if (!moduleDefinition.Visible) { continue; } if (!ChimneyFactory.ConfigureResources(prefab, moduleDefinition, db)) { NarrowChimneysPlugin.Detail("Waiting for resources: " + moduleDefinition.Id); continue; } if (!val.m_pieces.Contains(prefab)) { val.m_pieces.Add(prefab); } Piece component = prefab.GetComponent<Piece>(); if (!allBuildPieces.Contains(component)) { allBuildPieces.Add(component); } } loggedTables.RemoveWhere((PieceTable t) => !Object.op_Implicit((Object)(object)t)); if (loggedTables.Add(val)) { NarrowChimneysPlugin.Log.LogInfo((object)"Hammer: 5 chimney modules + optional bracket registered; legacy network prefab preserved"); } } catch (Exception ex) { NarrowChimneysPlugin.ErrorOnce("Hammer module registration failed", ex); } } } public sealed class ChimneyVent : MonoBehaviour { [SerializeField] internal ModuleKind Kind; private static readonly List<ChimneyVent> instances = new List<ChimneyVent>(); private void OnEnable() { ZNetView component = ((Component)this).GetComponent<ZNetView>(); if (Object.op_Implicit((Object)(object)component) && component.IsValid() && !instances.Contains(this)) { instances.Add(this); } } private void OnDisable() { instances.Remove(this); } private void Start() { OnEnable(); } internal static bool TryTarget(Vector3 position, float radius, Func<Vector3, Vector3, float, bool> clear, out Vector3 target) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) target = position; foreach (ChimneyVent instance in instances) { ChimneyVent cap = instance; if (!Object.op_Implicit((Object)(object)cap)) { continue; } Vector3 val = ((Component)cap).transform.position - position; if (!(((Vector3)(ref val)).sqrMagnitude > 4f)) { Vector3 val2 = ((Component)cap).transform.InverseTransformPoint(position); if (ModuleVentRoutes.Choose(cap.Kind, new Vector3(val2.x, val2.y, val2.z), radius, Check, out var target2)) { target = ((Component)cap).transform.TransformPoint(new Vector3(target2.X, target2.Y, target2.Z)); return true; } } bool Check(Vector3 a, Vector3 b, float r) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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_0072: 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_007e: Unknown result type (might be due to invalid IL or missing references) Vector3 val3 = ((Component)cap).transform.TransformPoint(new Vector3(a.X, a.Y, a.Z)); Vector3 val4 = ((Component)cap).transform.TransformPoint(new Vector3(b.X, b.Y, b.Z)); return clear(new Vector3(val3.x, val3.y, val3.z), new Vector3(val4.x, val4.y, val4.z), r); } } return false; } } [HarmonyPatch(typeof(SmokeSpawner), "Spawn")] internal static class OutletEmissionPatch { internal static void Prefix(SmokeSpawner __instance, out float __state) { __state = __instance.m_spawnRadius; if (NarrowChimneysPlugin.CenterEmission.Value && Object.op_Implicit((Object)(object)__instance.m_smokePrefab) && Object.op_Implicit((Object)(object)__instance.m_smokePrefab.GetComponent<Smoke>())) { __instance.m_spawnRadius = 0f; } NarrowChimneysPlugin.Detail($"Emission {((Object)__instance).GetInstanceID()}: originalScatter={__state}; effectiveScatter={__instance.m_spawnRadius}; ventilationRadius={__instance.m_testRadius}; mask={((LayerMask)(ref __instance.m_testMask)).value}; interval={__instance.m_interval}; activeSmoke={Smoke.Instances.Count}"); } internal static void Finalizer(SmokeSpawner __instance, float __state) { __instance.m_spawnRadius = __state; } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class SceneAwakePatch { private static void Prefix(ZNetScene __instance) { StoneMaterials.RegisterSources(__instance.m_prefabs); NarrowChimneysPlugin.Registry.BeforeSceneAwake(__instance); } private static void Postfix(ZNetScene __instance) { NarrowChimneysPlugin.Registry.AfterSceneAwake(__instance); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] internal static class ObjectDbAwakePatch { private static void Postfix(ObjectDB __instance) { NarrowChimneysPlugin.Registry.RegisterHammer(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] internal static class ObjectDbCopyPatch { private static void Postfix(ObjectDB __instance) { NarrowChimneysPlugin.Registry.RegisterHammer(__instance); } } [HarmonyPatch(typeof(ZNetView), "Awake")] internal static class ChimneyInstancePatch { private static void Postfix(ZNetView __instance) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (ZNetView.m_forceDisableInit || ZDOMan.instance == null || ModuleCatalog.Find(((Object)__instance).name) == null) { return; } try { ZDO zDO = __instance.GetZDO(); if (zDO == null) { throw new InvalidOperationException("Live chimney has no ZDO"); } NarrowChimneysPlugin.Log.LogInfo((object)$"Chimney instance: id={((Object)__instance).GetInstanceID()}; active={((Component)__instance).gameObject.activeInHierarchy}; persistent={zDO.Persistent}; hash={zDO.GetPrefab()}; position={((Component)__instance).transform.position}"); } catch (Exception ex) { NarrowChimneysPlugin.ErrorOnce("Live chimney validation failed", ex); } } } [HarmonyPatch(typeof(ZNetScene), "RemoveObjects")] internal static class SceneRemoveDiagnostics { private static void Finalizer(Exception __exception) { if (__exception != null) { NarrowChimneysPlugin.ErrorOnce("ZNetScene.RemoveObjects failed (exception preserved); inspect full stack and other mods", __exception); } } } internal sealed class MasonryAtlas { internal readonly struct Fragment { internal readonly int X; internal readonly int Y; internal readonly int Width; internal readonly int Height; internal Fragment(int x, int y, int width, int height) { X = x; Y = y; Width = width; Height = height; } } internal const int Size = 256; internal const int Columns = 4; internal const int Pitch = 64; internal const int Padding = 8; internal readonly byte[] Albedo = new byte[262144]; internal readonly byte[] Normal = new byte[262144]; internal readonly List<Fragment> Fragments = new List<Fragment>(); internal static bool[] Coverage(Vector2[] uv, int[] triangles, int width, int height) { bool[] array = new bool[width * height]; for (int i = 0; i < triangles.Length; i += 3) { Vector2 vector = uv[triangles[i]]; Vector2 vector2 = uv[triangles[i + 1]]; Vector2 vector3 = uv[triangles[i + 2]]; float num = Cross(vector2 - vector, vector3 - vector); if (Math.Abs(num) < 1E-07f) { continue; } int num2 = Math.Max(0, (int)Math.Floor(Math.Min(vector.X, Math.Min(vector2.X, vector3.X)) * (float)width)); int num3 = Math.Min(width - 1, (int)Math.Ceiling(Math.Max(vector.X, Math.Max(vector2.X, vector3.X)) * (float)width)); int num4 = Math.Max(0, (int)Math.Floor(Math.Min(vector.Y, Math.Min(vector2.Y, vector3.Y)) * (float)height)); int num5 = Math.Min(height - 1, (int)Math.Ceiling(Math.Max(vector.Y, Math.Max(vector2.Y, vector3.Y)) * (float)height)); for (int j = num4; j <= num5; j++) { for (int k = num2; k <= num3; k++) { Vector2 vector4 = new Vector2(((float)k + 0.5f) / (float)width, ((float)j + 0.5f) / (float)height); float num6 = Cross(vector2 - vector4, vector3 - vector4) / num; float num7 = Cross(vector3 - vector4, vector - vector4) / num; if (num6 >= -1E-06f && num7 >= -1E-06f && num6 + num7 <= 1.000001f) { array[j * width + k] = true; } } } } return array; static float Cross(Vector2 p, Vector2 q) { return p.X * q.Y - p.Y * q.X; } } internal static MasonryAtlas Build(byte[] albedo, byte[] normal, int width, int height, Vector2[] uv, int[] triangles) { if (albedo.Length != width * height * 4 || normal.Length != albedo.Length) { throw new ArgumentException("Atlas source maps must have matching dimensions/channels"); } bool[] array = Coverage(uv, triangles, width, height); List<Fragment> list = new List<Fragment>(); int[] array2 = new int[4] { 16, 12, 8, 4 }; foreach (int num in array2) { for (int j = 1; j + num < height; j += 2) { for (int k = 1; k + num < width; k += 2) { bool flag = true; for (int l = j - 1; l <= j + num && flag; l++) { for (int m = k - 1; m <= k + num; m++) { int num2 = l * width + m; int num3 = num2 * 4; if (!array[num2] || albedo[num3 + 3] < 250 || Math.Min(albedo[num3], Math.Min(albedo[num3 + 1], albedo[num3 + 2])) > 215) { flag = false; break; } } } if (flag) { list.Add(new Fragment(k, j, num, num)); } } } if (list.Count >= 16) { break; } list.Clear(); } if (list.Count == 0) { throw new InvalidOperationException("No safe fragment covered by original mesh UVs; raw native atlas must not be assigned to procedural UVs"); } MasonryAtlas masonryAtlas = new MasonryAtlas(); HashSet<int> hashSet = new HashSet<int>(); for (int n = 0; n < 16; n++) { int num4 = 17 % list.Count; if (n > 0) { double num5 = -1.0; for (int num6 = 0; num6 < list.Count; num6++) { if (hashSet.Contains(num6)) { continue; } Fragment fragment = list[num6]; double num7 = double.MaxValue; foreach (Fragment fragment2 in masonryAtlas.Fragments) { double num8 = fragment.X - fragment2.X; double num9 = fragment.Y - fragment2.Y; num7 = Math.Min(num7, num8 * num8 + num9 * num9); } if (num7 > num5) { num5 = num7; num4 = num6; } } } hashSet.Add(num4); Fragment item = list[num4]; masonryAtlas.Fragments.Add(item); for (int num10 = 0; num10 < 64; num10++) { for (int num11 = 0; num11 < 64; num11++) { float num12 = Math.Max(0f, Math.Min(1f, (float)(num11 - 8) / 47f)); float num13 = Math.Max(0f, Math.Min(1f, (float)(num10 - 8) / 47f)); int num14 = item.X + (int)Math.Round(num12 * (float)(item.Width - 1)); int num15 = ((item.Y + (int)Math.Round(num13 * (float)(item.Height - 1))) * width + num14) * 4; int num16 = ((n / 4 * 64 + num10) * 256 + n % 4 * 64 + num11) * 4; for (int num17 = 0; num17 < 4; num17++) { masonryAtlas.Albedo[num16 + num17] = albedo[num15 + num17]; masonryAtlas.Normal[num16 + num17] = normal[num15 + num17]; } } } } return masonryAtlas; } } internal enum ModuleKind { Section, Open, Grate, Roof, Arch, Bracket } internal sealed class ModuleDefinition { internal readonly string Id; internal readonly string Name; internal readonly string Description; internal readonly ModuleKind Kind; internal readonly int Stone; internal readonly int Iron; internal readonly bool Visible; internal ModuleDefinition(string id, string name, string description, ModuleKind kind, int stone, int iron = 0, bool visible = true) { Id = id; Name = name; Description = description; Kind = kind; Stone = stone; Iron = iron; Visible = visible; } } internal static class ModuleCatalog { internal static readonly ModuleDefinition[] All = new ModuleDefinition[7] { new ModuleDefinition("NC_Chimney", "Дымоход (старый)", "Совместимая секция 0.4.0. Каменная опора обязательна, кронштейн необязателен.", ModuleKind.Section, 6, 0, visible: false), new ModuleDefinition("nc_chimney_section", "Дымоход: секция 1 м", "Полая каменная секция 1×1×1 м. Канал 0,5 м. Соединяйте по высоте; нужна достаточная опора.", ModuleKind.Section, 6), new ModuleDefinition("nc_chimney_cap_open", "Дымоход: открытый оголовок", "Каменный обод на четырёх стойках. Открытый верх и боковые проёмы.", ModuleKind.Open, 4), new ModuleDefinition("nc_chimney_cap_grate", "Дымоход: оголовок с решёткой", "Каменная решётка с четырьмя сквозными отверстиями шириной 0,325 м; не закрывает проход дыму.", ModuleKind.Grate, 6), new ModuleDefinition("nc_chimney_cap_roof", "Дымоход: оголовок с крышей", "Четырёхскатная каменная крыша. Дым выходит через открытые боковые проёмы.", ModuleKind.Roof, 8), new ModuleDefinition("nc_chimney_cap_arch", "Дымоход: арочный оголовок", "Каменные арки, крыша и навершие. Настоящие боковые вентиляционные проёмы.", ModuleKind.Arch, 10), new ModuleDefinition("nc_chimney_bracket", "Дымоход: каменный кронштейн", "Необязательная опора с железным усилением. Крепится к прочной стене или перекрытию; не заменяет их поддержку.", ModuleKind.Bracket, 6, 2) }; internal static ModuleDefinition Find(string name) { if (name != null && name.EndsWith("(Clone)", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - 7); } ModuleDefinition[] all = All; foreach (ModuleDefinition moduleDefinition in all) { if (moduleDefinition.Id == name) { return moduleDefinition; } } return null; } } internal sealed class ModuleGeometry { internal sealed class Collision { internal Vector3 Centre; internal Vector3 Size; internal Quaternion Rotation = Quaternion.Identity; internal ChimneyGeometry Mesh; } internal sealed class Snap { internal string Name; internal Vector3 Position; internal Snap(string name, Vector3 position) { Name = name; Position = position; } } internal ChimneyGeometry Model = new ChimneyGeometry(); internal readonly List<Collision> Collisions = new List<Collision>(); internal readonly List<Snap> Snaps = new List<Snap>(); internal float Height; internal Vector3 CentreOfMass { get { float num = 0f; Vector3 zero = Vector3.Zero; for (int i = 0; i < Model.Triangles.Count; i += 3) { Vector3 vector = Model.Vertices[Model.Triangles[i]]; Vector3 vector2 = Model.Vertices[Model.Triangles[i + 1]]; Vector3 vector3 = Model.Vertices[Model.Triangles[i + 2]]; float num2 = Vector3.Dot(vector, Vector3.Cross(vector2, vector3)) / 6f; num += num2; zero += (vector + vector2 + vector3) * (num2 / 4f); } if (!(num > 1E-05f)) { return new Vector3(0f, Height / 2f, 0f); } return zero / num; } } internal static ModuleGeometry Build(ModuleKind kind) { ModuleGeometry moduleGeometry = new ModuleGeometry(); int[] array; switch (kind) { case ModuleKind.Section: { moduleGeometry.Model = ChimneyGeometry.Build(); moduleGeometry.Height = 1f; ChimneyGeometry.WallSpec[] walls = ChimneyGeometry.Walls; for (int i = 0; i < walls.Length; i++) { ChimneyGeometry.WallSpec wallSpec = walls[i]; moduleGeometry.Collisions.Add(new Collision { Centre = wallSpec.Centre, Size = wallSpec.Size }); } break; } case ModuleKind.Bracket: { moduleGeometry.Height = 0.8f; moduleGeometry.Ring(0.64f, 0.16f, 0.5f, 0.25f); moduleGeometry.Box(new Vector3(0f, 0.36f, -0.57f), new Vector3(0.94f, 0.72f, 0.18f)); array = new int[2] { -1, 1 }; for (int i = 0; i < array.Length; i++) { float x = (float)array[i] * 0.375f - 0.075f; ChimneyGeometry chimneyGeometry3 = ChimneyGeometry.Prism(new Vector3[3] { new Vector3(x, 0.12f, -0.48f), new Vector3(x, 0.64f, -0.48f), new Vector3(x, 0.64f, 0.43f) }, new Vector3(0.15f, 0f, 0f), -0.75f); moduleGeometry.Model.Add(chimneyGeometry3, Matrix4x4.Identity); moduleGeometry.Collisions.Add(new Collision { Mesh = chimneyGeometry3 }); } moduleGeometry.Snaps.Add(new Snap("WallAnchor", new Vector3(0f, 0.32f, -0.66f))); moduleGeometry.Snaps.Add(new Snap("CeilingAnchor", new Vector3(0f, 0.8f, -0.57f))); break; } default: { moduleGeometry.Ring(0f, 0.18f, 0.5f, 0.25f); float num = ((kind == ModuleKind.Roof) ? 0.18f : 0.16f); array = new int[2] { -1, 1 }; foreach (int num2 in array) { int[] array2 = new int[2] { -1, 1 }; foreach (int num3 in array2) { int num4 = ((kind == ModuleKind.Arch) ? 2 : 3); float num5 = ((kind == ModuleKind.Grate) ? 0.16f : 0.18f); moduleGeometry.Model.MortarBox(new Vector3((float)num2 * 0.4f, 0.18f + (float)num4 * num / 2f, (float)num3 * 0.4f), new Vector3(num5 - 0.07f, (float)num4 * num, num5 - 0.07f)); for (int k = 0; k < ((kind == ModuleKind.Arch) ? 2 : 3); k++) { moduleGeometry.Box(new Vector3((float)num2 * 0.4f, 0.18f + ((float)k + 0.5f) * num, (float)num3 * 0.4f), new Vector3((kind == ModuleKind.Grate) ? 0.16f : 0.18f, num - 0.004f, (kind == ModuleKind.Grate) ? 0.16f : 0.18f)); } } } if (kind == ModuleKind.Open) { moduleGeometry.Ring(0.66f, 0.18f, 0.56f, 0.28f); moduleGeometry.Height = 0.84f; break; } if (kind == ModuleKind.Grate) { moduleGeometry.Ring(0.66f, 0.18f, 0.56f, 0.36f); moduleGeometry.Box(new Vector3(0f, 0.75f, 0f), new Vector3(0.07f, 0.18f, 0.72f)); moduleGeometry.Box(new Vector3(0f, 0.75f, 0f), new Vector3(0.72f, 0.18f, 0.07f)); moduleGeometry.Height = 0.84f; break; } float num6 = ((kind == ModuleKind.Arch) ? 0.94f : 0.72f); if (kind == ModuleKind.Arch) { moduleGeometry.Arches(); } ChimneyGeometry chimneyGeometry = RoofMasonry(num6); moduleGeometry.Model.Add(chimneyGeometry, Matrix4x4.Identity); moduleGeometry.Collisions.Add(new Collision { Mesh = chimneyGeometry }); moduleGeometry.Height = num6 + 0.32f; if (kind == ModuleKind.Arch) { moduleGeometry.Box(new Vector3(0f, moduleGeometry.Height + 0.035f, 0f), new Vector3(0.21f, 0.07f, 0.21f)); ChimneyGeometry chimneyGeometry2 = ChimneyGeometry.Frustum(0.09f, 0.015f, moduleGeometry.Height + 0.07f, moduleGeometry.Height + 0.21f); moduleGeometry.Model.Add(chimneyGeometry2, Matrix4x4.Identity); moduleGeometry.Collisions.Add(new Collision { Mesh = chimneyGeometry2 }); moduleGeometry.Height += 0.21f; } break; } } moduleGeometry.Snaps.Add(new Snap("Bottom", Vector3.Zero)); moduleGeometry.Snaps.Add(new Snap("Top", new Vector3(0f, moduleGeometry.Height, 0f))); array = new int[2] { -1, 1 }; foreach (int num7 in array) { int[] array2 = new int[2] { -1, 1 }; foreach (int num8 in array2) { moduleGeometry.Snaps.Add(new Snap("BottomRim", new Vector3((float)num7 * 0.5f, 0f, (float)num8 * 0.5f))); moduleGeometry.Snaps.Add(new Snap("TopRim", new Vector3((float)num7 * 0.5f, moduleGeometry.Height, (float)num8 * 0.5f))); } } return moduleGeometry; } private void Box(Vector3 centre, Vector3 size, Quaternion? rotation = null) { Quaternion quaternion = rotation ?? Quaternion.Identity; ChimneyGeometry chimneyGeometry = new ChimneyGeometry(); chimneyGeometry.Brick(Vector3.Zero, size, 0.82f + (float)(Collisions.Count % 5) * 0.045f); Model.Add(chimneyGeometry, Matrix4x4.CreateFromQuaternion(quaternion) * Matrix4x4.CreateTranslation(centre)); Collisions.Add(new Collision { Centre = centre, Size = size, Rotation = quaternion }); } private void Ring(float bottom, float height, float outer, float inner) { float num = outer - inner; float num2 = (outer + inner) / 2f; Model.MortarRing(bottom + 0.03f, height - 0.06f, outer - 0.04f, inner + 0.04f); int[] array = new int[2] { -1, 1 }; foreach (int num3 in array) { for (int j = 0; j < 3; j++) { Box(new Vector3(0f - outer + ((float)j + 0.5f) * 2f * outer / 3f, bottom + height / 2f, (float)num3 * num2), new Vector3(2f * outer / 3f, height, num)); } for (int k = 0; k < 2; k++) { Box(new Vector3((float)num3 * num2, bottom + height / 2f, 0f - inner + ((float)k + 0.5f) * inner), new Vector3(num, height, inner)); } } } private void Arches() { for (int i = 0; i < 4; i++) { Quaternion quaternion = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)i * MathF.PI / 2f); for (int j = 0; j < 9; j++) { float angle = (float)j * MathF.PI / 9f + 0.012f; float angle2 = (float)(j + 1) * MathF.PI / 9f - 0.012f; ChimneyGeometry source = ChimneyGeometry.BevelledPrism(new Vector3[4] { Point(0.44f, angle), Point(0.44f, angle2), Point(0.285f, angle2), Point(0.285f, angle) }, new Vector3(0f, 0f, 0.22f), 0.83f + (float)(j % 4) * 0.05f, 0.01f); ChimneyGeometry chimneyGeometry = new ChimneyGeometry(); chimneyGeometry.Add(source, Matrix4x4.CreateFromQuaternion(quaternion)); Model.Add(chimneyGeometry, Matrix4x4.Identity); Collisions.Add(new Collision { Mesh = chimneyGeometry }); angle = (float)j * MathF.PI / 9f; angle2 = (float)(j + 1) * MathF.PI / 9f; ChimneyGeometry source2 = ChimneyGeometry.Prism(new Vector3[4] { MortarPoint(0.43f, angle), MortarPoint(0.43f, angle2), MortarPoint(0.295f, angle2), MortarPoint(0.295f, angle) }, new Vector3(0f, 0f, 0.18f), 0.35f); Model.Add(source2, Matrix4x4.CreateFromQuaternion(quaternion)); } } static Vector3 MortarPoint(float radius, float num) { return new Vector3(radius * (float)Math.Cos(num), 0.5f + radius * (float)Math.Sin(num), -0.49f); } static Vector3 Point(float radius, float num) { return new Vector3(radius * (float)Math.Cos(num), 0.5f + radius * (float)Math.Sin(num), -0.51f); } } private static ChimneyGeometry RoofMasonry(float bottom) { ChimneyGeometry chimneyGeometry = ChimneyGeometry.Frustum(0.59f, 0.055f, bottom, bottom + 0.32f); Vector3 vector = Vector3.Normalize(new Vector3(0f, 0.535f, -0.32f)); for (int i = 0; i < 4; i++) { Matrix4x4 transform = Matrix4x4.CreateRotationY((float)i * MathF.PI / 2f); for (int j = 0; j < 2; j++) { for (int k = 0; k < 2; k++) { float num = (float)j / 2f + 0.014f; float num2 = (float)(j + 1) / 2f - 0.014f; float num3 = 0.59f - 0.535f * num; float num4 = 0.59f - 0.535f * num2; float num5 = (float)(-1 + k) + 0.014f; float num6 = (float)k - 0.014f; Vector3[] surface = new Vector3[4] { new Vector3(num5 * num3, bottom + 0.32f * num, 0f - num3) + vector * 0.006f, new Vector3(num6 * num3, bottom + 0.32f * num, 0f - num3) + vector * 0.006f, new Vector3(num6 * num4, bottom + 0.32f * num2, 0f - num4) + vector * 0.006f, new Vector3(num5 * num4, bottom + 0.32f * num2, 0f - num4) + vector * 0.006f }; chimneyGeometry.Add(ChimneyGeometry.BevelledPrism(surface, -vector * 0.04f, 0.84f + (float)((j + k) % 3) * 0.06f, 0.006f), transform); } } } return chimneyGeometry; } } internal static class ModuleVentRoutes { internal static Vector3[] Exits(ModuleKind kind) { if (kind != ModuleKind.Grate) { return new Vector3[4] { new Vector3(-0.92f, 0.42f, 0f), new Vector3(0.92f, 0.42f, 0f), new Vector3(0f, 0.42f, -0.92f), new Vector3(0f, 0.42f, 0.92f) }; } return new Vector3[4] { new Vector3(-0.1975f, 1.12f, -0.1975f), new Vector3(0.1975f, 1.12f, -0.1975f), new Vector3(-0.1975f, 1.12f, 0.1975f), new Vector3(0.1975f, 1.12f, 0.1975f) }; } internal static bool Choose(ModuleKind kind, Vector3 position, float radius, Func<Vector3, Vector3, float, bool> clear, out Vector3 target) { target = position; if (kind != ModuleKind.Grate && kind != ModuleKind.Roof && kind != ModuleKind.Arch) { return false; } if (Math.Abs(position.X) > 1f || Math.Abs(position.Z) > 1f || position.Y < -0.2f || position.Y > 1.2f) { return false; } if (kind != ModuleKind.Grate && Math.Max(Math.Abs(position.X), Math.Abs(position.Z)) >= 0.72f + radius && position.Y >= 0.3f) { return false; } if (kind == ModuleKind.Grate && position.Y >= 0.86f + radius) { return false; } if (position.Y < 0.3f) { target = new Vector3(0f, 0.38f, 0f); return clear(position, target, radius); } float num = float.MaxValue; Vector3[] array; if (kind == ModuleKind.Grate) { array = Exits(kind); for (int i = 0; i < array.Length; i++) { Vector3 vector = array[i]; if (Math.Abs(position.X - vector.X) + Math.Abs(position.Z - vector.Z) < 0.035f && clear(position, vector, radius)) { target = vector; return true; } } } array = Exits(kind); for (int i = 0; i < array.Length; i++) { Vector3 vector2 = array[i]; Vector3 vector3 = ((kind == ModuleKind.Grate) ? new Vector3(vector2.X, 0.4f, vector2.Z) : vector2); Vector3 vector4 = vector3; if (clear(position, vector4, radius) && clear(vector3, vector2, radius)) { float num2 = Vector3.Distance(position, vector4); if (num2 < num) { num = num2; target = vector4; } } } return num < float.MaxValue; } } internal static class PassagePlanner { internal static bool Choose(Vector3 start, float radius, Func<Vector3, Vector3, float, bool> clearSegment, Func<Vector3, float, float> riseClearance, out Vector3 target) { target = start; float num = float.NegativeInfinity; for (int i = 0; i < 9; i++) { int num2 = ((i != 0) ? ((i - 1) % 3 - 1) : 0); int num3 = ((i != 0) ? ((i - 1) / 3 - 1) : 0); if (i > 4) { num2 = i % 3 - 1; num3 = i / 3 - 1; } Vector3 vector = start + new Vector3((float)num2 * 0.18f, 0.3f, (float)num3 * 0.18f); if (clearSegment(start, vector, radius)) { float num4 = riseClearance(vector, radius) - 0.6f * (float)(num2 * num2 + num3 * num3) * 0.18f; if (num4 > num) { num = num4; target = vector; } if (i == 0 && num4 >= 1.5f) { return true; } } } return !float.IsNegativeInfinity(num); } } [BepInPlugin("com.community.narrowchimneys", "Narrow Chimneys", "0.5.3.1")] public sealed class NarrowChimneysPlugin : BaseUnityPlugin { internal const string Id = "com.community.narrowchimneys"; internal static ManualLogSource Log; internal static ConfigEntry<bool> NarrowPassages; internal static ConfigEntry<bool> BuildableChimney; internal static ConfigEntry<bool> Verbose; internal static ConfigEntry<float> SmokeScale; internal static ConfigEntry<float> ExitRadius; internal static ConfigEntry<bool> Guidance; internal static ConfigEntry<bool> Diagnostics; internal static ConfigEntry<bool> ResizeOutlet; internal static ConfigEntry<bool> CenterEmission; internal static ConfigEntry<bool> ResizeCloud; internal static ConfigEntry<float> VisualSize; internal static ConfigEntry<float> UpwardStrength; internal static ConfigEntry<float> ProbeInterval; internal static ConfigEntry<float> PhysicalRadius; internal static ConfigEntry<int> QueryBudget; internal static ConfigEntry<float> VisualVolume; internal static ConfigEntry<float> VisualDensity; internal static ConfigEntry<float> VisualOpacity; internal static ConfigEntry<float> EmissionFrequency; internal static ConfigEntry<float> MaximumSpawns; internal static ConfigEntry<int> MaximumSmoke; internal static ConfigEntry<bool> ClipVisuals; internal static ConfigEntry<bool> SupportWarnings; internal static ConfigEntry<bool> ExpandVolume; internal static ConfigEntry<bool> EnhanceDensity; internal static ConfigEntry<bool> AdjustFrequency; internal static ConfigEntry<bool> LimitEmission; internal static ConfigEntry<bool> RouteCheck; internal static ConfigEntry<bool> UseProfiles; internal static ConfigEntry<bool> ProfileFrequency; internal static ConfigEntry<bool> OverrideSpawnRate; internal static ConfigEntry<float> SpawnRateMultiplier; internal static ConfigEntry<float> RouteInterval; internal static ConfigEntry<float> RouteHeight; internal static ConfigEntry<float> RouteGrace; internal static ConfigEntry<float> ProfileCloudScale; internal static ConfigEntry<float> ProfileDensityScale; internal static ConfigEntry<int> RouteBudget; internal static ConfigEntry<SmokeProfile> Profile; internal static ConfigEntry<StoneMaterialMode> MaterialMode; internal static ConfigEntry<bool> SurfaceColors; internal static ChimneyRegistry Registry; private Harmony harmony; private static readonly HashSet<string> reported = new HashSet<string>(); private void Awake() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Expected O, but got Unknown //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Expected O, but got Unknown //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Expected O, but got Unknown //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Expected O, but got Unknown //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Expected O, but got Unknown //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Expected O, but got Unknown //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Expected O, but got Unknown //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Expected O, but got Unknown //IL_03d2: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Expected O, but got Unknown //IL_040f: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Expected O, but got Unknown //IL_048c: Unknown result type (might be due to invalid IL or missing references) //IL_0496: Expected O, but got Unknown //IL_04c9: Unknown result type (might be due to invalid IL or missing references) //IL_04d3: Expected O, but got Unknown //IL_0546: Unknown result type (might be due to invalid IL or missing references) //IL_0550: Expected O, but got Unknown //IL_0583: Unknown result type (might be due to invalid IL or missing references) //IL_058d: Expected O, but got Unknown //IL_05e0: Unknown result type (might be due to invalid IL or missing references) //IL_05ea: Expected O, but got Unknown //IL_0653: Unknown result type (might be due to invalid IL or missing references) //IL_065d: Expected O, but got Unknown //IL_0690: Unknown result type (might be due to invalid IL or missing references) //IL_069a: Expected O, but got Unknown //IL_077a: Unknown result type (might be due to invalid IL or missing references) //IL_0784: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; NarrowPassages = ((BaseUnityPlugin)this).Config.Bind<bool>("Smoke", "EnableNarrowPassages", true, "Уменьшать физические коллайдеры новых частиц дыма, включая проходы между обычными блоками. Независимо от визуальных настроек."); SmokeScale = ((BaseUnityPlugin)this).Config.Bind<float>("Smoke", "ColliderRadiusMultiplier", 0.25f, new ConfigDescription("Множитель исходных размеров коллайдера. После изменения перезапустите игру.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f), Array.Empty<object>())); PhysicalRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Smoke", "PhysicalRadius", 0.1f, new ConfigDescription("Максимальный локальный радиус сферы/капсулы в метрах. Рассчитывается от исходного размера; направление не уменьшает его ради прохождения препятствий.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.03f, 0.5f), Array.Empty<object>())); ExitRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Smoke", "OutletTestRadius", 0.1f, new ConfigDescription("Максимальный радиус проверки выхода в метрах. Не увеличивает меньший исходный радиус. Применяется при ResizeOutlet=true.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.5f), Array.Empty<object>())); ResizeOutlet = ((BaseUnityPlugin)this).Config.Bind<bool>("Ventilation", "ResizeOutlet", true, "Изменять радиус проверки выхода независимо от движения частиц. Проверяется место выпуска, а не весь путь до неба."); RouteCheck = ((BaseUnityPlugin)this).Config.Bind<bool>("Ventilation", "CheckEnclosedRoute", true, "Дополнительно искать вентиляционный маршрут. Только подтверждённый закрытый объём добавляет блокировку; неизвестный результат оставляет штатные проверки. False — поведение 0.4.0/0.5.0."); RouteInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Ventilation", "RouteInterval", 2f, new ConfigDescription("Пауза между проверками источника в секундах; открытие выхода обнаруживается также у неактивного источника.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>())); RouteHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Ventilation", "RouteHeight", 8f, new ConfigDescription("Дальность поиска свободного подъёма в метрах. Выход за пределы поиска не считается стеной.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 16f), Array.Empty<object>())); RouteGrace = ((BaseUnityPlugin)this).Config.Bind<float>("Ventilation", "ClosedGrace", 3f, new ConfigDescription("Задержка подтверждённой блокировки в секундах. Штатные проверки Fireplace/SmokeSpawner сохраняются.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>())); RouteBudget = ((BaseUnityPlugin)this).Config.Bind<int>("Ventilation", "QueriesPerFrame", 128, new ConfigDescription("Общий бюджет физических запросов проверки вентиляции за кадр, отдельно от движения частиц.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 512), Array.Empty<object>())); CenterEmission = ((BaseUnityPlugin)this).Config.Bind<bool>("Emission", "CenterEmission", true, "Убрать случайное смещение только при создании Smoke. Исходный разброс затем восстанавливается; проверка вентиляции не меняется."); Guidance = ((BaseUnityPlugin)this).Config.Bind<bool>("Motion", "EnableGuidance", true, "Направлять Rigidbody к доступному пути вверх; в новых крытых оголовках — к боковым проёмам. Коллизии сохраняются."); UpwardStrength = ((BaseUnityPlugin)this).Config.Bind<float>("Motion", "UpwardStrength", 3f, new ConfigDescription("Сила реакции направления в секунду. Ноль отключает дополнительную силу; телепортации нет.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 8f), Array.Empty<object>())); ProbeInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Motion", "ProbeInterval", 0.35f, new ConfigDescription("Интервал локального поиска для одной частицы в секундах. Большие значения снижают нагрузку.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.15f, 2f), Array.Empty<object>())); QueryBudget = ((BaseUnityPlugin)this).Config.Bind<int>("Motion", "QueryBudgetPerFrame", 192, new ConfigDescription("Общий предел физических запросов направления за кадр для всех частиц.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 1024), Array.Empty<object>())); VisualSize = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "CloudSize", 0.45f, new ConfigDescription("Базовый визуальный диаметр облака в метрах. Не изменяет коллайдер. Для сравнения с 0.4.0 используйте 0.45.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 4f), Array.Empty<object>())); ResizeCloud = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual", "ResizeCloud", true, "Применять CloudSize независимо от физики и выпуска. False возвращает исходный размер рендера игры (обычно 4 м), а не размер 0.4.0."); UseProfiles = ((BaseUnityPlugin)this).Config.Bind<bool>("Presentation", "UseProfiles", true, "Применять профиль только к визуальному дыму и частоте. False использует отдельные параметры 0.5.0; физика и вентиляция профилем не меняются."); Profile = ((BaseUnityPlugin)this).Config.Bind<SmokeProfile>("Presentation", "Profile", SmokeProfile.Dense, "Original — штатный размер/частота; Dense — диаметр до 3 м вне стен, плотность 1.2, частота 4×; Performance — меньшая визуальная нагрузка. Физика не меняется; реальный поток ограничен бюджетом и локальными препятствиями."); ProfileCloudScale = ((BaseUnityPlugin)this).Config.Bind<float>("Presentation", "CloudScale", 1f, new ConfigDescription("Независимый множитель размера выбранного профиля.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 2f), Array.Empty<object>())); ProfileDensityScale = ((BaseUnityPlugin)this).Config.Bind<float>("Presentation", "DensityScale", 1f, new ConfigDescription("Независимый множитель плотности выбранного профиля; Opacity применяется отдельно.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>())); ProfileFrequency = ((BaseUnityPlugin)this).Config.Bind<bool>("Emission", "UseProfileFrequency", true, "Применять частоту профиля при UseProfiles=true. False оставляет штатную частоту, если отдельно не включён AdjustFrequency."); OverrideSpawnRate = ((BaseUnityPlugin)this).Config.Bind<bool>("Emission", "OverrideSpawnRate", false, "Использовать точный SmokeSpawnRateMultiplier вместо частоты профиля и старого FrequencyMultiplier. Позволяет независимо сравнить 1×–5×. False возвращает профиль/старые настройки; собственный генератор дыма не запускается."); SpawnRateMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Emission", "SmokeSpawnRateMultiplier", 4f, new ConfigDescription("Точный множитель исходной частоты при OverrideSpawnRate=true: 1 — исходная, 2 — двойная, 3 — тройная, 4 — четырёхкратная, 5 — экспериментальная. Для исходного интервала 0.5 с: 0.5/0.25/0.167/0.125/0.1 с. Это попытки, а не гарантия выпуска: сохраняются блокировка и лимит физических объектов. Не умножается повторно на Dense.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 5f), Array.Empty<object>())); VisualVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "VolumeMultiplier", 2f, new ConfigDescription("Множитель визуального диаметра. Действует при ExpandVolume=true; коллайдеры не меняются.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3f), Array.Empty<object>())); ExpandVolume = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual", "ExpandVolume", true, "Включить дополнительный объём версии 0.5.0. False оставляет только CloudSize, как в 0.4.0."); EnhanceDensity = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual", "AdjustDensityAndOpacity", true, "Применять Density и Opacity. False возвращает исходную прозрачность рендера, как в 0.4.0. Нейтральные значения отдельных множителей: 1."); VisualDensity = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "Density", 1.6f, new ConfigDescription("Оптическая плотность исходной текстуры дыма. Не создаёт объекты и не меняет коллайдеры; 1 — нейтральное значение.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2.5f), Array.Empty<object>())); VisualOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "Opacity", 1f, new ConfigDescription("Множитель непрозрачности. 1 не ослабляет дым; меньшее значение делает его прозрачнее. Независимо от физического размера и частоты.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1f), Array.Empty<object>())); ClipVisuals = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual", "LimitCloudNearSolids", true, "Ограничивать размер облака свободным объёмом у стен. До 64 дополнительных запросов в кадре. False отключает только это визуальное ограничение."); EmissionFrequency = ((BaseUnityPlugin)this).Config.Bind<float>("Emission", "FrequencyMultiplier", 1f, new ConfigDescription("Старый множитель частоты при AdjustFrequency=true и OverrideSpawnRate=false. Совмещается с профилем, суммарно не более 5×, ускоренный интервал не ниже 0.1 с. Для точного сравнения 1×–5× используйте SmokeSpawnRateMultiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>())); AdjustFrequency = ((BaseUnityPlugin)this).Config.Bind<bool>("Emission", "AdjustFrequency", false, "Менять частоту выпуска. False оставляет точный исходный интервал источника, как в 0.4.0."); LimitEmission = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "LimitEmission", true, "Включить новые ограничения количества объектов и скорости создания. False возвращает исходный выпуск 0.4.0. Ограничение нагрузки не объявляет свободную вентиляцию заблокированной."); MaximumSmoke = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "MaximumActiveSmoke", 96, new ConfigDescription("Предел активных Smoke, включая исчезающие объекты. Буфер игры рассчитан на 100 частиц; предел применяется только при LimitEmission=true.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 96), Array.Empty<object>())); MaximumSpawns = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "MaximumSpawnsPerSecond", 24f, new ConfigDescription("Общий бюджет создания физических объектов в секунду; запас до 10 для одновременных выпусков. При перегрузке источники получают очередь разрешений; отложенные частицы не создаются. Одиночный штатный источник с интервалом 0.5 с не ограничивается значением 12.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); SupportWarnings = ((BaseUnityPlugin)this).Config.Bind<bool>("Building", "SupportWarnings", true, "Показывать предварительное предупреждение о слабой опоре. Не запрещает размещение и не заменяет WearNTear; кронштейн необязателен."); MaterialMode = ((BaseUnityPlugin)this).Config.Bind<StoneMaterialMode>("Building", "StoneMaterial", StoneMaterialMode.DetailedPBR, "Сохранённый ключ совместимости 0.5.2. В 0.5.3 оба значения используют проверенный Custom/Piece и безопасный согласованный атлас; исходный атлас stone не назначается процедурным UV напрямую. Требуется перезапуск."); SurfaceColors = ((BaseUnityPlugin)this).Config.Bind<bool>("Building", "SurfaceDiagnosticColors", false, "Визуальная диагностика: камень оранжевый, раствор бирюзовый, внутренняя облицовка синяя, металл фиолетовый; зелёные контуры показывают открытые проёмы. Выключено по умолчанию, не меняет коллайдеры, опоры или дым. Переключение отображается примерно за 0.5 с."); Diagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "Diagnostics", false, "Маркеры физического центра дыма (до 12 частиц), препятствий и диагностика поддержки, здоровья и разрушения."); BuildableChimney = ((BaseUnityPlugin)this).Config.Bind<bool>("Building", "EnableBuildableChimney", true, "Регистрировать модули, необязательный кронштейн и старый NC_Chimney. Не отключайте при загрузке мира с такими деталями."); Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "Verbose", false, "Подробный журнал коллайдеров и источников. Может создавать много записей."); ((BaseUnityPlugin)this).Config.Save(); Registry = new ChimneyRegistry(); harmony = new Harmony("com.community.narrowchimneys"); try { harmony.PatchAll(typeof(NarrowChimneysPlugin).Assembly); Log.LogInfo((object)$"Loaded 0.5.3.1 experimental; profile={Profile.Value}, profiles={UseProfiles.Value},