Compare commits
2 commits
bb0b4fe4f2
...
ad541dfbd8
| Author | SHA1 | Date | |
|---|---|---|---|
| ad541dfbd8 | |||
| e070bbc9da |
2 changed files with 75 additions and 23 deletions
|
|
@ -7,8 +7,8 @@ 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 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
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ public partial class MapGenerator : TextureRect
|
|||
public bool IsHighwayNode;
|
||||
}
|
||||
|
||||
// When generation began, in engine milliseconds. Every progress print is stamped with
|
||||
// the time since this, so the cost of each stage is readable straight off the console.
|
||||
private ulong _pipelineStartMs;
|
||||
|
||||
private FastNoiseLite _noise; // The one and only original detailed noise!
|
||||
private Vector2 _impactCenter;
|
||||
private float _impactRadius;
|
||||
|
|
@ -35,6 +39,8 @@ public partial class MapGenerator : TextureRect
|
|||
|
||||
public override async void _Ready()
|
||||
{
|
||||
_pipelineStartMs = Time.GetTicksMsec();
|
||||
|
||||
// 1. LOAD CONFIGURATION
|
||||
ConfigManager.LoadConfig();
|
||||
MapSize = ConfigManager.MapSize;
|
||||
|
|
@ -66,27 +72,71 @@ public partial class MapGenerator : TextureRect
|
|||
float randomY = (float)GD.RandRange(0.05f, 0.12f);
|
||||
_impactCenter = new Vector2(MapSize * randomX, MapSize * randomY);
|
||||
|
||||
// --- THE GENERATION PIPELINE ---
|
||||
// Each stage prints its elapsed time and drops a snapshot, so you can watch the
|
||||
// world appear in layers instead of waiting for the whole run to judge any of it.
|
||||
// A future water stage slots in as another CaptureStage call at its own boundary.
|
||||
|
||||
GenerateTopography();
|
||||
GD.Print($"{T()} Topography done (height + temperature).");
|
||||
|
||||
CalculateTrueOcean();
|
||||
CalculateMainland();
|
||||
GD.Print($"{T()} Ocean and mainland masks done.");
|
||||
|
||||
AssignBiomesAndDraw();
|
||||
GD.Print($"{T()} Biomes done.");
|
||||
await CaptureStage("1_biomes");
|
||||
|
||||
GenerateTowns();
|
||||
GD.Print($"{T()} Towns placed: {_towns.Count}.");
|
||||
await CaptureStage("2_towns");
|
||||
|
||||
// NEW: We await the roads so the engine doesn't freeze!
|
||||
await GenerateRoadsAsync();
|
||||
|
||||
ExportMapData();
|
||||
SaveMapSnapshot();
|
||||
GD.Print($"{T()} Blueprint exported.");
|
||||
|
||||
await CaptureStage("3_roads");
|
||||
GD.Print($"{T()} GENERATION COMPLETE.");
|
||||
}
|
||||
|
||||
private async void SaveMapSnapshot()
|
||||
/// <summary>
|
||||
/// Elapsed time since generation started, e.g. "[+12.4s]". Prefixed onto the progress
|
||||
/// prints so the cost of each stage can be read straight off the console — no stopwatch.
|
||||
/// </summary>
|
||||
private string T()
|
||||
{
|
||||
// 1. THE FIX: Wait exactly one frame to let Godot finish _Ready() and unlock the Scene Tree!
|
||||
double seconds = (Time.GetTicksMsec() - _pipelineStartMs) / 1000.0;
|
||||
return $"[+{seconds,6:F1}s]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a PNG of the map AS IT STANDS RIGHT NOW, tagged with a stage label.
|
||||
///
|
||||
/// Call this at any pipeline boundary. It draws whatever exists at that moment — after
|
||||
/// biomes you get bare terrain, after towns the town markers appear, after roads the
|
||||
/// road network does. Nothing needs to be passed in or turned on; the draw proxy just
|
||||
/// renders the lists as they currently are, so a future stage (water, for instance)
|
||||
/// shows up automatically once it populates its data.
|
||||
///
|
||||
/// ⚠ DO NOT "SIMPLIFY" THE WAITS BELOW. They look like superstition and are not — each
|
||||
/// one is there because removing it produced blank or half-drawn images. The design
|
||||
/// vault records why (01_Design/tooling_notes.md). Symptoms of getting this wrong are
|
||||
/// postage-stamp, black, or road-less captures.
|
||||
/// </summary>
|
||||
private async Task CaptureStage(string label)
|
||||
{
|
||||
// 1. Yield one frame. Godot locks the scene tree while it is processing, and we are
|
||||
// about to add nodes to it. Without this, the AddChild below is unsafe.
|
||||
await ToSignal(GetTree(), "process_frame");
|
||||
|
||||
GD.Print("Starting offscreen map capture...");
|
||||
GD.Print($"{T()} Capturing snapshot: {label}...");
|
||||
|
||||
// 2. Build the invisible, offscreen 4096 monitor
|
||||
// 2. Build an invisible, offscreen "monitor" at the map's real size. It must be a
|
||||
// SubViewport we size ourselves — rendering through the scene's UI layout makes
|
||||
// Godot shrink the image to fit the physical screen.
|
||||
var offscreenVP = new SubViewport();
|
||||
offscreenVP.Size = new Vector2I(MapSize, MapSize);
|
||||
offscreenVP.RenderTargetUpdateMode = SubViewport.UpdateMode.Always;
|
||||
|
|
@ -99,27 +149,29 @@ public partial class MapGenerator : TextureRect
|
|||
bgRect.CustomMinimumSize = new Vector2(MapSize, MapSize);
|
||||
offscreenVP.AddChild(bgRect);
|
||||
|
||||
// 4. Attach the proxy to draw the roads and towns on top
|
||||
// 4. Attach the proxy to draw the roads and towns on top. Reading the viewport's
|
||||
// texture alone would miss them — _Draw() output is not part of the base texture.
|
||||
var drawProxy = new MapDrawProxy();
|
||||
drawProxy.Source = this;
|
||||
drawProxy.CustomMinimumSize = new Vector2(MapSize, MapSize);
|
||||
offscreenVP.AddChild(drawProxy);
|
||||
|
||||
// 5. WAIT FOR THE GPU! (Wait 2 render frames to guarantee the framebuffer is painted)
|
||||
// 5. WAIT FOR THE GPU! Issuing draw calls is not the same as having drawn.
|
||||
// Two frames — one proved insufficient in practice.
|
||||
await ToSignal(RenderingServer.Singleton, RenderingServer.SignalName.FramePostDraw);
|
||||
await ToSignal(RenderingServer.Singleton, RenderingServer.SignalName.FramePostDraw);
|
||||
|
||||
// 6. Pull the image from the invisible monitor and save it!
|
||||
Image capture = offscreenVP.GetTexture().GetImage();
|
||||
string seedStr = _noise.Seed.ToString();
|
||||
string fileName = $"user://Map_Seed_{seedStr}.png";
|
||||
string fileName = $"user://Map_Seed_{seedStr}_{label}.png";
|
||||
|
||||
Error saveResult = capture.SavePng(fileName);
|
||||
|
||||
if (saveResult == Error.Ok)
|
||||
GD.Print($"Map saved successfully to: {ProjectSettings.GlobalizePath(fileName)}");
|
||||
GD.Print($"{T()} Map saved: {ProjectSettings.GlobalizePath(fileName)}");
|
||||
else
|
||||
GD.PrintErr($"Failed to save map. Godot Error code: {saveResult}");
|
||||
GD.PrintErr($"{T()} Failed to save map. Godot Error code: {saveResult}");
|
||||
|
||||
// 7. Delete the invisible monitor to free up RAM
|
||||
offscreenVP.QueueFree();
|
||||
|
|
@ -622,7 +674,7 @@ public partial class MapGenerator : TextureRect
|
|||
// --- LOGISTICS & ROAD GENERATION ---
|
||||
private async Task GenerateRoadsAsync()
|
||||
{
|
||||
GD.Print($"[A*] Starting Logistics Pathfinding for {MapSize}x{MapSize} world...");
|
||||
GD.Print($"{T()} [A*] Starting Logistics Pathfinding for {MapSize}x{MapSize} world...");
|
||||
_highwayPaths.Clear(); _branchPaths.Clear(); _ruggedPaths.Clear(); _trailPaths.Clear();
|
||||
|
||||
AStarGrid2D astar = new AStarGrid2D();
|
||||
|
|
@ -645,7 +697,7 @@ public partial class MapGenerator : TextureRect
|
|||
return angleA.CompareTo(angleB);
|
||||
});
|
||||
|
||||
GD.Print($"[A*] 1/4: Plotting the {highwayNodes.Count}-Node Continental Loop...");
|
||||
GD.Print($"{T()} [A*] 1/4: Plotting the {highwayNodes.Count}-Node Continental Loop...");
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); // BREATHE
|
||||
|
||||
if (highwayNodes.Count >= 3) {
|
||||
|
|
@ -668,7 +720,7 @@ public partial class MapGenerator : TextureRect
|
|||
}
|
||||
|
||||
// --- 2. THE MOUNTAIN BOSS BRANCH ---
|
||||
GD.Print("[A*] 2/4: Connecting the Snow Boss...");
|
||||
GD.Print($"{T()} [A*] 2/4: Connecting the Snow Boss...");
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); // BREATHE
|
||||
|
||||
List<TownData> connectedTowns = new List<TownData>(highwayNodes);
|
||||
|
|
@ -700,7 +752,7 @@ public partial class MapGenerator : TextureRect
|
|||
}
|
||||
|
||||
// --- 3 & 4. COUNTY ROADS (The Daisy-Chain Protocol) ---
|
||||
GD.Print("[A*] 3/4 & 4/4: Weaving County Roads (Daisy-Chaining)...");
|
||||
GD.Print($"{T()} [A*] 3/4 & 4/4: Weaving County Roads (Daisy-Chaining)...");
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); // BREATHE
|
||||
|
||||
List<TownData> unconnectedTowns = _towns.FindAll(t => !connectedTowns.Contains(t) && t.Tier != TownTier.IslandLoot);
|
||||
|
|
@ -731,7 +783,7 @@ public partial class MapGenerator : TextureRect
|
|||
float actualDist = Mathf.Sqrt(shortestDist);
|
||||
|
||||
// NEW: Live Diagnostics! Print the exact town we are testing BEFORE we calculate.
|
||||
GD.Print($" -> ({count}/{totalToConnect}) Pathing {bestUnconnected.Tier} to grid. Dist: {actualDist:F1}px");
|
||||
GD.Print($"{T()} -> ({count}/{totalToConnect}) Pathing {bestUnconnected.Tier} to grid. Dist: {actualDist:F1}px");
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); // BREATHE!
|
||||
|
||||
Vector2[] countyPath = new Vector2[0];
|
||||
|
|
@ -739,7 +791,7 @@ public partial class MapGenerator : TextureRect
|
|||
// THE ABANDON PROTOCOL: If it's more than 8% of the map away, it's a hermit camp. Leave it!
|
||||
if (actualDist > (MapSize * 0.08f))
|
||||
{
|
||||
GD.Print($" [!] Too isolated. Abandoning road.");
|
||||
GD.Print($"{T()} [!] Too isolated. Abandoning road.");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -759,14 +811,14 @@ public partial class MapGenerator : TextureRect
|
|||
}
|
||||
else if (actualDist <= (MapSize * 0.08f))
|
||||
{
|
||||
GD.Print($" [!] Path impossible (Trapped by terrain). Abandoning road.");
|
||||
GD.Print($"{T()} [!] Path impossible (Trapped by terrain). Abandoning road.");
|
||||
}
|
||||
|
||||
unconnectedTowns.Remove(bestUnconnected);
|
||||
connectedTowns.Add(bestUnconnected);
|
||||
}
|
||||
|
||||
GD.Print("[A*] Logistics Network Complete!");
|
||||
GD.Print($"{T()} [A*] Logistics Network Complete!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Reference in a new issue