mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-19 02:21:24 +01:00
sky-burgs-sky-routes
This commit is contained in:
parent
2c3692f000
commit
73ab86b957
23 changed files with 919 additions and 49 deletions
|
|
@ -34,6 +34,7 @@ function editBurg(id) {
|
|||
byId("burgCulture").addEventListener("input", changeCulture);
|
||||
byId("burgNameReCulture").addEventListener("click", generateNameCulture);
|
||||
byId("burgPopulation").addEventListener("change", changePopulation);
|
||||
byId("burgAltitude")?.addEventListener("change", changeAltitude);
|
||||
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
|
||||
byId("burgLinkOpen").addEventListener("click", openBurgLink);
|
||||
byId("burgLinkEdit").addEventListener("click", changeBurgLink);
|
||||
|
|
@ -77,13 +78,25 @@ function editBurg(id) {
|
|||
byId("burgTemperature").innerHTML = convertTemperature(temperature);
|
||||
byId("burgTemperatureLikeIn").dataset.tip =
|
||||
"Average yearly temperature is like in " + getTemperatureLikeness(temperature);
|
||||
byId("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]);
|
||||
const altitudeRow = byId("burgAltitudeRow");
|
||||
if (b.flying) {
|
||||
altitudeRow.style.display = "flex";
|
||||
byId("burgAltitude").value = b.altitude ?? 1000;
|
||||
byId("burgElevation").innerHTML = `${b.altitude ?? 1000} m (sky altitude)`;
|
||||
} else {
|
||||
altitudeRow.style.display = "none";
|
||||
byId("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]);
|
||||
}
|
||||
|
||||
// toggle features
|
||||
if (b.capital) byId("burgCapital").classList.remove("inactive");
|
||||
else byId("burgCapital").classList.add("inactive");
|
||||
if (b.port) byId("burgPort").classList.remove("inactive");
|
||||
else byId("burgPort").classList.add("inactive");
|
||||
if (b.skyPort) byId("burgSkyPort").classList.remove("inactive");
|
||||
else byId("burgSkyPort").classList.add("inactive");
|
||||
if (b.flying) byId("burgFlying").classList.remove("inactive");
|
||||
else byId("burgFlying").classList.add("inactive");
|
||||
if (b.citadel) byId("burgCitadel").classList.remove("inactive");
|
||||
else byId("burgCitadel").classList.add("inactive");
|
||||
if (b.walls) byId("burgWalls").classList.remove("inactive");
|
||||
|
|
@ -292,12 +305,50 @@ function editBurg(id) {
|
|||
updateBurgPreview(burg);
|
||||
}
|
||||
|
||||
function changeAltitude() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
burg.altitude = Math.max(0, Math.round(+byId("burgAltitude").value));
|
||||
}
|
||||
|
||||
function toggleFeature() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const feature = this.dataset.feature;
|
||||
const turnOn = this.classList.contains("inactive");
|
||||
if (feature === "port") togglePort(id);
|
||||
else if (feature === "skyPort") {
|
||||
burg.skyPort = +turnOn;
|
||||
// Assign to Sky State when turning on (if not a state capital)
|
||||
if (turnOn) {
|
||||
try {
|
||||
if (!burg.capital) {
|
||||
const skyId = ensureSkyState(id);
|
||||
if (burg.state !== skyId) {
|
||||
// Reassign cell ownership
|
||||
pack.cells.state[burg.cell] = skyId;
|
||||
burg.state = skyId;
|
||||
}
|
||||
}
|
||||
} catch (e) { ERROR && console.error(e); }
|
||||
}
|
||||
// Regenerate routes to reflect air network
|
||||
regenerateRoutes();
|
||||
}
|
||||
else if (feature === "flying") {
|
||||
burg.flying = +turnOn;
|
||||
if (turnOn) {
|
||||
try {
|
||||
const skyId = ensureSkyState(id);
|
||||
if (burg.state !== skyId) {
|
||||
pack.cells.state[burg.cell] = skyId;
|
||||
burg.state = skyId;
|
||||
}
|
||||
if (burg.altitude == null) burg.altitude = 1000;
|
||||
} catch (e) { ERROR && console.error(e); }
|
||||
}
|
||||
regenerateRoutes();
|
||||
}
|
||||
else if (feature === "capital") toggleCapital(id);
|
||||
else burg[feature] = +turnOn;
|
||||
if (burg[feature]) this.classList.remove("inactive");
|
||||
|
|
@ -434,8 +485,10 @@ function editBurg(id) {
|
|||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
|
||||
if (cells.h[cell] < 20) {
|
||||
tip("Cannot place burg into the water! Select a land cell", false, "error");
|
||||
const isWater = cells.h[cell] < 20;
|
||||
const allowWater = pack.burgs[id]?.flying || d3.event.altKey;
|
||||
if (isWater && !allowWater) {
|
||||
tip("Hold Alt or mark as Flying to place over water", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -447,7 +500,7 @@ function editBurg(id) {
|
|||
const newState = cells.state[cell];
|
||||
const oldState = burg.state;
|
||||
|
||||
if (newState !== oldState && burg.capital) {
|
||||
if (newState !== oldState && burg.capital && !isWater) {
|
||||
tip("Capital cannot be relocated into another state!", false, "error");
|
||||
return;
|
||||
}
|
||||
|
|
@ -477,7 +530,15 @@ function editBurg(id) {
|
|||
cells.burg[burg.cell] = 0;
|
||||
cells.burg[cell] = id;
|
||||
burg.cell = cell;
|
||||
burg.state = newState;
|
||||
|
||||
// Set target state based on terrain and sky features
|
||||
if (isWater || burg.flying) {
|
||||
const skyId = ensureSkyState(id);
|
||||
cells.state[cell] = skyId;
|
||||
burg.state = skyId;
|
||||
} else {
|
||||
burg.state = newState;
|
||||
}
|
||||
burg.x = x;
|
||||
burg.y = y;
|
||||
if (burg.capital) pack.states[newState].center = burg.cell;
|
||||
|
|
|
|||
|
|
@ -281,12 +281,25 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
|
|||
const point = d3.mouse(this);
|
||||
const cell = findCell(...point);
|
||||
|
||||
if (pack.cells.h[cell] < 20)
|
||||
return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||
// Allow placing over water as a flying burg when Alt is held
|
||||
if (pack.cells.h[cell] < 20 && !d3.event.altKey)
|
||||
return tip("Hold Alt to place a flying burg over water", false, "error");
|
||||
if (pack.cells.burg[cell])
|
||||
return tip("There is already a burg in this cell. Please select a free cell", false, "error");
|
||||
|
||||
addBurg(point); // add new burg
|
||||
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];
|
||||
burg.flying = 1;
|
||||
burg.skyPort = 1;
|
||||
if (burg.altitude == null) burg.altitude = 1000;
|
||||
const skyStateId = ensureSkyState(id);
|
||||
if (burg.state !== skyStateId) burg.state = skyStateId;
|
||||
// Keep as non-sea port
|
||||
burg.port = 0;
|
||||
}
|
||||
|
||||
if (d3.event.shiftKey === false) {
|
||||
exitAddBurgMode();
|
||||
|
|
@ -520,7 +533,7 @@ function downloadBurgsData() {
|
|||
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,`;
|
||||
data += `X_World (m),Y_World (m),X_Pixel,Y_Pixel,`; // New world coords + renamed pixel coords
|
||||
data += `Latitude,Longitude,`; // Keep for compatibility
|
||||
data += `Elevation (${heightUnit.value}),Temperature,Temperature likeness,`;
|
||||
data += `Elevation (${heightUnit.value}),Sky Altitude (m),Temperature,Temperature likeness,`;
|
||||
data += `Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town,Emblem,City Generator Link\n`;
|
||||
|
||||
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
|
|
@ -553,6 +566,8 @@ function downloadBurgsData() {
|
|||
|
||||
// Continue with elevation and other data
|
||||
data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
|
||||
// Sky altitude in meters (only for flying burgs), else blank
|
||||
data += (b.flying ? (b.altitude ?? 1000) : "") + ",";
|
||||
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
|
||||
data += convertTemperature(temperature) + ",";
|
||||
data += getTemperatureLikeness(temperature) + ",";
|
||||
|
|
|
|||
|
|
@ -227,6 +227,52 @@ 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;
|
||||
|
||||
// Create a new locked Sky State
|
||||
const b = burgs[anchorBurgId];
|
||||
const i = states.length;
|
||||
const culture = (b && b.culture) || cells.culture?.[b?.cell] || 1;
|
||||
const type = "Generic";
|
||||
const name = "Sky Realm";
|
||||
const color = "#5b8bd4";
|
||||
const coa = COA.generate(null, null, null, cultures[culture]?.type || "Generic");
|
||||
coa.shield = COA.getShield(culture, null);
|
||||
|
||||
const newState = {
|
||||
i,
|
||||
name,
|
||||
type,
|
||||
color,
|
||||
capital: anchorBurgId,
|
||||
center: b.cell,
|
||||
culture,
|
||||
expansionism: 0.1,
|
||||
coa,
|
||||
lock: 1,
|
||||
skyRealm: 1
|
||||
};
|
||||
|
||||
states.push(newState);
|
||||
|
||||
// Assign the burg and its cell to the Sky State
|
||||
if (cells && typeof b.cell === "number") cells.state[b.cell] = i;
|
||||
if (b) {
|
||||
b.state = i;
|
||||
b.capital = 1;
|
||||
// Move to cities layer for capitals
|
||||
moveBurgToGroup(anchorBurgId, "cities");
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
function moveAllBurgsToGroup(fromGroup, toGroup) {
|
||||
const groupToMove = document.querySelector(`#burgIcons #${fromGroup}`);
|
||||
const burgsToMove = Array.from(groupToMove.children).map(x => x.dataset.id);
|
||||
|
|
@ -316,6 +362,9 @@ function togglePort(burg) {
|
|||
function getBurgLink(burg) {
|
||||
if (burg.link) return burg.link;
|
||||
|
||||
// Sky burgs: force MFCG with sky-friendly parameters
|
||||
if (burg.flying || burg.skyPort) return createMfcgLink(burg, true);
|
||||
|
||||
const population = burg.population * populationRate * urbanization;
|
||||
if (population >= options.villageMaxPopulation || burg.citadel || burg.walls || burg.temple || burg.shanty)
|
||||
return createMfcgLink(burg);
|
||||
|
|
@ -323,42 +372,42 @@ function getBurgLink(burg) {
|
|||
return createVillageGeneratorLink(burg);
|
||||
}
|
||||
|
||||
function createMfcgLink(burg) {
|
||||
function createMfcgLink(burg, isSky = false) {
|
||||
const {cells} = pack;
|
||||
const {i, name, population: burgPopulation, cell} = burg;
|
||||
const burgSeed = burg.MFCG || seed + String(burg.i).padStart(4, 0);
|
||||
|
||||
const sizeRaw = 2.13 * Math.pow((burgPopulation * populationRate) / urbanDensity, 0.385);
|
||||
const size = minmax(Math.ceil(sizeRaw), 6, 100);
|
||||
const size = isSky ? 25 : minmax(Math.ceil(sizeRaw), 6, 100);
|
||||
const population = rn(burgPopulation * populationRate * urbanization);
|
||||
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
const coast = Number(burg.port > 0);
|
||||
const sea = (() => {
|
||||
if (!coast || !cells.haven[cell]) return null;
|
||||
const river = isSky ? 0 : (cells.r[cell] ? 1 : 0);
|
||||
const coast = isSky ? 0 : Number(burg.port > 0);
|
||||
|
||||
// calculate see 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;
|
||||
if (deg < 0) deg += 360;
|
||||
return rn(normalize(deg, 0, 360) * 2, 2);
|
||||
})();
|
||||
const sea = !isSky && coast && cells.haven[cell]
|
||||
? (() => {
|
||||
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;
|
||||
if (deg < 0) deg += 360;
|
||||
return rn(normalize(deg, 0, 360) * 2, 2);
|
||||
})()
|
||||
: null;
|
||||
|
||||
const arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
|
||||
const farms = +arableBiomes.includes(cells.biome[cell]);
|
||||
const farms = isSky ? 0 : +arableBiomes.includes(cells.biome[cell]);
|
||||
|
||||
const citadel = +burg.citadel;
|
||||
const urban_castle = +(citadel && each(2)(i));
|
||||
const citadel = isSky ? 1 : +burg.citadel;
|
||||
const urban_castle = isSky ? 1 : +(citadel && each(2)(i));
|
||||
const hub = isSky ? 0 : Routes.isCrossroad(cell);
|
||||
const walls = isSky ? 1 : +burg.walls;
|
||||
const plaza = isSky ? 1 : +burg.plaza;
|
||||
const temple = isSky ? 1 : +burg.temple;
|
||||
const shantytown = isSky ? 0 : +burg.shanty;
|
||||
const greens = isSky ? 1 : undefined;
|
||||
const gates = isSky ? 0 : -1;
|
||||
|
||||
const hub = Routes.isCrossroad(cell);
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shantytown = +burg.shanty;
|
||||
|
||||
const url = new URL("https://watabou.github.io/city-generator/");
|
||||
url.search = new URLSearchParams({
|
||||
const params = {
|
||||
name,
|
||||
population,
|
||||
size,
|
||||
|
|
@ -372,9 +421,13 @@ function createMfcgLink(burg) {
|
|||
plaza,
|
||||
temple,
|
||||
walls,
|
||||
shantytown,
|
||||
gates: -1
|
||||
});
|
||||
shantytown
|
||||
};
|
||||
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);
|
||||
|
||||
return url.toString();
|
||||
|
|
|
|||
|
|
@ -401,11 +401,14 @@ function editHeightmap(options) {
|
|||
// find best cell for burgs
|
||||
for (const b of pack.burgs) {
|
||||
if (!b.i || b.removed) continue;
|
||||
b.cell = findBurgCell(b.x, b.y);
|
||||
// Keep flying burgs at their current (possibly water) cell
|
||||
if (!b.flying) {
|
||||
b.cell = findBurgCell(b.x, b.y);
|
||||
}
|
||||
b.feature = pack.cells.f[b.cell];
|
||||
|
||||
pack.cells.burg[b.cell] = b.i;
|
||||
if (!b.capital && pack.cells.h[b.cell] < 20) removeBurg(b.i);
|
||||
if (!b.capital && pack.cells.h[b.cell] < 20 && !b.flying) removeBurg(b.i);
|
||||
if (b.capital) pack.states[b.state].center = b.cell;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ function addStylePreset() {
|
|||
"#roads": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#trails": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#searoutes": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#airroutes": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#statesBody": ["opacity", "filter"],
|
||||
"#statesHalo": ["opacity", "data-width", "stroke-width", "filter"],
|
||||
"#provs": ["opacity", "fill", "font-size", "font-family", "filter"],
|
||||
|
|
@ -290,6 +291,16 @@ function addStylePreset() {
|
|||
"stroke-dasharray",
|
||||
"stroke-linecap"
|
||||
],
|
||||
"#burgIcons > #skyburgs": [
|
||||
"opacity",
|
||||
"fill",
|
||||
"fill-opacity",
|
||||
"size",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"stroke-dasharray",
|
||||
"stroke-linecap"
|
||||
],
|
||||
"#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"],
|
||||
"#burgLabels > #towns": [
|
||||
"opacity",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue