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

@ -32,7 +32,6 @@ function editBurg(id) {
byId("burgPopulation").on("change", changePopulation);
burgBody.querySelectorAll(".burgFeature").forEach(el => el.on("click", toggleFeature));
byId("burgLinkOpen").on("click", openBurgLink);
byId("burgLinkEdit").on("click", changeBurgLink);
byId("burgStyleShow").on("click", showStyleSection);
byId("burgStyleHide").on("click", hideStyleSection);
@ -41,7 +40,7 @@ function editBurg(id) {
byId("burgEditAnchorStyle").on("click", editGroupAnchorStyle);
byId("burgEmblem").on("click", openEmblemEdit);
byId("burgTogglePreview").on("click", toggleBurgPreview);
byId("burgSetPreviewLink").on("click", setCustomPreview);
byId("burgEditEmblem").on("click", openEmblemEdit);
byId("burgLocate").on("click", zoomIntoBurg);
byId("burgRelocate").on("click", toggleRelocateBurg);
@ -105,12 +104,7 @@ function editBurg(id) {
COArenderer.trigger(coaID, b.coa);
byId("burgEmblem").setAttribute("href", "#" + coaID);
if (options.showBurgPreview) {
byId("burgPreviewSection").style.display = "block";
updateBurgPreview(b);
} else {
byId("burgPreviewSection").style.display = "none";
}
updateBurgPreview(b);
}
function dragBurgLabel() {
@ -231,31 +225,37 @@ function editBurg(id) {
}
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)
const container = byId("burgPreviewObject");
container.innerHTML = "";
const object = document.createElement("object");
object.style.width = "100%";
object.data = src;
object.data = preview;
container.insertBefore(object, null);
}
function openBurgLink() {
const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];
openURL(getBurgLink(burg));
const link = Burgs.getPreview(burg).link;
if (link) openURL(link);
}
function changeBurgLink() {
function setCustomPreview() {
const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];
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},
"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: Burgs.getPreview(burg).link, required: false},
link => {
if (link) burg.link = link;
else delete burg.link;
@ -265,17 +265,11 @@ function editBurg(id) {
}
function openEmblemEdit() {
const id = +elSelected.attr("data-id"),
burg = pack.burgs[id];
const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];
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() {
const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];

View file

@ -61,6 +61,14 @@ function editBurgGroups() {
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="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 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>

View file

@ -423,7 +423,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
}
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
valid.forEach(b => {
@ -458,7 +458,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 += getBurgLink(b);
data += Burgs.getPreview(b).link;
data += "\n";
});

View file

@ -240,120 +240,6 @@ function togglePort(burg) {
.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
function drawLegend(name, data) {
legend.selectAll("*").remove(); // fully redraw every time