From d6c3c46a5e02d23a1f58e7eda8e675d34f0f5331 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 14 Mar 2024 13:56:12 +0400 Subject: [PATCH] feat: generate watabou preview links for villages (#1056) Co-authored-by: Azgaar --- index.html | 76 +++++------ main.js | 5 +- modules/burgs-and-states.js | 14 +- modules/dynamic/auto-update.js | 16 +++ modules/io/load.js | 2 +- modules/ui/burg-editor.js | 226 ++++++++++++++++----------------- modules/ui/burgs-overview.js | 2 +- modules/ui/editors.js | 95 ++++++++++---- versioning.js | 3 +- 9 files changed, 247 insertions(+), 192 deletions(-) diff --git a/index.html b/index.html index 4a36f3d4..9ac253c8 100644 --- a/index.html +++ b/index.html @@ -3329,6 +3329,22 @@ +
+
Temperature:
+ , like in + + +
+ +
+
Elevation:
+ above sea level +
+
Features:
- -
-
Temperature:
- , like in - - -
- -
-
Elevation:
- above sea level -
-
-
- See in City Generator by Watabou. -
- Seed: +
+
+ Burg preview: +
+
-
- +
@@ -3460,7 +3450,7 @@
- + @@ -8044,7 +8034,7 @@ - + @@ -8061,11 +8051,11 @@ - + - + @@ -8082,12 +8072,12 @@ - + - + @@ -8103,7 +8093,7 @@ - + diff --git a/main.js b/main.js index 0fa7f790..efc115cf 100644 --- a/main.js +++ b/main.js @@ -185,12 +185,13 @@ const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", onZoomDebouced); // default options, based on Earth data let options = { pinNotes: false, - showMFCGMap: true, winds: [225, 45, 225, 315, 135, 315], temperatureEquator: 27, temperatureNorthPole: -30, temperatureSouthPole: -15, - stateLabelsMode: "auto" + stateLabelsMode: "auto", + showBurgPreview: true, + villageMaxPopulation: 2000 }; let mapCoordinates = {}; // map coordinates on globe diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index 390165dc..f4f6463a 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -252,13 +252,15 @@ window.BurgsAndStates = (function () { .filter(b => (newburg ? b.i == newburg.i : b.i && !b.removed)) .forEach(b => { const pop = b.population; - b.citadel = b.capital || (pop > 50 && P(0.75)) || P(0.5) ? 1 : 0; - b.plaza = pop > 50 || (pop > 30 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.25) ? 1 : 0; - b.walls = b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.2) ? 1 : 0; - b.shanty = pop > 60 || (pop > 40 && P(0.75)) || (pop > 20 && b.walls && P(0.4)) ? 1 : 0; + b.citadel = Number(b.capital || (pop > 50 && P(0.75)) || (pop > 15 && P(0.5)) || P(0.1)); + b.plaza = Number(pop > 20 || (pop > 10 && P(0.8)) || (pop > 4 && P(0.7)) || P(0.6)); + b.walls = Number(b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.1)); + b.shanty = Number(pop > 60 || (pop > 40 && P(0.75)) || (pop > 20 && b.walls && P(0.4))); const religion = cells.religion[b.cell]; const theocracy = pack.states[b.state].form === "Theocracy"; - b.temple = (religion && theocracy) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5)) ? 1 : 0; + b.temple = Number( + (religion && theocracy && P(0.5)) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5)) + ); }); }; @@ -860,7 +862,7 @@ window.BurgsAndStates = (function () { } if (base === 31 && (form === "Empire" || form === "Kingdom")) return "Khanate"; // Mongolian - if (base === 16 && (form === "Principality" )) return "Beylik"; // Turkic + if (base === 16 && form === "Principality") return "Beylik"; // Turkic if (base === 5 && (form === "Empire" || form === "Kingdom")) return "Tsardom"; // Ruthenian if (base === 16 && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic if (base === 12 && (form === "Kingdom" || form === "Grand Duchy")) return "Shogunate"; // Japanese diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index 51c5ee4c..a77ddd45 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -827,4 +827,20 @@ export function resolveVersionConflicts(version) { }); }); } + + if (version < 1.97) { + // v1.97.00 changed MFCG link to an arbitrary preview URL + options.villageMaxPopulation = 2000; + options.showBurgPreview = options.showMFCGMap; + delete options.showMFCGMap; + + pack.burgs.forEach(burg => { + if (!burg.i || burg.removed) return; + + if (burg.MFCG) { + burg.link = getBurgLink(burg); + delete burg.MFCG; + } + }); + } } diff --git a/modules/io/load.js b/modules/io/load.js index 3f6abf40..d07679d4 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -456,7 +456,7 @@ async function parseLoadedData(data, mapVersion) { { // dynamically import and run auto-update script const versionNumber = parseFloat(params[0]); - const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.96.00"); + const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.97.00"); resolveVersionConflicts(versionNumber); } diff --git a/modules/ui/burg-editor.js b/modules/ui/burg-editor.js index dd34b38a..a90a11d8 100644 --- a/modules/ui/burg-editor.js +++ b/modules/ui/burg-editor.js @@ -21,38 +21,37 @@ function editBurg(id) { modules.editBurg = true; // add listeners - document.getElementById("burgGroupShow").addEventListener("click", showGroupSection); - document.getElementById("burgGroupHide").addEventListener("click", hideGroupSection); - document.getElementById("burgSelectGroup").addEventListener("change", changeGroup); - document.getElementById("burgInputGroup").addEventListener("change", createNewGroup); - document.getElementById("burgAddGroup").addEventListener("click", toggleNewGroupInput); - document.getElementById("burgRemoveGroup").addEventListener("click", removeBurgsGroup); + byId("burgGroupShow").addEventListener("click", showGroupSection); + byId("burgGroupHide").addEventListener("click", hideGroupSection); + byId("burgSelectGroup").addEventListener("change", changeGroup); + byId("burgInputGroup").addEventListener("change", createNewGroup); + byId("burgAddGroup").addEventListener("click", toggleNewGroupInput); + byId("burgRemoveGroup").addEventListener("click", removeBurgsGroup); - document.getElementById("burgName").addEventListener("input", changeName); - document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom); - document.getElementById("burgType").addEventListener("input", changeType); - document.getElementById("burgCulture").addEventListener("input", changeCulture); - document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture); - document.getElementById("burgPopulation").addEventListener("change", changePopulation); + byId("burgName").addEventListener("input", changeName); + byId("burgNameReRandom").addEventListener("click", generateNameRandom); + byId("burgType").addEventListener("input", changeType); + byId("burgCulture").addEventListener("input", changeCulture); + byId("burgNameReCulture").addEventListener("click", generateNameCulture); + byId("burgPopulation").addEventListener("change", changePopulation); burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature)); - document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed); - document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed); - document.getElementById("addCustomMFCGBurgLink").addEventListener("click", addCustomMfcgLink); + byId("burgLinkOpen").addEventListener("click", openBurgLink); + byId("burgLinkEdit").addEventListener("click", changeBurgLink); - document.getElementById("burgStyleShow").addEventListener("click", showStyleSection); - document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection); - document.getElementById("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle); - document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle); - document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle); + byId("burgStyleShow").addEventListener("click", showStyleSection); + byId("burgStyleHide").addEventListener("click", hideStyleSection); + byId("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle); + byId("burgEditIconStyle").addEventListener("click", editGroupIconStyle); + byId("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle); - document.getElementById("burgEmblem").addEventListener("click", openEmblemEdit); - document.getElementById("burgToggleMFCGMap").addEventListener("click", toggleMFCGMap); - document.getElementById("burgEditEmblem").addEventListener("click", openEmblemEdit); - document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg); - document.getElementById("burglLegend").addEventListener("click", editBurgLegend); - document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton); - document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg); - document.getElementById("burgTemperatureGraph").addEventListener("click", showTemperatureGraph); + byId("burgEmblem").addEventListener("click", openEmblemEdit); + byId("burgTogglePreview").addEventListener("click", toggleBurgPreview); + byId("burgEditEmblem").addEventListener("click", openEmblemEdit); + byId("burgRelocate").addEventListener("click", toggleRelocateBurg); + byId("burglLegend").addEventListener("click", editBurgLegend); + byId("burgLock").addEventListener("click", toggleBurgLockButton); + byId("burgRemove").addEventListener("click", removeSelectedBurg); + byId("burgTemperatureGraph").addEventListener("click", showTemperatureGraph); function updateBurgValues() { const id = +elSelected.attr("data-id"); @@ -60,46 +59,46 @@ function editBurg(id) { const province = pack.cells.province[b.cell]; const provinceName = province ? pack.provinces[province].fullName + ", " : ""; const stateName = pack.states[b.state].fullName || pack.states[b.state].name; - document.getElementById("burgProvinceAndState").innerHTML = provinceName + stateName; + byId("burgProvinceAndState").innerHTML = provinceName + stateName; - document.getElementById("burgName").value = b.name; - document.getElementById("burgType").value = b.type || "Generic"; - document.getElementById("burgPopulation").value = rn(b.population * populationRate * urbanization); - document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none"; + byId("burgName").value = b.name; + byId("burgType").value = b.type || "Generic"; + byId("burgPopulation").value = rn(b.population * populationRate * urbanization); + byId("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none"; // update list and select culture - const cultureSelect = document.getElementById("burgCulture"); + const cultureSelect = byId("burgCulture"); cultureSelect.options.length = 0; const cultures = pack.cultures.filter(c => !c.removed); cultures.forEach(c => cultureSelect.options.add(new Option(c.name, c.i, false, c.i === b.culture))); const temperature = grid.cells.temp[pack.cells.g[b.cell]]; - document.getElementById("burgTemperature").innerHTML = convertTemperature(temperature); - document.getElementById("burgTemperatureLikeIn").innerHTML = getTemperatureLikeness(temperature); - document.getElementById("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]); + byId("burgTemperature").innerHTML = convertTemperature(temperature); + byId("burgTemperatureLikeIn").innerHTML = getTemperatureLikeness(temperature); + byId("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]); // toggle features - if (b.capital) document.getElementById("burgCapital").classList.remove("inactive"); - else document.getElementById("burgCapital").classList.add("inactive"); - if (b.port) document.getElementById("burgPort").classList.remove("inactive"); - else document.getElementById("burgPort").classList.add("inactive"); - if (b.citadel) document.getElementById("burgCitadel").classList.remove("inactive"); - else document.getElementById("burgCitadel").classList.add("inactive"); - if (b.walls) document.getElementById("burgWalls").classList.remove("inactive"); - else document.getElementById("burgWalls").classList.add("inactive"); - if (b.plaza) document.getElementById("burgPlaza").classList.remove("inactive"); - else document.getElementById("burgPlaza").classList.add("inactive"); - if (b.temple) document.getElementById("burgTemple").classList.remove("inactive"); - else document.getElementById("burgTemple").classList.add("inactive"); - if (b.shanty) document.getElementById("burgShanty").classList.remove("inactive"); - else document.getElementById("burgShanty").classList.add("inactive"); + 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.citadel) byId("burgCitadel").classList.remove("inactive"); + else byId("burgCitadel").classList.add("inactive"); + if (b.walls) byId("burgWalls").classList.remove("inactive"); + else byId("burgWalls").classList.add("inactive"); + if (b.plaza) byId("burgPlaza").classList.remove("inactive"); + else byId("burgPlaza").classList.add("inactive"); + if (b.temple) byId("burgTemple").classList.remove("inactive"); + else byId("burgTemple").classList.add("inactive"); + if (b.shanty) byId("burgShanty").classList.remove("inactive"); + else byId("burgShanty").classList.add("inactive"); //toggle lock updateBurgLockIcon(); // select group const group = elSelected.node().parentNode.id; - const select = document.getElementById("burgSelectGroup"); + const select = byId("burgSelectGroup"); select.options.length = 0; // remove all options burgLabels.selectAll("g").each(function () { @@ -109,20 +108,13 @@ function editBurg(id) { // set emlem image const coaID = "burgCOA" + id; COArenderer.trigger(coaID, b.coa); - document.getElementById("burgEmblem").setAttribute("href", "#" + coaID); + byId("burgEmblem").setAttribute("href", "#" + coaID); - if (options.showMFCGMap) { - document.getElementById("mfcgPreviewSection").style.display = "block"; - updateMFCGFrame(b); - - if (b.link) { - document.getElementById("mfcgBurgSeedSection").style.display = "none"; - } else { - document.getElementById("mfcgBurgSeedSection").style.display = "inline-block"; - document.getElementById("mfcgBurgSeed").value = getBurgSeed(b); - } + if (options.showBurgPreview) { + byId("burgPreviewSection").style.display = "block"; + updateBurgPreview(b); } else { - document.getElementById("mfcgPreviewSection").style.display = "none"; + byId("burgPreviewSection").style.display = "none"; } } @@ -141,15 +133,15 @@ function editBurg(id) { function showGroupSection() { document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none")); - document.getElementById("burgGroupSection").style.display = "inline-block"; + byId("burgGroupSection").style.display = "inline-block"; } function hideGroupSection() { document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block")); - document.getElementById("burgGroupSection").style.display = "none"; - document.getElementById("burgInputGroup").style.display = "none"; - document.getElementById("burgInputGroup").value = ""; - document.getElementById("burgSelectGroup").style.display = "inline-block"; + byId("burgGroupSection").style.display = "none"; + byId("burgInputGroup").style.display = "none"; + byId("burgInputGroup").value = ""; + byId("burgSelectGroup").style.display = "inline-block"; } function changeGroup() { @@ -178,7 +170,7 @@ function editBurg(id) { .replace(/ /g, "_") .replace(/[^\w\s]/gi, ""); - if (document.getElementById(group)) { + if (byId(group)) { tip("Element with this id already exists. Please provide a unique name", false, "error"); return; } @@ -206,10 +198,10 @@ function editBurg(id) { // just rename if only 1 element left const count = elSelected.node().parentNode.childElementCount; if (oldGroup !== "cities" && oldGroup !== "towns" && count === 1) { - document.getElementById("burgSelectGroup").selectedOptions[0].remove(); - document.getElementById("burgSelectGroup").options.add(new Option(group, group, false, true)); + byId("burgSelectGroup").selectedOptions[0].remove(); + byId("burgSelectGroup").options.add(new Option(group, group, false, true)); toggleNewGroupInput(); - document.getElementById("burgInputGroup").value = ""; + byId("burgInputGroup").value = ""; labelG.id = group; iconG.id = group; if (anchor) anchorG.id = group; @@ -217,9 +209,9 @@ function editBurg(id) { } // create new groups - document.getElementById("burgSelectGroup").options.add(new Option(group, group, false, true)); + byId("burgSelectGroup").options.add(new Option(group, group, false, true)); toggleNewGroupInput(); - document.getElementById("burgInputGroup").value = ""; + byId("burgInputGroup").value = ""; addBurgsGroup(group); moveBurgToGroup(id, group); @@ -300,7 +292,10 @@ function editBurg(id) { function changePopulation() { const id = +elSelected.attr("data-id"); + const burg = pack.burgs[id]; + pack.burgs[id].population = rn(burgPopulation.value / populationRate / urbanization, 4); + updateBurgPreview(burg); } function toggleFeature() { @@ -314,9 +309,9 @@ function editBurg(id) { if (burg[feature]) this.classList.remove("inactive"); else if (!burg[feature]) this.classList.add("inactive"); - if (burg.port) document.getElementById("burgEditAnchorStyle").style.display = "inline-block"; - else document.getElementById("burgEditAnchorStyle").style.display = "none"; - updateMFCGFrame(burg); + if (burg.port) byId("burgEditAnchorStyle").style.display = "inline-block"; + else byId("burgEditAnchorStyle").style.display = "none"; + updateBurgPreview(burg); } function toggleBurgLockButton() { @@ -331,22 +326,22 @@ function editBurg(id) { const id = +elSelected.attr("data-id"); const b = pack.burgs[id]; if (b.lock) { - document.getElementById("burgLock").classList.remove("icon-lock-open"); - document.getElementById("burgLock").classList.add("icon-lock"); + byId("burgLock").classList.remove("icon-lock-open"); + byId("burgLock").classList.add("icon-lock"); } else { - document.getElementById("burgLock").classList.remove("icon-lock"); - document.getElementById("burgLock").classList.add("icon-lock-open"); + byId("burgLock").classList.remove("icon-lock"); + byId("burgLock").classList.add("icon-lock-open"); } } function showStyleSection() { document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none")); - document.getElementById("burgStyleSection").style.display = "inline-block"; + byId("burgStyleSection").style.display = "inline-block"; } function hideStyleSection() { document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block")); - document.getElementById("burgStyleSection").style.display = "none"; + byId("burgStyleSection").style.display = "none"; } function editGroupLabelStyle() { @@ -364,39 +359,38 @@ function editBurg(id) { editStyle("anchors", g); } - function updateMFCGFrame(burg) { - const mfcgURL = getMFCGlink(burg); - document.getElementById("mfcgPreview").setAttribute("src", mfcgURL + "&preview=1"); - document.getElementById("mfcgLink").setAttribute("href", mfcgURL); + function updateBurgPreview(burg) { + const src = getBurgLink(burg) + "&preview=1"; + + // recreate object to force reload (Chrome bug) + const container = byId("burgPreviewObject"); + container.innerHTML = ""; + const object = document.createElement("object"); + object.style.width = "100%"; + object.data = src; + container.insertBefore(object, null); } - function changeSeed() { + function openBurgLink() { const id = +elSelected.attr("data-id"); const burg = pack.burgs[id]; - const burgSeed = +this.value; - burg.MFCG = burgSeed; - updateMFCGFrame(burg); + + openURL(getBurgLink(burg)); } - function randomizeSeed() { + function changeBurgLink() { const id = +elSelected.attr("data-id"); const burg = pack.burgs[id]; - const burgSeed = rand(1e9 - 1); - burg.MFCG = burgSeed; - updateMFCGFrame(burg); - document.getElementById("mfcgBurgSeed").value = burgSeed; - } - function addCustomMfcgLink() { - const id = +elSelected.attr("data-id"); - const burg = pack.burgs[id]; - const message = - "Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed"; - prompt(message, {default: burg.link || "", required: false}, link => { - if (link) burg.link = link; - else delete burg.link; - updateMFCGFrame(burg); - }); + prompt( + "Provide custom link to the burg map. It can be a link to Medieval Fantasy City Generator, a different tool, or just an image. Leave empty to use the default map", + {default: getBurgLink(burg), required: false}, + link => { + if (link) burg.link = link; + else delete burg.link; + updateBurgPreview(burg); + } + ); } function openEmblemEdit() { @@ -405,16 +399,16 @@ function editBurg(id) { editEmblem("burg", "burgCOA" + id, burg); } - function toggleMFCGMap() { - options.showMFCGMap = !options.showMFCGMap; - document.getElementById("mfcgPreviewSection").style.display = options.showMFCGMap ? "block" : "none"; - document.getElementById("burgToggleMFCGMap").className = options.showMFCGMap ? "icon-map" : "icon-map-o"; + function toggleBurgPreview() { + options.showBurgPreview = !options.showBurgPreview; + byId("burgPreviewSection").style.display = options.showBurgPreview ? "block" : "none"; + byId("burgTogglePreview").className = options.showBurgPreview ? "icon-map" : "icon-map-o"; } function toggleRelocateBurg() { - const toggler = document.getElementById("toggleCells"); - document.getElementById("burgRelocate").classList.toggle("pressed"); - if (document.getElementById("burgRelocate").classList.contains("pressed")) { + const toggler = byId("toggleCells"); + byId("burgRelocate").classList.toggle("pressed"); + if (byId("burgRelocate").classList.contains("pressed")) { viewbox.style("cursor", "crosshair").on("click", relocateBurgOnClick); tip("Click on map to relocate burg. Hold Shift for continuous move", true); if (!layerIsOn("toggleCells")) { @@ -534,7 +528,7 @@ function editBurg(id) { } function closeBurgEditor() { - document.getElementById("burgRelocate").classList.remove("pressed"); + byId("burgRelocate").classList.remove("pressed"); burgLabels.selectAll("text").call(d3.drag().on("drag", null)).classed("draggable", false); unselect(); } diff --git a/modules/ui/burgs-overview.js b/modules/ui/burgs-overview.js index d0ad25c2..561722ce 100644 --- a/modules/ui/burgs-overview.js +++ b/modules/ui/burgs-overview.js @@ -514,7 +514,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) { data += b.temple ? "temple," : ","; data += b.shanty ? "shanty town," : ","; data += b.coa ? JSON.stringify(b.coa).replace(/"/g, "").replace(/,/g, ";") + "," : ","; - data += getMFCGlink(b); + data += getBurgLink(b); data += "\n"; }); diff --git a/modules/ui/editors.js b/modules/ui/editors.js index 7ebed41d..58c1c18b 100644 --- a/modules/ui/editors.js +++ b/modules/ui/editors.js @@ -288,16 +288,20 @@ function togglePort(burg) { .attr("height", size); } -function getBurgSeed(burg) { - return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`); -} - -function getMFCGlink(burg) { +function getBurgLink(burg) { if (burg.link) return burg.link; + const population = burg.population * populationRate * urbanization; + if (population >= options.villageMaxPopulation || burg.citadel || burg.walls || burg.temple || burg.shanty) + return createMfcgLink(burg); + + return createVillageGeneratorLink(burg); +} + +function createMfcgLink(burg) { const {cells} = pack; const {i, name, population: burgPopulation, cell} = burg; - const seed = getBurgSeed(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); @@ -305,11 +309,19 @@ function getMFCGlink(burg) { const river = cells.r[cell] ? 1 : 0; const coast = Number(burg.port > 0); - const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : null; + const sea = (() => { + if (!coast || !cells.haven[cell]) return null; + + // 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 biome = cells.biome[cell]; const arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8]; - const farms = +arableBiomes.includes(biome); + const farms = +arableBiomes.includes(cells.biome[cell]); const citadel = +burg.citadel; const urban_castle = +(citadel && each(2)(i)); @@ -321,19 +333,12 @@ function getMFCGlink(burg) { const temple = +burg.temple; const shantytown = +burg.shanty; - function getSeaDirections(i) { - const p1 = cells.p[i]; - const p2 = cells.p[cells.haven[i]]; - 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); // 0 = south, 0.5 = west, 1 = north, 1.5 = east - } - - const parameters = { + const url = new URL("https://watabou.github.io/city-generator/"); + url.search = new URLSearchParams({ name, population, size, - seed, + seed: burgSeed, river, coast, farms, @@ -345,14 +350,60 @@ function getMFCGlink(burg) { walls, shantytown, gates: -1 - }; - const url = new URL("https://watabou.github.io/city-generator/"); - url.search = new URLSearchParams(parameters); + }); if (sea) url.searchParams.append("sea", sea); return url.toString(); } +function createVillageGeneratorLink(burg) { + const {cells, features} = pack; + const {i, population, cell} = burg; + + const pop = rn(population * populationRate * urbanization); + const burgSeed = seed + String(i).padStart(4, 0); + const tags = []; + + if (cells.r[cell] && cells.haven[cell]) tags.push("estuary"); + else if (cells.haven[cell] && features[cells.f[cell]].cells === 1) tags.push("island,district"); + else if (burg.port) tags.push("coast"); + else if (cells.conf[cell]) tags.push("confluence"); + else if (cells.r[cell]) tags.push("river"); + else if (pop < 200 && each(4)(cell)) tags.push("pond"); + + const roadsAround = cells.c[cell].filter(c => cells.h[c] >= 20 && cells.road[c]).length; + if (roadsAround > 1) tags.push("highway"); + else if (roadsAround === 1) tags.push("dead end"); + else tags.push("isolated"); + + const biome = cells.biome[cell]; + const arableBiomes = cells.r[cell] ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8]; + if (!arableBiomes.includes(biome)) tags.push("uncultivated"); + else if (each(6)(cell)) tags.push("farmland"); + + const temp = grid.cells.temp[cells.g[cell]]; + if (temp <= 0 || temp > 28 || (temp > 25 && each(3)(cell))) tags.push("no orchards"); + + if (!burg.plaza) tags.push("no square"); + + if (pop < 100) tags.push("sparse"); + else if (pop > 300) tags.push("dense"); + + const width = (() => { + if (pop > 1500) return 1600; + if (pop > 1000) return 1400; + if (pop > 500) return 1000; + if (pop > 200) return 800; + if (pop > 100) return 600; + return 400; + })(); + const height = rn(width / 2.2); + + const url = new URL("https://watabou.github.io/village-generator/"); + url.search = new URLSearchParams({pop, name: "", seed: burgSeed, width, height, tags}); + return url.toString(); +} + // draw legend box function drawLegend(name, data) { legend.selectAll("*").remove(); // fully redraw every time diff --git a/versioning.js b/versioning.js index bf163fcc..6b7cc11d 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.96.07"; // generator version, update each time +const version = "1.97.00"; // generator version, update each time { document.title += " v" + version; @@ -28,6 +28,7 @@ const version = "1.96.07"; // generator version, update each time
    Latest changes: +
  • Preview villages map
  • Ability to render ocean heightmap
  • Scale bar styling features
  • Vignette visual layer and vignette styling options