mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 18:11:24 +01:00
Sky burgs + air routes: layer, editor toggles, styling, altitude, icons, and generators
This commit is contained in:
parent
e94ec5b123
commit
c075e704fd
6 changed files with 45 additions and 12 deletions
1
main.js
1
main.js
|
|
@ -114,6 +114,7 @@ labels.append("g").attr("id", "states");
|
||||||
labels.append("g").attr("id", "addedLabels");
|
labels.append("g").attr("id", "addedLabels");
|
||||||
|
|
||||||
burgIcons.append("g").attr("id", "cities");
|
burgIcons.append("g").attr("id", "cities");
|
||||||
|
burgIcons.append("g").attr("id", "skyburgs");
|
||||||
burgLabels.append("g").attr("id", "cities");
|
burgLabels.append("g").attr("id", "cities");
|
||||||
anchors.append("g").attr("id", "cities");
|
anchors.append("g").attr("id", "cities");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,21 @@ function drawBurgIcons() {
|
||||||
.attr("width", townsAnchorsSize)
|
.attr("width", townsAnchorsSize)
|
||||||
.attr("height", 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");
|
TIME && console.timeEnd("drawBurgIcons");
|
||||||
|
|
||||||
// Sky burgs (flying or sky port) — styled separately
|
// Sky burgs (flying or sky port) — styled separately
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,8 @@ window.Routes = (function () {
|
||||||
|
|
||||||
for (const burg of burgs) {
|
for (const burg of burgs) {
|
||||||
if (burg.i && !burg.removed) {
|
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;
|
const {feature, capital, port} = burg;
|
||||||
addBurg(burgsByFeature, feature, burg);
|
addBurg(burgsByFeature, feature, burg);
|
||||||
|
|
||||||
|
|
@ -846,13 +848,11 @@ window.Routes = (function () {
|
||||||
function generateAirRoutes() {
|
function generateAirRoutes() {
|
||||||
TIME && console.time("generateAirRoutes");
|
TIME && console.time("generateAirRoutes");
|
||||||
const air = [];
|
const air = [];
|
||||||
|
|
||||||
const skyPorts = pack.burgs.filter(b => b && b.i && !b.removed && (b.skyPort || b.flying));
|
const skyPorts = pack.burgs.filter(b => b && b.i && !b.removed && (b.skyPort || b.flying));
|
||||||
if (skyPorts.length < 2) {
|
if (skyPorts.length < 2) {
|
||||||
TIME && console.timeEnd("generateAirRoutes");
|
TIME && console.timeEnd("generateAirRoutes");
|
||||||
return air;
|
return air;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use Urquhart edges to avoid a complete graph
|
// Use Urquhart edges to avoid a complete graph
|
||||||
const points = skyPorts.map(b => [b.x, b.y]);
|
const points = skyPorts.map(b => [b.x, b.y]);
|
||||||
const edges = calculateUrquhartEdges(points);
|
const edges = calculateUrquhartEdges(points);
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,7 @@ function editBurg(id) {
|
||||||
}
|
}
|
||||||
// Regenerate routes to reflect air network
|
// Regenerate routes to reflect air network
|
||||||
regenerateRoutes();
|
regenerateRoutes();
|
||||||
|
if (layerIsOn("toggleBurgIcons")) drawBurgIcons();
|
||||||
}
|
}
|
||||||
else if (feature === "flying") {
|
else if (feature === "flying") {
|
||||||
burg.flying = +turnOn;
|
burg.flying = +turnOn;
|
||||||
|
|
@ -348,6 +349,7 @@ function editBurg(id) {
|
||||||
} catch (e) { ERROR && console.error(e); }
|
} catch (e) { ERROR && console.error(e); }
|
||||||
}
|
}
|
||||||
regenerateRoutes();
|
regenerateRoutes();
|
||||||
|
if (layerIsOn("toggleBurgIcons")) drawBurgIcons();
|
||||||
}
|
}
|
||||||
else if (feature === "capital") toggleCapital(id);
|
else if (feature === "capital") toggleCapital(id);
|
||||||
else burg[feature] = +turnOn;
|
else burg[feature] = +turnOn;
|
||||||
|
|
@ -541,11 +543,18 @@ function editBurg(id) {
|
||||||
}
|
}
|
||||||
burg.x = x;
|
burg.x = x;
|
||||||
burg.y = y;
|
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();
|
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() {
|
function editBurgLegend() {
|
||||||
const id = elSelected.attr("data-id");
|
const id = elSelected.attr("data-id");
|
||||||
const name = elSelected.text();
|
const name = elSelected.text();
|
||||||
|
|
|
||||||
|
|
@ -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");
|
return tip("There is already a burg in this cell. Please select a free cell", false, "error");
|
||||||
|
|
||||||
const id = addBurg(point); // add new burg
|
const id = addBurg(point); // add new burg
|
||||||
|
|
||||||
// Mark flying burgs and assign to Sky State, make them sky ports
|
// Mark flying burgs and assign to Sky State, make them sky ports
|
||||||
if (pack.cells.h[cell] < 20) {
|
if (pack.cells.h[cell] < 20) {
|
||||||
const burg = pack.burgs[id];
|
const burg = pack.burgs[id];
|
||||||
|
|
@ -299,6 +298,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
|
||||||
if (burg.state !== skyStateId) burg.state = skyStateId;
|
if (burg.state !== skyStateId) burg.state = skyStateId;
|
||||||
// Keep as non-sea port
|
// Keep as non-sea port
|
||||||
burg.port = 0;
|
burg.port = 0;
|
||||||
|
if (layerIsOn("toggleBurgIcons")) drawBurgIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d3.event.shiftKey === false) {
|
if (d3.event.shiftKey === false) {
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,9 @@ function addBurg(point) {
|
||||||
|
|
||||||
BurgsAndStates.defineBurgFeatures(burg);
|
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")) {
|
if (newRoute && layerIsOn("toggleRoutes")) {
|
||||||
routes
|
routes
|
||||||
.select("#" + newRoute.group)
|
.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
|
// Ensure a dedicated locked Sky State exists; create if missing and return its id
|
||||||
function ensureSkyState(anchorBurgId) {
|
function ensureSkyState(anchorBurgId) {
|
||||||
const {states, burgs, cultures, cells} = pack;
|
const {states, burgs, cultures, cells} = pack;
|
||||||
|
|
||||||
// Reuse existing sky state if present
|
// Reuse existing sky state if present
|
||||||
let sky = states.find(s => s && s.i && !s.removed && s.skyRealm);
|
let sky = states.find(s => s && s.i && !s.removed && s.skyRealm);
|
||||||
if (sky) return sky.i;
|
if (sky) return sky.i;
|
||||||
|
|
@ -258,7 +259,6 @@ function ensureSkyState(anchorBurgId) {
|
||||||
lock: 1,
|
lock: 1,
|
||||||
skyRealm: 1
|
skyRealm: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
states.push(newState);
|
states.push(newState);
|
||||||
|
|
||||||
// Assign the burg and its cell to the Sky State
|
// Assign the burg and its cell to the Sky State
|
||||||
|
|
@ -269,7 +269,6 @@ function ensureSkyState(anchorBurgId) {
|
||||||
// Move to cities layer for capitals
|
// Move to cities layer for capitals
|
||||||
moveBurgToGroup(anchorBurgId, "cities");
|
moveBurgToGroup(anchorBurgId, "cities");
|
||||||
}
|
}
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,7 +366,7 @@ function getBurgLink(burg) {
|
||||||
|
|
||||||
const population = burg.population * populationRate * urbanization;
|
const population = burg.population * populationRate * urbanization;
|
||||||
if (population >= options.villageMaxPopulation || burg.citadel || burg.walls || burg.temple || burg.shanty)
|
if (population >= options.villageMaxPopulation || burg.citadel || burg.walls || burg.temple || burg.shanty)
|
||||||
return createMfcgLink(burg);
|
return createMfcgLink(burg, false);
|
||||||
|
|
||||||
return createVillageGeneratorLink(burg);
|
return createVillageGeneratorLink(burg);
|
||||||
}
|
}
|
||||||
|
|
@ -386,6 +385,7 @@ function createMfcgLink(burg, isSky = false) {
|
||||||
|
|
||||||
const sea = !isSky && coast && cells.haven[cell]
|
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 p1 = cells.p[cell];
|
||||||
const p2 = cells.p[cells.haven[cell]];
|
const p2 = cells.p[cells.haven[cell]];
|
||||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
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 greens = isSky ? 1 : undefined;
|
||||||
const gates = isSky ? 0 : -1;
|
const gates = isSky ? 0 : -1;
|
||||||
|
|
||||||
|
const url = new URL("https://watabou.github.io/city-generator/");
|
||||||
const params = {
|
const params = {
|
||||||
name,
|
name,
|
||||||
population,
|
population,
|
||||||
|
|
@ -425,8 +426,6 @@ function createMfcgLink(burg, isSky = false) {
|
||||||
};
|
};
|
||||||
if (greens !== undefined) params.greens = greens;
|
if (greens !== undefined) params.greens = greens;
|
||||||
if (gates !== undefined) params.gates = gates;
|
if (gates !== undefined) params.gates = gates;
|
||||||
|
|
||||||
const url = new URL("https://watabou.github.io/city-generator/");
|
|
||||||
url.search = new URLSearchParams(params);
|
url.search = new URLSearchParams(params);
|
||||||
if (sea) url.searchParams.append("sea", sea);
|
if (sea) url.searchParams.append("sea", sea);
|
||||||
|
|
||||||
|
|
@ -483,6 +482,15 @@ function createVillageGeneratorLink(burg) {
|
||||||
return url.toString();
|
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
|
// draw legend box
|
||||||
function drawLegend(name, data) {
|
function drawLegend(name, data) {
|
||||||
legend.selectAll("*").remove(); // fully redraw every time
|
legend.selectAll("*").remove(); // fully redraw every time
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue