From a5b0d76c9ca41f976cc41e385f85283d86cdde79 Mon Sep 17 00:00:00 2001 From: Jonas Reith Date: Sun, 30 Aug 2026 19:23:41 +0200 Subject: [PATCH] Add realm names to the 2D map/exports; make diplomacy arcs a separate toggle Kingdom/empire capital labels only ever drew on the 3D globe -- neither the on-screen 2D map nor exportMapImage()/exportAtlasImage() showed them. drawMapOverlays() now has a 2D realm-label block mirroring the existing 3D one; exportAtlasImage() adds nation names as label candidates in its decluttered pass (offset above the capital's own settlement-name anchor so the two don't collide), shown regardless of the current colour mode since the atlas is a reference, not "what you're looking at". Also split the Territory view's political borders from its alliance/rivalry diplomacy arcs, which previously always showed together with no way to hide just the arcs. New independent showDiplomacy toggle (key A, default on, a toolbar checkbox in Overlays) gates only the ally/rival arc draw calls. Verified end to end: exported both Map and Atlas images from a live world with several realms and confirmed realm names render correctly (cleanly decluttered in the Atlas, alongside geography and settlement names). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01TMfyZv91tonDnPJqbaTVJE --- BUILD.md | 3 ++- CLAUDE.md | 20 ++++++++++++++++- src/render/Toolbar.cpp | 7 +++--- src/render/Viewer.hpp | 2 ++ src/render/ViewerInput.cpp | 1 + src/render/ViewerRender.cpp | 43 ++++++++++++++++++++++++++++++++++--- 6 files changed, 68 insertions(+), 8 deletions(-) diff --git a/BUILD.md b/BUILD.md index c134733..fe5f51b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -57,7 +57,8 @@ the full ~2.8x speedup; the default uses all cores for no extra gain: E ecoregions colour view (names ecological provinces on first use; Eco tab) I habitability heat map (where civilization can thrive) U settlements: the dawn of civilization on first press, then toggle markers (Civ tab) - P territory / realms view + borders + war fronts + alliance/rivalry arcs (Realms tab) + P territory / realms view + borders + war fronts + realm-name labels (Realms tab) + A toggle alliance/rivalry diplomacy arcs on the Territory view (on by default) X culture / faiths view + cultural borders (peoples/ethos/religion in the Cultures tab) Z wealth / trade view + trade routes (sea/river/overland; prosperity feeds city growth) Y follow-cam: cycle the 3D camera through active storms (Live World; off after last) diff --git a/CLAUDE.md b/CLAUDE.md index 14c28ed..80fb78b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -406,6 +406,22 @@ on the Live World clock). **Steps 1–7 of the roadmap are done (plus a derived etc., now zero-arg methods on `Viewer`, e.g. `mode == ColorMode::Territory`) instead of stored, independently-mutable state — the two can no longer drift apart because there's only one source of truth. `src/render` only, no save-format or `src/sim` change; ctest unaffected (`src/sim` untouched). +- **Realm names in the 2D map/exports + a diplomacy-arc toggle independent of Territory** *(done)* — + two follow-up requests after the above fix. (1) Kingdom/empire capital labels ("Kingdom of X") were + 3D-globe-only (`renderFrame()`'s "3D realm labels" block); neither the on-screen 2D map nor + `exportMapImage()`/`exportAtlasImage()` ever drew them. `drawMapOverlays()` now has a matching 2D + block (same `showNationBorders() && drawLabels` gating, `projLonLat` instead of the 3D camera + projection; city-states skipped same as the 3D version) — fixes the on-screen map and + `exportMapImage()` in one place. `exportAtlasImage()` separately adds nation names as label + candidates in its decluttered pass (priority tier alongside cities; skipped for city-states), offset + above the capital's own settlement-name anchor so the two don't fight over one box; shown regardless + of the current colour mode, consistent with the atlas being a reference rather than "what you're + looking at". (2) `showNationBorders()` (Territory view) used to unconditionally show the political + borders **and** the Step-6 alliance/rivalry arcs together — no way to see realm borders without the + diplomacy lines. Added an independent `showDiplomacy` bool (default on, key **`A`**, a toolbar + "Alliances" checkbox in Overlays), `&&`-ed into just the ally/rival arc draw calls (3D + 2D) so + political borders/war fronts still show under Territory regardless — Territory now toggles cleanly + with or without the arcs. `src/render` only, no save-format change. ## Current state @@ -930,7 +946,9 @@ all in 3D + 2D) · `N` day/night terminator (Live World) · `T` tide-coloured co `V` volcano markers (Live World) · `M` place-name labels (the atlas; names the world on first use) · `E` ecoregions colour view (names ecology on first use) · `I` habitability heat map · `U` settlements (the dawn of civilization on first press; toggles markers after) · -`P` territory / realms view + political borders (Realms tab lists nations) · +`P` territory / realms view + political borders + realm-name labels (Realms tab lists nations) · +`A` toggle the alliance/rivalry diplomacy arcs on the Territory view (on by default; political +borders/war fronts stay regardless) · `X` culture / faiths view + cultural borders (Cultures tab lists peoples, ethos & religion) · `Z` wealth / trade view + trade routes (sea/river/overland; prosperity feeds city growth) · `SPACE` or on-screen button pause · diff --git a/src/render/Toolbar.cpp b/src/render/Toolbar.cpp index 7bd924b..17cfd4f 100644 --- a/src/render/Toolbar.cpp +++ b/src/render/Toolbar.cpp @@ -130,12 +130,13 @@ void drawToolbar(Viewer& v) { {"Grid (G)", &v.showGrat}, {"Rivers (J)", &v.showRivers}, {"Day/night (N)", &v.dayNightOn}, {"Tides (T)", &v.showTides}, {"Currents (O)", &v.showCurrents}, {"Clouds (K)", &v.showClouds}, - {"Volcanoes (V)", &v.showVolcanoes}, + {"Volcanoes (V)", &v.showVolcanoes}, {"Alliances (A)", &v.showDiplomacy}, }; + const int overlayCount = 10; float halfW = (cw - GAP) * 0.5f; - for (int i = 0; i < 9; i += 2) { + for (int i = 0; i < overlayCount; i += 2) { guiCheckRow(Rectangle{ cx, cy, halfW, ROWH }, overlays[i].label, overlays[i].flag); - if (i + 1 < 9) guiCheckRow(Rectangle{ cx + halfW + GAP, cy, halfW, ROWH }, overlays[i + 1].label, overlays[i + 1].flag); + if (i + 1 < overlayCount) guiCheckRow(Rectangle{ cx + halfW + GAP, cy, halfW, ROWH }, overlays[i + 1].label, overlays[i + 1].flag); cy += ROWH + GAP; } { diff --git a/src/render/Viewer.hpp b/src/render/Viewer.hpp index c3091ab..bc8dede 100644 --- a/src/render/Viewer.hpp +++ b/src/render/Viewer.hpp @@ -110,6 +110,8 @@ struct Viewer { std::vector cultureBorders; // cultural-region border segments (civ Step 4, rebuilt with territory) std::vector warFrontier; // red frontier segments between realms currently at war (civ Step 5) std::vector allyLinks, rivalLinks; // civ Step 6: capital-to-capital arcs (allies green, rivals red) + bool showDiplomacy = true; // draw the alliance/rivalry arcs above with the Territory view (key A) -- + // independent of showNationBorders() so Territory can be shown without them std::vector tradeSea, tradeLand; // civ Step 7: trade routes (sea cyan / river+land amber) long lastTerritoryYear = -1; // sim year territory was last recomputed (recompute when it ticks) // Whether to draw the political/cultural/trade overlays is *derived* from `mode` -- deliberately diff --git a/src/render/ViewerInput.cpp b/src/render/ViewerInput.cpp index 5efc68c..06e9fb8 100644 --- a/src/render/ViewerInput.cpp +++ b/src/render/ViewerInput.cpp @@ -197,6 +197,7 @@ void Viewer::handleInput() { if (IsKeyPressed(KEY_D)) showDrift = !showDrift; if (IsKeyPressed(KEY_G)) showGrat = !showGrat; if (IsKeyPressed(KEY_J)) showRivers = !showRivers; + if (IsKeyPressed(KEY_A)) showDiplomacy = !showDiplomacy; // alliance/rivalry arcs with the Territory view if (IsKeyPressed(KEY_H)) toggleHydrology(); if (IsKeyPressed(KEY_L)) generateOrRegenerateBiota(); if (IsKeyPressed(KEY_E)) toggleEcoregionView(); diff --git a/src/render/ViewerRender.cpp b/src/render/ViewerRender.cpp index 4e4b6d4..33e5abc 100644 --- a/src/render/ViewerRender.cpp +++ b/src/render/ViewerRender.cpp @@ -108,7 +108,7 @@ void Viewer::renderGlobe3D() { } rlEnd(); rlSetLineWidth(1.0f); } - if (showNationBorders()) { // civ Step 6: diplomacy arcs (allies green, rivals dark red) + if (showNationBorders() && showDiplomacy) { // civ Step 6: diplomacy arcs (allies green, rivals dark red) rlSetLineWidth(2.0f); rlBegin(RL_LINES); rlColor4ub(70, 220, 120, 200); for (size_t i = 0; i + 1 < allyLinks.size(); i += 2) { @@ -394,8 +394,8 @@ void Viewer::drawMapOverlays(Rectangle vr, double lonOffset, float scale, float if (showNationBorders() && !nationBorders.empty()) drawSegments2D(nationBorders, Color{18, 18, 26, 235}, 2.0f * scale, vr, lonOffset); if (showCultureBorders() && !cultureBorders.empty()) drawSegments2D(cultureBorders, Color{245, 240, 220, 230}, 2.5f * scale, vr, lonOffset); if (showNationBorders() && !warFrontier.empty()) drawSegments2D(warFrontier, Color{235, 40, 30, 255}, 2.5f * scale, vr, lonOffset); - if (showNationBorders() && !allyLinks.empty()) drawSegments2D(allyLinks, Color{70, 220, 120, 220}, 1.5f * scale, vr, lonOffset); - if (showNationBorders() && !rivalLinks.empty()) drawSegments2D(rivalLinks, Color{150, 40, 60, 220}, 1.5f * scale, vr, lonOffset); + if (showNationBorders() && showDiplomacy && !allyLinks.empty()) drawSegments2D(allyLinks, Color{70, 220, 120, 220}, 1.5f * scale, vr, lonOffset); + if (showNationBorders() && showDiplomacy && !rivalLinks.empty()) drawSegments2D(rivalLinks, Color{150, 40, 60, 220}, 1.5f * scale, vr, lonOffset); if (showTradeRoutes() && !tradeSea.empty()) drawSegments2D(tradeSea, Color{90, 200, 235, 220}, 1.2f * scale, vr, lonOffset); if (showTradeRoutes() && !tradeLand.empty()) drawSegments2D(tradeLand, Color{230, 180, 90, 220}, 1.2f * scale, vr, lonOffset); if (showDrift && !driftArrows.empty()) drawSegments2D(driftArrows, Color{90, 230, 255, 255}, 2.0f * scale, vr, lonOffset); @@ -464,6 +464,25 @@ void Viewer::drawMapOverlays(Rectangle vr, double lonOffset, float scale, float DrawText(txt, (int)(lp.x + 4 * scale), (int)(lp.y - 8 * scale), fs, RAYWHITE); } } + // Realm labels (with the territory view): name kingdoms/empires at their capital. Mirrors the + // 3D realm-label block in renderFrame() -- previously 2D-only had none, so neither the on-screen + // map nor exportMapImage() ever showed realm names, only the 3D globe did. + if (drawLabels && showNationBorders() && !planet.nationList().empty()) { + for (const Nation& nat : planet.nationList()) { + if (nat.tier == NationTier::CityState) continue; // declutter: only multi-settlement realms + if (nat.capital < 0 || nat.capital >= (int)planet.settlements.size()) continue; + int cell = planet.settlements[nat.capital].cell; + if (cell < 0 || cell >= (int)planet.cells.size()) continue; + int font = (int)std::round((nat.tier == NationTier::Empire ? 16 : 14) * scale); + if (font <= 0) continue; + double lon, lat; dirToLonLat(planet.cells[cell].unit, lon, lat); + Vector2 lp = projLonLat(lon, lat, lonOffset, vr); + if (!CheckCollisionPointRec(lp, vr)) continue; + int w = MeasureText(nat.name.c_str(), font); + DrawText(nat.name.c_str(), (int)lp.x - w / 2 + 1, (int)lp.y - font - 7, font, Color{0, 0, 0, 205}); + DrawText(nat.name.c_str(), (int)lp.x - w / 2, (int)lp.y - font - 8, font, Color{245, 235, 210, 255}); + } + } // Place-name labels (the atlas). Minor features only when the map is zoomed in (on-screen) or // always for a high-res export, where there's ample room for the full detail. Skipped entirely // when the caller (exportAtlasImage) does its own decluttered label pass instead. @@ -582,6 +601,24 @@ void Viewer::exportAtlasImage() { labels.push_back({ lp, &f.name, (int)std::round(font * labelScale), col, prio, (float)f.size, dot }); } } + // Realm names (kingdoms/empires), regardless of the current colour mode -- an atlas is a + // reference, not "what you're currently looking at". Offset above the capital's own marker/name + // (rather than sharing its exact anchor point) so the two don't fight over the same box in the + // decluttering pass below; city-states are skipped (bare settlement name already covers them). + if (!planet.nationList().empty()) { + for (const Nation& nat : planet.nationList()) { + if (nat.tier == NationTier::CityState || nat.name.empty()) continue; + if (nat.capital < 0 || nat.capital >= (int)planet.settlements.size()) continue; + int cell = planet.settlements[nat.capital].cell; + if (cell < 0 || cell >= (int)planet.cells.size()) continue; + int font = (int)std::round((nat.tier == NationTier::Empire ? 16 : 14) * labelScale); + int prio = nat.tier == NationTier::Empire ? 0 : 1; + double lon, lat; dirToLonLat(planet.cells[cell].unit, lon, lat); + Vector2 lp = projLonLat(lon, lat, mapLon, er); + lp.y -= font * 0.9f; + labels.push_back({ lp, &nat.name, font, Color{245, 235, 210, 255}, prio, (float)nat.totalPop, false }); + } + } if (!planet.settlements.empty()) { const double townP = planet.cfg.civTownPop, cityP = planet.cfg.civCityPop, abP = planet.cfg.civAbandonPop; for (const Settlement& s : planet.settlements) {