feat: burg group editor - map preview

This commit is contained in:
Azgaar 2024-10-11 11:29:41 +02:00
parent 1125192b72
commit 6526c4c1f1
7 changed files with 192 additions and 194 deletions

View file

@ -3467,12 +3467,6 @@
<div style="display: flex; justify-content: space-between"> <div style="display: flex; justify-content: space-between">
<span>Burg preview:</span> <span>Burg preview:</span>
<div style="display: flex; gap: 0.5em"> <div style="display: flex; gap: 0.5em">
<i
id="burgLinkEdit"
data-tip="Provide custom link to the burg map"
class="icon-pencil pointer"
style="margin-top: -0.1em"
></i>
<i id="burgLinkOpen" data-tip="Open burg map in a new tab" class="icon-link-ext pointer"></i> <i id="burgLinkOpen" data-tip="Open burg map in a new tab" class="icon-link-ext pointer"></i>
</div> </div>
</div> </div>
@ -3502,7 +3496,7 @@
</div> </div>
<button id="burgEditEmblem" data-tip="Edit emblem" class="icon-shield-alt"></button> <button id="burgEditEmblem" data-tip="Edit emblem" class="icon-shield-alt"></button>
<button id="burgTogglePreview" data-tip="Toggle preview" class="icon-map"></button> <button id="burgSetPreviewLink" data-tip="Set custom burg map URL" class="icon-map-o"></button>
<button id="burgLocate" data-tip="Zoom map and center view in the burg" class="icon-target"></button> <button id="burgLocate" data-tip="Zoom map and center view in the burg" class="icon-target"></button>
<button id="burgRelocate" data-tip="Relocate burg. Click on map to move the burg" class="icon-map-pin"></button> <button id="burgRelocate" data-tip="Relocate burg. Click on map to move the burg" class="icon-map-pin"></button>
<button id="burglLegend" data-tip="Edit free text notes (legend) for this burg" class="icon-edit"></button> <button id="burglLegend" data-tip="Edit free text notes (legend) for this burg" class="icon-edit"></button>
@ -5340,30 +5334,13 @@
</div> </div>
<div id="burgGroupsEditor" class="dialog stable" style="display: none"> <div id="burgGroupsEditor" class="dialog stable" style="display: none">
<form id="burgGroupsForm" class="table"> <form id="burgGroupsForm">
<table> <table class="table">
<colgroup>
<col style="width: 1em" />
<col style="width: 6em" />
<col style="width: 4em" />
<col style="width: 4em" />
<col style="width: 4em" />
<col style="width: 4em" />
<col style="width: 4em" />
<col style="width: 4em" />
<col style="width: 4em" />
<col style="width: 4em" />
<col style="width: 1em" />
<col style="width: 1em" />
<col style="width: 1em" />
<col style="width: 1em" />
<col style="width: 1em" />
<col style="width: 1em" />
</colgroup>
<thead> <thead>
<tr> <tr>
<th data-tip="Rendering order: higher values are rendered on top">Order</th> <th data-tip="Rendering order: higher values are rendered on top">Order</th>
<th data-tip="Type group name">Name</th> <th data-tip="Type group name">Name</th>
<th data-tip="Burg preview generator">Preview generator</th>
<th data-tip="Set min population constraint" colspan="2">Population</th> <th data-tip="Set min population constraint" colspan="2">Population</th>
<th data-tip="Set population percentile: 0-100, where 90 means the burg must have a population higher than 90% of all burgs">Percentile</th> <th data-tip="Set population percentile: 0-100, where 90 means the burg must have a population higher than 90% of all burgs">Percentile</th>
<th data-tip="Select allowed biomes">Biomes</th> <th data-tip="Select allowed biomes">Biomes</th>

View file

