Sky burgs + air routes: layer, editor toggles, styling, altitude, icons, and generators

This commit is contained in:
barrulus 2025-09-05 17:18:21 +01:00
parent e94ec5b123
commit c075e704fd
6 changed files with 45 additions and 12 deletions

View file

@ -114,6 +114,7 @@ labels.append("g").attr("id", "states");
labels.append("g").attr("id", "addedLabels");
burgIcons.append("g").attr("id", "cities");
burgIcons.append("g").attr("id", "skyburgs");
burgLabels.append("g").attr("id", "cities");
anchors.append("g").attr("id", "cities");

View file

@ -65,6 +65,21 @@ function drawBurgIcons() {
.attr("width", townsAnchorsSize)
.attr("height", townsAnchorsSize);
// Sky burgs (flying or sky port)
const sky = pack.burgs.filter(b => b.i && !b.removed && (b.flying || b.skyPort));
const skyIcons = burgIcons.select("#skyburgs");
const skySize = skyIcons.attr("size") || 0.6;
skyIcons
.selectAll("circle")
.data(sky)
.enter()
.append("circle")
.attr("id", d => "burg" + d.i)
.attr("data-id", d => d.i)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", skySize);
TIME && console.timeEnd("drawBurgIcons");
// Sky burgs (flying or sky port) — styled separately

View file

@ -75,6 +75,8 @@ window.Routes = (function () {
for (const burg of burgs) {
if (burg.i && !burg.removed) {
// Exclude flying / sky port burgs from land road/trail graphs
if (burg.flying || burg.skyPort) continue;
const {feature, capital, port} = burg;
addBurg(burgsByFeature, feature, burg);
@ -846,13 +848,11 @@ window.Routes = (function () {
function generateAirRoutes() {
TIME && console.time("generateAirRoutes");
const air = [];
const skyPorts = pack.burgs.filter(b => b && b.i && !b.removed && (b.skyPort || b.flying));
if (skyPorts.length < 2) {
TIME && console.timeEnd("generateAirRoutes");
return air;
}
// Use Urquhart edges to avoid a complete graph
const points = skyPorts.map(b => [b.x, b.y]);
const edges = calculateUrquhartEdges(points);

View file

@ -334,6 +334,7 @@ function editBurg(id) {
}
// Regenerate routes to reflect air network
regenerateRoutes();
if (layerIsOn("toggleBurgIcons")) drawBurgIcons();
}
else if (feature === "flying") {
burg.flying = +turnOn;
@ -348,6 +349,7 @@ function editBurg(id) {
} catch (e) { ERROR && console.error(e); }
}
regenerateRoutes();
if (layerIsOn("toggleBurgIcons")) drawBurgIcons();
}
else if (feature === "capital") toggleCapital(id);
else burg[feature] = +turnOn;
@ -541,11 +543,18 @@ function editBurg(id) {
}
burg.x = x;
burg.y = y;
if (burg.capital) pack.states[newState].center = burg.cell;
if (burg.capital) pack.states[burg.state].center = burg.cell;
if (d3.event.shiftKey === false) toggleRelocateBurg();
}
function changeAltitude() {
const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];
burg.altitude = Math.max(0, Math.round(+byId("burgAltitude").value));
if (burg.flying) byId("burgElevation").innerHTML = `${burg.altitude} m (sky altitude)`;
}
function editBurgLegend() {
const id = elSelected.attr("data-id");
const name = elSelected.text();

View file

@ -288,7 +288,6 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
return tip("There is already a burg in this cell. Please select a free cell", false, "error");
const id = addBurg(point); // add new burg
// Mark flying burgs and assign to Sky State, make them sky ports
if (pack.cells.h[cell] < 20) {
const burg = pack.burgs[id];
@ -299,6 +298,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
if (burg.state !== skyStateId) burg.state = skyStateId;
// Keep as non-sea port
burg.port = 0;
if (layerIsOn("toggleBurgIcons")) drawBurgIcons();
}
if (d3.event.shiftKey === false) {

View file

@ -189,7 +189,9 @@ function addBurg(point) {
BurgsAndStates.defineBurgFeatures(burg);
const newRoute = Routes.connect(cellId);
// Do not auto-connect routes for water-placed (flying) burgs
const isWater = cells.h[cellId] < 20;
const newRoute = isWater ? null : Routes.connect(cellId);
if (newRoute && layerIsOn("toggleRoutes")) {
routes
.select("#" + newRoute.group)
@ -230,7 +232,6 @@ function moveBurgToGroup(id, g) {
// Ensure a dedicated locked Sky State exists; create if missing and return its id
function ensureSkyState(anchorBurgId) {
const {states, burgs, cultures, cells} = pack;
// Reuse existing sky state if present
let sky = states.find(s => s && s.i && !s.removed && s.skyRealm);
if (sky) return sky.i;
@ -258,7 +259,6 @@ function ensureSkyState(anchorBurgId) {
lock: 1,
skyRealm: 1
};
states.push(newState);
// Assign the burg and its cell to the Sky State
@ -269,7 +269,6 @@ function ensureSkyState(anchorBurgId) {
// Move to cities layer for capitals
moveBurgToGroup(anchorBurgId, "cities");
}
return i;
}
@ -367,7 +366,7 @@ function getBurgLink(burg) {
const population = burg.population * populationRate * urbanization;
if (population >= options.villageMaxPopulation || burg.citadel || burg.walls || burg.temple || burg.shanty)
return createMfcgLink(burg);
return createMfcgLink(burg, false);
return createVillageGeneratorLink(burg);
}
@ -386,6 +385,7 @@ function createMfcgLink(burg, isSky = false) {
const sea = !isSky && coast && cells.haven[cell]
? (() => {
// calculate sea direction: 0 = south, 0.5 = west, 1 = north, 1.5 = east
const p1 = cells.p[cell];
const p2 = cells.p[cells.haven[cell]];
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
@ -407,6 +407,7 @@ function createMfcgLink(burg, isSky = false) {
const greens = isSky ? 1 : undefined;
const gates = isSky ? 0 : -1;
const url = new URL("https://watabou.github.io/city-generator/");
const params = {
name,
population,
@ -425,8 +426,6 @@ function createMfcgLink(burg, isSky = false) {
};
if (greens !== undefined) params.greens = greens;
if (gates !== undefined) params.gates = gates;
const url = new URL("https://watabou.github.io/city-generator/");
url.search = new URLSearchParams(params);
if (sea) url.searchParams.append("sea", sea);
@ -483,6 +482,15 @@ function createVillageGeneratorLink(burg) {
return url.toString();
}
// helper: draw legend entry for Air routes
function drawAirRoutesLegend() {
const group = document.querySelector("#airroutes");
if (!group) return tip("Air routes group not found", false, "error");
const stroke = group.getAttribute("stroke") || "#8a2be2";
const data = [["airroutes", stroke, "Air routes"]];
drawLegend("Routes", data);
}
// draw legend box
function drawLegend(name, data) {
legend.selectAll("*").remove(); // fully redraw every time