using Godot; using System.IO; using System.Collections.Generic; namespace IslaApocalypse.Core { // 1. The Data Container // This holds the unpacked data in RAM so the Server can ask it questions // without having to re-read the hard drive every single frame. public struct TownLocation { public Vector2 Position; public TownTier Tier; // Whether the generator marked this settlement as a highway node (it drives the // road topology at generation time). Carried by the v2 blueprint; always false // when loading a legacy v1 file, which never stored it. Nothing server-side // consumes it yet — it is carried and exposed, not acted on. public bool IsHighwayNode; } /// /// The resolved generation inputs embedded in a v2 blueprint, so a file is /// self-describing and config desync is detectable. Null on a legacy v1 load. /// A v2 file re-encoded from a v1 source carries the BlueprintFormat sentinel /// values instead of real inputs. /// public class BlueprintParams { public int WorldSeed; // the generator's RESOLVED noise seed (names the file) public int MapSize; public float CraterRadius; public float DensityMultiplier; public Vector2 ImpactCenter; // the crater's _impactCenter, in map pixels public string GeneratedUtc = ""; // stamped at write time, ISO-8601 public string GeneratorGitHash = ""; // short hash of the generator repo, "" if unknown } public class WorldBlueprint { public int MapSize; public float[,] HeightMap; public Biome[,] BiomeMap; public List Towns = new List(); // All 4 road tiers! public List Highways = new List(); public List BranchRoads = new List(); public List RuggedRoads = new List(); public List TrailRoads = new List(); // Which on-disk format this blueprint was parsed from (1 or 2), and the embedded // generation parameters. Params is null when the source was a legacy v1 file. public int FormatVersion = 1; public BlueprintParams Params; } // 2. The Parser Utility public static class MapDataParser { public static WorldBlueprint LoadMapData(string seedStr) { string filePath = ProjectSettings.GlobalizePath($"user://MapData_Seed_{seedStr}.dat"); return LoadMapDataFromPath(filePath); } /// /// Loads a blueprint from an explicit absolute path. Same result as LoadMapData; /// exists so tooling (the round-trip harness) can load files that do not follow /// the seed-derived naming. /// public static WorldBlueprint LoadMapDataFromPath(string filePath) { if (!File.Exists(filePath)) { GD.PrintErr($"[MapDataParser] CRITICAL ERROR: Map file not found at {filePath}"); return null; } using (FileStream stream = File.OpenRead(filePath)) { using (BinaryReader reader = new BinaryReader(stream)) { return LoadV1(reader); } } } // =============================================================== // LEGACY v1 PARSE PATH — the original "ISLA_V1" positional format. // Kept intact and untouched; do not extend it. New content belongs // in the v2 tagged-section format. // =============================================================== private static WorldBlueprint LoadV1(BinaryReader reader) { WorldBlueprint blueprint = new WorldBlueprint(); { { // 1. Header Validation (CRITICAL: Must read string first to align bytes!) string header = reader.ReadString(); if (header != "ISLA_V1") { GD.PrintErr("[MapDataParser] ERROR: Invalid map version or corrupted file."); return null; } // 2. Read Map Dimensions & Initialize Arrays blueprint.MapSize = reader.ReadInt32(); blueprint.HeightMap = new float[blueprint.MapSize, blueprint.MapSize]; blueprint.BiomeMap = new Biome[blueprint.MapSize, blueprint.MapSize]; // 3. Read Raw Voxel Data (Height & Biome) for (int x = 0; x < blueprint.MapSize; x++) { for (int y = 0; y < blueprint.MapSize; y++) { blueprint.HeightMap[x, y] = reader.ReadSingle(); blueprint.BiomeMap[x, y] = (Biome)reader.ReadInt32(); } } // 4. Read Logistical Anchors (Towns & POIs) int townCount = reader.ReadInt32(); for (int i = 0; i < townCount; i++) { TownLocation town = new TownLocation(); town.Position = new Vector2(reader.ReadSingle(), reader.ReadSingle()); town.Tier = (TownTier)reader.ReadInt32(); blueprint.Towns.Add(town); } // 5. Read Road Networks // Highway int highwayCount = reader.ReadInt32(); for (int i = 0; i < highwayCount; i++) { int pathLength = reader.ReadInt32(); Vector2[] path = new Vector2[pathLength]; for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); } blueprint.Highways.Add(path); } // Branch int branchCount = reader.ReadInt32(); for (int i = 0; i < branchCount; i++) { int pathLength = reader.ReadInt32(); Vector2[] path = new Vector2[pathLength]; for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); } blueprint.BranchRoads.Add(path); } // Rugged int ruggedCount = reader.ReadInt32(); for (int i = 0; i < ruggedCount; i++) { int pathLength = reader.ReadInt32(); Vector2[] path = new Vector2[pathLength]; for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); } blueprint.RuggedRoads.Add(path); } // Trails int trailCount = reader.ReadInt32(); for (int i = 0; i < trailCount; i++) { int pathLength = reader.ReadInt32(); Vector2[] path = new Vector2[pathLength]; for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); } blueprint.TrailRoads.Add(path); } } } GD.Print($"[MapDataParser] Successfully loaded v1 Blueprint. Dimension: {blueprint.MapSize}x{blueprint.MapSize}."); return blueprint; } } }