@ -261,23 +261,21 @@ window.Burgs = (() => {
} }
const getDefaultGroups = () => [ const getDefaultGroups = () => [
{name: "capitals", active: true, order: 9, features: {capital: true}, preview: "watabou-city-generator"}, {name: "capitals", active: true, order: 9, features: {capital: true}, preview: "watabou-city"},
{name: "cities", active: true, order: 8, percentile: 90, min: 5, preview: "watabou-city-generator"}, {name: "cities", active: true, order: 8, percentile: 90, min: 5, preview: "watabou-city"},
{ {
name: "forts", name: "forts",
active: true, active: true,
features: {citadel: true, walls: false, plaza: false, port: false}, features: {citadel: true, walls: false, plaza: false, port: false},
order: 6, order: 6,
max: 1, max: 1
preview: null
}, },
{ {
name: "monasteries", name: "monasteries",
active: true, active: true,
features: {temple: true, walls: false, plaza: false, port: false}, features: {temple: true, walls: false, plaza: false, port: false},
order: 5, order: 5,
max: 0.8, max: 0.8
preview: null
}, },
{ {
name: "caravanserais", name: "caravanserais",
@ -285,8 +283,7 @@ window.Burgs = (() => {
features: {port: false, plaza: true}, features: {port: false, plaza: true},
order: 4, order: 4,
max: 0.8, max: 0.8,
biomes: [1, 2, 3], biomes: [1, 2, 3]
preview: null
}, },
{ {
name: "trading_posts", name: "trading_posts",
@ -295,7 +292,7 @@ window.Burgs = (() => {
features: {plaza: true}, features: {plaza: true},
max: 0.8, max: 0.8,
biomes: [5, 6, 7, 8, 9, 10, 11, 12], biomes: [5, 6, 7, 8, 9, 10, 11, 12],
preview: null preview: "watabou-dwelling"
}, },
{ {
name: "villages", name: "villages",
@ -304,7 +301,7 @@ window.Burgs = (() => {
min: 0.1, min: 0.1,
max: 2, max: 2,
features: {walls: false}, features: {walls: false},
preview: "watabou-village-generator" preview: "watabou-village"
}, },
{ {
name: "hamlets", name: "hamlets",
@ -312,9 +309,9 @@ window.Burgs = (() => {
order: 1, order: 1,
features: {walls: false, plaza: false}, features: {walls: false, plaza: false},
max: 0.1, max: 0.1,
preview: "watabou-village-generator" preview: "watabou-village"
}, },
{name: "towns", active: true, order: 7, isDefault: true, preview: "watabou-city-generator"} {name: "towns", active: true, order: 7, isDefault: true, preview: "watabou-city"}
]; ];
function defineGroup(burg, populations) { function defineGroup(burg, populations) {
@ -359,6 +356,146 @@ window.Burgs = (() => {
} }
} }
const previewGeneratorsMap = {
"watabou-city": createWatabouCityLinks,
"watabou-village": createWatabouVillageLinks,
"watabou-dwelling": createWatabouDwellingLinks
};
function getPreview(burg) {
if (burg.link) return {link: burg.link, preview: burg.link};
const group = options.burgs.groups.find(g => g.name === burg.group);
if (!group?.preview || !previewGeneratorsMap[group.preview]) return {link: null, preview: null};
return previewGeneratorsMap[group.preview](burg);
}
function createWatabouCityLinks(burg) {
const cells = pack.cells;
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 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;
// 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 arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
const farms = +arableBiomes.includes(cells.biome[cell]);
const citadel = +burg.citadel;
const urban_castle = +(citadel && each(2)(i));
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({
name,
population,
size,
seed: burgSeed,
river,
coast,
farms,
citadel,
urban_castle,
hub,
plaza,
temple,
walls,
shantytown,
gates: -1
});
if (sea) url.searchParams.append("sea", sea);
const link = url.toString();
return {link, preview: link + "&preview=1"};
}
function createWatabouVillageLinks(burg) {
const {cells, features} = pack;
const {i, population, cell} = burg;
const burgSeed = seed + String(i).padStart(4, 0);
const pop = rn(population * populationRate * urbanization);
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 connectivityRate = Routes.getConnectivityRate(cell);
tags.push(connectivityRate > 1 ? "highway" : connectivityRate === 1 ? "dead end" : "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.05);
const url = new URL("https://watabou.github.io/village-generator/");
url.search = new URLSearchParams({pop, name: "", seed: burgSeed, width, height, tags});
const link = url.toString();
return {link, preview: link + "&preview=1"};
}
function createWatabouDwellingLinks(burg) {
const burgSeed = seed + String(burg.i).padStart(4, 0);
const pop = rn(burg.population * populationRate * urbanization);
const tags = (() => {
if (pop > 200) return ["large", "tall"];
if (pop > 100) return ["large"];
if (pop > 50) return ["tall"];
if (pop > 20) return ["low"];
return ["small"];
})();
const url = new URL("https://watabou.github.io/dwellings/");
url.search = new URLSearchParams({pop, name: "", seed: burgSeed, tags});
const link = url.toString();
return {link, preview: link + "&preview=1"};
}
function add([x, y]) { function add([x, y]) {
const {cells} = pack; const {cells} = pack;
@ -410,5 +547,5 @@ window.Burgs = (() => {
return burgId; return burgId;
} }
return {generate, getDefaultGroups, specify, defineGroup, getType, add}; return {generate, getDefaultGroups, specify, defineGroup, getPreview, getType, add};
})(); })();

View file

@ -830,21 +830,6 @@ export function resolveVersionConflicts(mapVersion) {
}); });
} }
if (isOlderThan("1.97.0")) {
// v1.97.00 changed MFCG link to an arbitrary preview URL
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;
}
});
}
if (isOlderThan("1.98.0")) { if (isOlderThan("1.98.0")) {
// v1.98.00 changed compass layer and rose element id // v1.98.00 changed compass layer and rose element id
const rose = compass.select("use"); const rose = compass.select("use");
@ -966,6 +951,17 @@ export function resolveVersionConflicts(mapVersion) {
groups: groups.map(name => ({name, active: true, preview: null})) groups: groups.map(name => ({name, active: true, preview: null}))
}; };
pack.burgs.forEach(burg => {
if (!burg.i || burg.removed) return;
if (burg.MFCG) {
burg.link = getBurgLink(burg);
delete burg.MFCG;
}
});
delete options.showBurgPreview;
delete options.showMFCGMap;
delete options.villageMaxPopulation; delete options.villageMaxPopulation;
} }
} }

