using System; using System.Collections.Generic; namespace IslaApocalypse.Core { /// /// The two material schemas, as APPEND-ONLY registries. → `Design - Data - Material Schema.md`, D-054. /// /// ⭐ NO MATERIAL LOGIC IS HARDCODED ANYWHERE — everything reads from here. /// This is the specific discipline that makes deferring the full material list SAFE, unlike /// biomes, which were hardcoded and coupled. Understand the shape now; fill the rows forever. /// /// ⚠ THE SEED ROWS BELOW ARE NOT THE MATERIAL LIST. They are the few that are certain /// (limestone is in — karst substrate). Do NOT enumerate the full list now; design materials /// incrementally as building proceeds. Adding one is one new row, appended, nothing else. /// /// ⚠ REGISTRATION ORDER IS NOT MEANINGFUL. It sets RuntimeIndex, which is never /// serialized. Reordering these lines must not change a single persisted byte, now or ever. /// /// Phase 0: rows are seeded in code. Loading them from a data file is a later phase — the shape /// here (identity + properties, no logic) is what makes that a loader change and nothing more. /// public static class MaterialRegistry { // ---- terrain -------------------------------------------------------- private static readonly Dictionary _terrain = new(); private static readonly List _terrainByIndex = new(); // ---- building ------------------------------------------------------- private static readonly Dictionary _building = new(); private static readonly List _buildingByIndex = new(); /// /// AIR. The one key the column model leans on structurally: everything above the surface is /// the top AIR run, and a cave is an air run mid-column. It is a material row like any /// other — there is no "is air" branch anywhere. → `Design - Data - Column Model.md`. /// public static readonly MaterialKey Air = new("core.air"); static MaterialRegistry() { // ===== TERRAIN — seed rows, NOT final ===== // key display hardness yield per-voxel support RegisterTerrain("core.air", "Air", 0.0f, MaterialKey.None, 0, 0.0f); RegisterTerrain("core.sand", "Sand", 0.2f, "core.sand", 1, 0.1f); RegisterTerrain("core.dirt", "Dirt", 0.3f, "core.dirt", 1, 0.2f); RegisterTerrain("core.clay", "Clay", 0.4f, "core.clay", 1, 0.3f); RegisterTerrain("core.limestone", "Limestone", 0.7f, "core.limestone", 1, 0.7f); // karst substrate RegisterTerrain("core.stone", "Stone", 0.9f, "core.stone", 1, 0.9f); RegisterTerrain("core.bedrock", "Bedrock", 1.0f, MaterialKey.None, 0, 1.0f); // unmineable floor // ===== BUILDING — seed rows, NOT final ===== // key display reach decay weight durability RegisterBuilding("build.thatch", "Thatch/Palm", 1.5f, 0.60f, 0.2f, 0.2f); RegisterBuilding("build.wood", "Wood Plank", 4.0f, 0.30f, 0.5f, 0.5f); RegisterBuilding("build.stone", "Worked Stone", 6.0f, 0.20f, 1.0f, 0.8f); RegisterBuilding("build.concrete", "Concrete", 9.0f, 0.12f, 1.1f, 0.9f); RegisterBuilding("build.steel", "Steel", 14.0f, 0.06f, 0.9f, 1.0f); } // ---- registration (append-only; no removal, by design) -------------- private static void RegisterTerrain(string key, string name, float hardness, MaterialKey yieldKey, int yieldPer, float support) => RegisterTerrain(new TerrainMaterial(new MaterialKey(key), name, hardness, yieldKey, yieldPer, support)); private static void RegisterTerrain(string key, string name, float hardness, string yieldKey, int yieldPer, float support) => RegisterTerrain(key, name, hardness, new MaterialKey(yieldKey), yieldPer, support); private static void RegisterBuilding(string key, string name, float reach, float decay, float weight, float durability) => RegisterBuilding(new BuildingMaterial(new MaterialKey(key), name, reach, decay, weight, durability)); /// Append a terrain material. Public so mods and data loaders add rows the same way seeds do. public static void RegisterTerrain(TerrainMaterial material) { if (material == null) throw new ArgumentNullException(nameof(material)); if (_terrain.ContainsKey(material.Key)) throw new InvalidOperationException($"Terrain material '{material.Key}' is already registered. The registry is append-only; keys are unique."); material.RuntimeIndex = _terrainByIndex.Count; _terrainByIndex.Add(material); _terrain.Add(material.Key, material); } /// Append a building material. public static void RegisterBuilding(BuildingMaterial material) { if (material == null) throw new ArgumentNullException(nameof(material)); if (_building.ContainsKey(material.Key)) throw new InvalidOperationException($"Building material '{material.Key}' is already registered. The registry is append-only; keys are unique."); material.RuntimeIndex = _buildingByIndex.Count; _buildingByIndex.Add(material); _building.Add(material.Key, material); } // ---- lookup --------------------------------------------------------- public static TerrainMaterial Terrain(MaterialKey key) => _terrain.TryGetValue(key, out var m) ? m : throw new KeyNotFoundException($"No terrain material registered for key '{key}'."); public static BuildingMaterial Building(MaterialKey key) => _building.TryGetValue(key, out var m) ? m : throw new KeyNotFoundException($"No building material registered for key '{key}'."); public static bool TryTerrain(MaterialKey key, out TerrainMaterial material) => _terrain.TryGetValue(key, out material); public static bool TryBuilding(MaterialKey key, out BuildingMaterial material) => _building.TryGetValue(key, out material); public static bool IsRegisteredTerrain(MaterialKey key) => _terrain.ContainsKey(key); public static IReadOnlyList AllTerrain => _terrainByIndex; public static IReadOnlyList AllBuilding => _buildingByIndex; public static int TerrainCount => _terrainByIndex.Count; public static int BuildingCount => _buildingByIndex.Count; } }