using Godot; using Godot.Collections; // Required for Godot's built-in JSON parser namespace IslaApocalypse.Core // Change this if your namespace is different { public static class ConfigManager { // Default Fallbacks // public static int WorldSeed = 1063685222; public static int WorldSeed = (int)GD.Randi(); // if 0 also randomizes public static int MapSize = 8192; public static float CraterRadius = 400f; public static float DensityMultiplier = 1.0f; // <-- Replaces TownCount public static int ChunkRadius = 24; // Default chunk size, can be overridden by config // Iteration toggle: skip the ~25-min A* road pass entirely. The blueprint is // still exported (road sections present but empty) — an iteration artifact, // not a shippable world. Default false. public static bool SkipRoads = false; // Sea-level model (D-033, terrain-water task 04): "flat" = one scalar sea // level everywhere (SeaLevelValue); "field" = the legacy latitude Lerp // (0.26 north .. 0.15 south). Generator-only — the runtime never computes // sea level. Default: flat 0.15. public static string SeaLevelModel = "flat"; public static float SeaLevelValue = 0.15f; // Height-redistribution curve (tasks 05–10, graduation M-7): "v5" is the // task-09 gate's winner — the BALANCED terraced ascent with corner easing and // modulated shelves; "off" is the raw legacy profile. "v1"–"v4" and the // taste-batch tri-state ("v5-compact" lost, "v5-balanced" became plain "v5") // are retired. Default: v5. public static string TerrainCurve = "v5"; // Terrain detail passes (task 10): "v1" = shelf micro-relief + shelf-edge // variation as one judged unit (requires the curve; no-op when it is off); // "off" disables both. ShelfReliefAmp is the micro-relief amplitude in metres // of OUTPUT height. ShelfEdgeVariation is the shelf-edge warp amplitude in // metres of INPUT height — how far the shelf/riser boundary contour is // displaced, not an elevation change; it is clamped at load time to the // largest shift the curve's bands can absorb. Defaults: v1, 3 m, 12 m. public static string TerrainDetail = "v1"; public static float ShelfReliefAmp = 3.0f; public static float ShelfEdgeVariation = 12.0f; // Island falloff shaping (task 11). // // CoastProfile: "wide" adds the submarine shelf — the height curve is identity // at and below sea, so it never reached the seabed, which still dropped ~6.7x // steeper than the land it meets. "steep" is the pre-task-11 seabed, kept for // A/B. The shelf cannot move the waterline, so biomes and water are identical // either way. Default: wide. // // IslandAxisX/Y: the falloff axis ratios (pre-task-11: 1.15 / 0.90). NOTE, // measured: the island is already Trench-clamped in x at ~90% of the map // width, so AxisX is a weak lever — aspect responds almost entirely to AxisY. // These MOVE THE COASTLINE and therefore move biomes, by design. // // OffshoreIslandDensity: fraction of the ocean noise field above the islet // threshold. 0 disables the layer. Islets never touch the Trench and are held // off the mainland by a depth moat. public static string CoastProfile = "wide"; public static float IslandAxisX = 1.30f; public static float IslandAxisY = 0.78f; public static float OffshoreIslandDensity = 0.02f; // The pre-task-11 axis ratios, so "steep"/legacy runs can restore them exactly. public const float LEGACY_AXIS_X = 1.15f; public const float LEGACY_AXIS_Y = 0.90f; public static void LoadConfig() { string path = "res://ServerConfig.json"; if (!FileAccess.FileExists(path)) { GD.PrintErr("[ConfigManager] ServerConfig.json not found! Defaulting to 8K."); return; } // Read the file using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read); string content = file.GetAsText(); // Parse the JSON var json = new Json(); var error = json.Parse(content); if (error != Error.Ok) { GD.PrintErr($"[ConfigManager] JSON Parse Error: {json.GetErrorMessage()}"); return; } var data = (Dictionary)json.Data; // Extract the Seed if (data.ContainsKey("WorldSeed")) { WorldSeed = (int)data["WorldSeed"]; } // Extract the MapProfile and run your Switch/Case logic! string profile = "8K"; if (data.ContainsKey("MapProfile")) { profile = (string)data["MapProfile"]; } // Extract the TownDensity and run your Switch/Case logic! string townDensity = "Normal"; if (data.ContainsKey("TownDensity")) { townDensity = (string)data["TownDensity"]; } // Extract the ChunkRadius if (data.ContainsKey("ChunkRadius")) { ChunkRadius = (int)data["ChunkRadius"]; } // Extract the SkipRoads iteration toggle if (data.ContainsKey("SkipRoads")) { SkipRoads = (bool)data["SkipRoads"]; } // Extract the sea-level model if (data.ContainsKey("SeaLevelModel")) { string model = (string)data["SeaLevelModel"]; if (model == "flat" || model == "field") SeaLevelModel = model; else GD.PrintErr($"[ConfigManager] Unknown SeaLevelModel '{model}'. Keeping '{SeaLevelModel}'."); } if (data.ContainsKey("SeaLevelValue")) { SeaLevelValue = (float)data["SeaLevelValue"]; } // Extract the terrain-curve gate if (data.ContainsKey("TerrainCurve")) { string curve = (string)data["TerrainCurve"]; if (curve == "off" || curve == "v5") TerrainCurve = curve; else if (curve == "v5-balanced") GD.PrintErr($"[ConfigManager] TerrainCurve 'v5-balanced' won the task-09 gate and is now plain \"v5\". Keeping '{TerrainCurve}'."); else if (curve == "v5-compact") GD.PrintErr($"[ConfigManager] TerrainCurve 'v5-compact' was retired by the task-09 verdict (BALANCED won). Keeping '{TerrainCurve}' — use \"v5\" or \"off\"."); else if (curve == "v1" || curve == "v2" || curve == "v3" || curve == "v4") GD.PrintErr($"[ConfigManager] TerrainCurve '{curve}' was retired by a later recalibration (v5, tasks 09/10). Keeping '{TerrainCurve}' — use \"v5\" or \"off\"."); else GD.PrintErr($"[ConfigManager] Unknown TerrainCurve '{curve}'. Keeping '{TerrainCurve}'."); } // Extract the terrain-detail gate + relief amplitude if (data.ContainsKey("TerrainDetail")) { string detail = (string)data["TerrainDetail"]; if (detail == "off" || detail == "v1") TerrainDetail = detail; else GD.PrintErr($"[ConfigManager] Unknown TerrainDetail '{detail}'. Keeping '{TerrainDetail}'."); } if (data.ContainsKey("ShelfReliefAmp")) { ShelfReliefAmp = (float)data["ShelfReliefAmp"]; } if (data.ContainsKey("ShelfEdgeVariation")) { ShelfEdgeVariation = (float)data["ShelfEdgeVariation"]; } // Extract the island-falloff dials (task 11) if (data.ContainsKey("CoastProfile")) { string coast = (string)data["CoastProfile"]; if (coast == "steep" || coast == "wide") CoastProfile = coast; else GD.PrintErr($"[ConfigManager] Unknown CoastProfile '{coast}'. Keeping '{CoastProfile}'."); } if (data.ContainsKey("IslandAxisX")) IslandAxisX = (float)data["IslandAxisX"]; if (data.ContainsKey("IslandAxisY")) IslandAxisY = (float)data["IslandAxisY"]; if (data.ContainsKey("OffshoreIslandDensity")) OffshoreIslandDensity = (float)data["OffshoreIslandDensity"]; // The axis ratios divide map extents; a zero or negative one is a divide-by- // zero that would silently produce an all-ocean map. Refuse it loudly. if (IslandAxisX <= 0.01f || IslandAxisY <= 0.01f) { GD.PrintErr($"[ConfigManager] IslandAxisX/Y must be > 0.01 (got {IslandAxisX}/{IslandAxisY}). Restoring legacy {LEGACY_AXIS_X}/{LEGACY_AXIS_Y}."); IslandAxisX = LEGACY_AXIS_X; IslandAxisY = LEGACY_AXIS_Y; } OffshoreIslandDensity = Mathf.Clamp(OffshoreIslandDensity, 0f, 0.5f); switch (profile) { case "4K": MapSize = 4096; CraterRadius = 400f; // Scaled down crater break; case "6K": MapSize = 6144; CraterRadius = 600f; break; case "8K": MapSize = 8192; CraterRadius = 800f; // Your original crater size spread over an 8K map break; case "10K": MapSize = 10240; CraterRadius = 1000f; break; default: GD.PrintErr($"[ConfigManager] Unknown MapProfile '{profile}'. Defaulting to 8K."); MapSize = 8192; CraterRadius = 800f; break; } switch (townDensity) { case "Sparse": DensityMultiplier = 0.5f; break; case "Normal": DensityMultiplier = 1.0f; break; case "Dense": DensityMultiplier = 2.0f; break; default: GD.PrintErr($"[ConfigManager] Unknown TownDensity '{townDensity}'. Defaulting to Normal."); DensityMultiplier = 1.0f; break; } GD.Print($"[ConfigManager] Successfully loaded {profile} Profile. MapSize set to {MapSize}."); } } }