View file

@ -32,7 +32,6 @@ function editBurg(id) {
byId("burgPopulation").on("change", changePopulation); byId("burgPopulation").on("change", changePopulation);
burgBody.querySelectorAll(".burgFeature").forEach(el => el.on("click", toggleFeature)); burgBody.querySelectorAll(".burgFeature").forEach(el => el.on("click", toggleFeature));
byId("burgLinkOpen").on("click", openBurgLink); byId("burgLinkOpen").on("click", openBurgLink);
byId("burgLinkEdit").on("click", changeBurgLink);
byId("burgStyleShow").on("click", showStyleSection); byId("burgStyleShow").on("click", showStyleSection);
byId("burgStyleHide").on("click", hideStyleSection); byId("burgStyleHide").on("click", hideStyleSection);
@ -41,7 +40,7 @@ function editBurg(id) {
byId("burgEditAnchorStyle").on("click", editGroupAnchorStyle); byId("burgEditAnchorStyle").on("click", editGroupAnchorStyle);
byId("burgEmblem").on("click", openEmblemEdit); byId("burgEmblem").on("click", openEmblemEdit);
byId("burgTogglePreview").on("click", toggleBurgPreview); byId("burgSetPreviewLink").on("click", setCustomPreview);
byId("burgEditEmblem").on("click", openEmblemEdit); byId("burgEditEmblem").on("click", openEmblemEdit);
byId("burgLocate").on("click", zoomIntoBurg); byId("burgLocate").on("click", zoomIntoBurg);
byId("burgRelocate").on("click", toggleRelocateBurg); byId("burgRelocate").on("click", toggleRelocateBurg);
@ -105,12 +104,7 @@ function editBurg(id) {
COArenderer.trigger(coaID, b.coa); COArenderer.trigger(coaID, b.coa);
byId("burgEmblem").setAttribute("href", "#" + coaID); byId("burgEmblem").setAttribute("href", "#" + coaID);
if (options.showBurgPreview) {
byId("burgPreviewSection").style.display = "block";
updateBurgPreview(b); updateBurgPreview(b);
} else {
byId("burgPreviewSection").style.display = "none";
}
} }
function dragBurgLabel() { function dragBurgLabel() {
@ -231,31 +225,37 @@ function editBurg(id) {
} }
function updateBurgPreview(burg) { function updateBurgPreview(burg) {
const src = getBurgLink(burg) + "&preview=1"; const preview = Burgs.getPreview(burg).preview;
if (!preview) {
byId("burgPreviewSection").style.display = "none";
return;
}
byId("burgPreviewSection").style.display = "block";
// recreate object to force reload (Chrome bug) // recreate object to force reload (Chrome bug)
const container = byId("burgPreviewObject"); const container = byId("burgPreviewObject");
container.innerHTML = ""; container.innerHTML = "";
const object = document.createElement("object"); const object = document.createElement("object");
object.style.width = "100%"; object.style.width = "100%";
object.data = src; object.data = preview;
container.insertBefore(object, null); container.insertBefore(object, null);
} }
function openBurgLink() { function openBurgLink() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const burg = pack.burgs[id]; const burg = pack.burgs[id];
const link = Burgs.getPreview(burg).link;
openURL(getBurgLink(burg)); if (link) openURL(link);
} }
function changeBurgLink() { function setCustomPreview() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const burg = pack.burgs[id]; const burg = pack.burgs[id];
prompt( 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", "Provide custom URL to the burg map. It can be a link to a generator or just an image. Leave empty to use the default map preview",
{default: getBurgLink(burg), required: false}, {default: Burgs.getPreview(burg).link, required: false},
link => { link => {
if (link) burg.link = link; if (link) burg.link = link;
else delete burg.link; else delete burg.link;
@ -265,17 +265,11 @@ function editBurg(id) {
} }
function openEmblemEdit() { function openEmblemEdit() {
const id = +elSelected.attr("data-id"), const id = +elSelected.attr("data-id");
burg = pack.burgs[id]; const burg = pack.burgs[id];
editEmblem("burg", "burgCOA" + id, burg); editEmblem("burg", "burgCOA" + id, burg);
} }
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 zoomIntoBurg() { function zoomIntoBurg() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const burg = pack.burgs[id]; const burg = pack.burgs[id];

View file

@ -61,6 +61,14 @@ function editBurgGroups() {
return /* html */ `<tr name="${group.name}"> return /* html */ `<tr name="${group.name}">
<td data-tip="Rendering order: higher values are rendered on top"><input type="number" name="order" min="1" max="999" step="1" required value="${group.order || ''}" /></td> <td data-tip="Rendering order: higher values are rendered on top"><input type="number" name="order" min="1" max="999" step="1" required value="${group.order || ''}" /></td>
<td data-tip="Type group name. It can contain only text, digits and underscore"><input type="text" name="name" value="${group.name}" required pattern="\\w+" /></td> <td data-tip="Type group name. It can contain only text, digits and underscore"><input type="text" name="name" value="${group.name}" required pattern="\\w+" /></td>
<td data-tip="Burg preview generator">
<select name="preview">
<option value="" ${!group.preview ? "selected" : ""}>no</option>
<option value="watabou-city" ${group.preview === "watabou-city" ? "selected" : ""}>Watabou City</option>
<option value="watabou-village" ${group.preview === "watabou-village" ? "selected" : ""}>Watabou Village</option>
<option value="watabou-dwelling" ${group.preview === "watabou-dwellings" ? "selected" : ""}>Watabou Dwelling</option>
</select>
</td>
<td data-tip="Set min population constraint"><input type="number" name="min" min="0" step="any" value="${group.min || ''}" /></td> <td data-tip="Set min population constraint"><input type="number" name="min" min="0" step="any" value="${group.min || ''}" /></td>
<td data-tip="Set max population constraint"><input type="number" name="max" min="0" step="any" value="${group.max || ''}" /></td> <td data-tip="Set max population constraint"><input type="number" name="max" min="0" step="any" value="${group.max || ''}" /></td>
<td data-tip="Set population percentile"><input type="number" name="percentile" min="0" max="100" step="any" value="${group.percentile || ''}" /></td> <td data-tip="Set population percentile"><input type="number" name="percentile" min="0" max="100" step="any" value="${group.percentile || ''}" /></td>

View file

@ -423,7 +423,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
} }
function downloadBurgsData() { function downloadBurgsData() {
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Group,Population,X,Y,Latitude,Longitude,Elevation (${heightUnit.value}),Temperature,Temperature likeness,Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town,Emblem,City Generator Link\n`; // headers let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Group,Population,X,Y,Latitude,Longitude,Elevation (${heightUnit.value}),Temperature,Temperature likeness,Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town,Emblem,Preview link\n`; // headers
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
valid.forEach(b => { valid.forEach(b => {
@ -458,7 +458,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
data += b.temple ? "temple," : ","; data += b.temple ? "temple," : ",";
data += b.shanty ? "shanty town," : ","; data += b.shanty ? "shanty town," : ",";
data += b.coa ? JSON.stringify(b.coa).replace(/"/g, "").replace(/,/g, ";") + "," : ","; data += b.coa ? JSON.stringify(b.coa).replace(/"/g, "").replace(/,/g, ";") + "," : ",";
data += getBurgLink(b); data += Burgs.getPreview(b).link;
data += "\n"; data += "\n";
}); });

View file

@ -240,120 +240,6 @@ function togglePort(burg) {
.attr("height", size); .attr("height", size);
} }
// TODO: rework this function to use the new data structure
function getBurgLink(burg) {
if (burg.link) return burg.link;
if (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 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 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;
// 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 arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
const farms = +arableBiomes.includes(cells.biome[cell]);
const citadel = +burg.citadel;
const urban_castle = +(citadel && each(2)(i));
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({
name,
population,
size,
seed: burgSeed,
river,
coast,
farms,
citadel,
urban_castle,
hub,
plaza,
temple,
walls,
shantytown,
gates: -1
});
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 roadsNumber = Object.values(pack.cells.routes[cell] || {}).filter(routeId => {
const route = pack.routes.find(route => route.i === routeId);
if (!route) return false;
return route.group === "roads" || route.group === "trails";
}).length;
tags.push(roadsNumber > 1 ? "highway" : roadsNumber === 1 ? "dead end" : "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 // 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