Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into urquhart-routes

This commit is contained in:
Azgaar 2024-06-19 19:00:07 +02:00
commit b30358859c
8 changed files with 210 additions and 137 deletions

View file

@ -223,18 +223,19 @@ function addBurgsGroup(group) {
}
function removeBurg(id) {
const label = document.querySelector("#burgLabels [data-id='" + id + "']");
const icon = document.querySelector("#burgIcons [data-id='" + id + "']");
const anchor = document.querySelector("#anchors [data-id='" + id + "']");
if (label) label.remove();
if (icon) icon.remove();
if (anchor) anchor.remove();
document.querySelector("#burgLabels [data-id='" + id + "']")?.remove();
document.querySelector("#burgIcons [data-id='" + id + "']")?.remove();
document.querySelector("#anchors [data-id='" + id + "']")?.remove();
const cells = pack.cells;
const burg = pack.burgs[id];
const cells = pack.cells,
burg = pack.burgs[id];
burg.removed = true;
cells.burg[burg.cell] = 0;
const noteId = notes.findIndex(note => note.id === `burg${id}`);
if (noteId !== -1) notes.splice(noteId, 1);
if (burg.coa) {
const coaId = "burgCOA" + id;
if (byId(coaId)) byId(coaId).remove();

View file

@ -312,10 +312,7 @@ function showElevationProfile(data, routeLen, isRiver) {
.attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset + 20) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "center")
.attr("transform", function (d) {
return "rotate(0)"; // used to rotate labels, - anti-clockwise, + clockwise
});
.style("text-anchor", "center");
chart
.append("g")

View file

@ -8,9 +8,10 @@ function editRegiment(selector) {
armies.selectAll(":scope > g > g").call(d3.drag().on("drag", dragRegiment));
elSelected = selector ? document.querySelector(selector) : d3.event.target.parentElement; // select g element
if (!pack.states[elSelected.dataset.state]) return;
if (!regiment()) return;
updateRegimentData(regiment());
if (!getRegiment()) return;
updateRegimentData(getRegiment());
drawBase();
drawRotationControl();
$("#regimentEditor").dialog({
title: "Edit Regiment",
@ -37,8 +38,8 @@ function editRegiment(selector) {
document.getElementById("regimentRemove").addEventListener("click", removeRegiment);
// get regiment data element
function regiment() {
return pack.states[elSelected.dataset.state].military.find(r => r.i == elSelected.dataset.id);
function getRegiment() {
return pack.states[elSelected.dataset.state]?.military.find(r => r.i == elSelected.dataset.id);
}
function updateRegimentData(regiment) {
@ -60,7 +61,7 @@ function editRegiment(selector) {
}
function drawBase() {
const reg = regiment();
const reg = getRegiment();
const clr = pack.states[elSelected.dataset.state].color;
const base = viewbox
.insert("g", "g#armies")
@ -69,12 +70,8 @@ function editRegiment(selector) {
.attr("stroke", "#000")
.attr("cursor", "move");
base
.on("mouseenter", () => {
tip("Regiment base. Drag to re-base the regiment", true);
})
.on("mouseleave", () => {
tip("", true);
});
.on("mouseenter", () => tip("Regiment base. Drag to re-base the regiment", true))
.on("mouseleave", () => tip("", true));
base
.append("line")
@ -92,8 +89,42 @@ function editRegiment(selector) {
.call(d3.drag().on("drag", dragBase));
}
function drawRotationControl() {
const reg = getRegiment();
const {x, width, y, height} = elSelected.getBBox();
debug
.append("circle")
.attr("id", "rotationControl")
.attr("cx", x + width)
.attr("cy", y + height / 2)
.attr("r", 1)
.attr("opacity", 1)
.attr("fill", "yellow")
.attr("stroke-width", 0.3)
.attr("stroke", "black")
.attr("cursor", "alias")
.attr("transform", `rotate(${reg.angle || 0})`)
.attr("transform-origin", `${reg.x}px ${reg.y}px`)
.on("mouseenter", () => tip("Drag to rotate the regiment", true))
.on("mouseleave", () => tip("", true))
.call(d3.drag().on("start", rotateRegiment));
}
function rotateRegiment() {
const reg = getRegiment();
d3.event.on("drag", function () {
const {x, y} = d3.event;
const angle = rn(Math.atan2(y - reg.y, x - reg.x) * (180 / Math.PI), 2);
elSelected.setAttribute("transform", `rotate(${angle})`);
this.setAttribute("transform", `rotate(${angle})`);
reg.angle = rn(angle, 2);
});
}
function changeType() {
const reg = regiment();
const reg = getRegiment();
reg.n = +!reg.n;
document.getElementById("regimentType").className = reg.n ? "icon-anchor" : "icon-users";
@ -110,11 +141,11 @@ function editRegiment(selector) {
}
function changeName() {
elSelected.dataset.name = regiment().name = this.value;
elSelected.dataset.name = getRegiment().name = this.value;
}
function restoreName() {
const reg = regiment(),
const reg = getRegiment(),
regs = pack.states[elSelected.dataset.state].military;
const name = Military.getName(reg, regs);
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
@ -129,12 +160,12 @@ function editRegiment(selector) {
function changeEmblem() {
const emblem = document.getElementById("regimentEmblem").value;
regiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem;
getRegiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem;
}
function changeUnit() {
const u = this.dataset.u;
const reg = regiment();
const reg = getRegiment();
reg.u[u] = +this.value || 0;
reg.a = d3.sum(Object.values(reg.u));
elSelected.querySelector("text").innerHTML = Military.getTotal(reg);
@ -143,7 +174,7 @@ function editRegiment(selector) {
}
function splitRegiment() {
const reg = regiment(),
const reg = getRegiment(),
u1 = reg.u;
const state = +elSelected.dataset.state,
military = pack.states[state].military;
@ -206,8 +237,7 @@ function editRegiment(selector) {
function addRegimentOnClick() {
const point = d3.mouse(this);
const cell = findCell(point[0], point[1]);
const x = pack.cells.p[cell][0],
y = pack.cells.p[cell][1];
const [x, y] = pack.cells.p[cell];
const state = +elSelected.dataset.state,
military = pack.states[state].military;
const i = military.length ? last(military).i + 1 : 0;
@ -254,7 +284,7 @@ function editRegiment(selector) {
return;
}
const attacker = regiment();
const attacker = getRegiment();
const defender = pack.states[regSelected.dataset.state].military.find(r => r.i == regSelected.dataset.id);
if (!attacker.a || !defender.a) {
tip("Regiment has no troops to battle", false, "error");
@ -322,7 +352,7 @@ function editRegiment(selector) {
return;
}
const reg = regiment(); // reg to be attached
const reg = getRegiment(); // reg to be attached
const sel = pack.states[newState].military.find(r => r.i == regSelected.dataset.id); // reg to attach to
for (const unit of options.military) {
@ -349,11 +379,11 @@ function editRegiment(selector) {
if (index != -1) notes.splice(index, 1);
const s = pack.states[elSelected.dataset.state];
Military.generateNote(regiment(), s);
Military.generateNote(getRegiment(), s);
}
function editLegend() {
editNotes(elSelected.id, regiment().name);
editNotes(elSelected.id, getRegiment().name);
}
function removeRegiment() {
@ -365,7 +395,7 @@ function editRegiment(selector) {
Remove: function () {
$(this).dialog("close");
const military = pack.states[elSelected.dataset.state].military;
const regIndex = military.indexOf(regiment());
const regIndex = military.indexOf(getRegiment());
if (regIndex === -1) return;
military.splice(regIndex, 1);
@ -392,8 +422,6 @@ function editRegiment(selector) {
const size = +armies.attr("box-size");
const w = reg.n ? size * 4 : size * 6;
const h = size * 2;
const x1 = x => rn(x - w / 2, 2);
const y1 = y => rn(y - size, 2);
const baseRect = this.querySelector("rect");
const text = this.querySelector("text");
@ -402,26 +430,37 @@ function editRegiment(selector) {
const self = elSelected === this;
const baseLine = viewbox.select("g#regimentBase > line");
const rotationControl = debug.select("#rotationControl");
d3.event.on("drag", function () {
const x = (reg.x = d3.event.x),
y = (reg.y = d3.event.y);
const {x, y} = d3.event;
reg.x = x;
reg.y = y;
const x1 = rn(x - w / 2, 2);
const y1 = rn(y - size, 2);
baseRect.setAttribute("x", x1(x));
baseRect.setAttribute("y", y1(y));
this.setAttribute("transform-origin", `${x}px ${y}px`);
baseRect.setAttribute("x", x1);
baseRect.setAttribute("y", y1);
text.setAttribute("x", x);
text.setAttribute("y", y);
iconRect.setAttribute("x", x1(x) - h);
iconRect.setAttribute("y", y1(y));
icon.setAttribute("x", x1(x) - size);
iconRect.setAttribute("x", x1 - h);
iconRect.setAttribute("y", y1);
icon.setAttribute("x", x1 - size);
icon.setAttribute("y", y);
if (self) baseLine.attr("x2", x).attr("y2", y);
if (self) {
baseLine.attr("x2", x).attr("y2", y);
rotationControl
.attr("cx", x1 + w)
.attr("cy", y)
.attr("transform-origin", `${x}px ${y}px`);
}
});
}
function dragBase() {
const baseLine = viewbox.select("g#regimentBase > line");
const reg = regiment();
const reg = getRegiment();
d3.event.on("drag", function () {
this.setAttribute("cx", d3.event.x);
@ -436,9 +475,10 @@ function editRegiment(selector) {
}
function closeEditor() {
debug.selectAll("*").remove();
viewbox.selectAll("g#regimentBase").remove();
armies.selectAll(":scope > g").classed("draggable", false);
armies.selectAll("g>g").call(d3.drag().on("drag", null));
viewbox.selectAll("g#regimentBase").remove();
document.getElementById("regimentAdd").classList.remove("pressed");
document.getElementById("regimentAttack").classList.remove("pressed");
document.getElementById("regimentAttach").classList.remove("pressed");

View file

@ -336,29 +336,44 @@ function regenerateProvinces() {
}
function regenerateBurgs() {
const {cells, states} = pack;
const lockedburgs = pack.burgs.filter(b => b.i && !b.removed && b.lock);
const {cells, features, burgs, states, provinces} = pack;
rankCells();
cells.burg = new Uint16Array(cells.i.length);
const burgs = (pack.burgs = [0]); // clear burgs array
states.filter(s => s.i).forEach(s => (s.capital = 0)); // clear state capitals
pack.provinces.filter(p => p.i).forEach(p => (p.burg = 0)); // clear province capitals
// remove notes for unlocked burgs
notes = notes.filter(note => {
if (note.id.startsWith("burg")) {
const burgId = +note.id.slice(4);
return burgs[burgId]?.lock;
}
return true;
});
const newBurgs = [0]; // new burgs array
const burgsTree = d3.quadtree();
// add locked burgs
cells.burg = new Uint16Array(cells.i.length); // clear cells burg data
states.filter(s => s.i).forEach(s => (s.capital = 0)); // clear state capitals
provinces.filter(p => p.i).forEach(p => (p.burg = 0)); // clear province capitals
// readd locked burgs
const lockedburgs = burgs.filter(burg => burg.i && !burg.removed && burg.lock);
for (let j = 0; j < lockedburgs.length; j++) {
const id = burgs.length;
const lockedBurg = lockedburgs[j];
lockedBurg.i = id;
burgs.push(lockedBurg);
const newId = newBurgs.length;
const noteIndex = notes.findIndex(note => note.id === `burg${lockedBurg.i}`);
if (noteIndex !== -1) notes[noteIndex].id = `burg${newId}`;
lockedBurg.i = newId;
newBurgs.push(lockedBurg);
burgsTree.add([lockedBurg.x, lockedBurg.y]);
cells.burg[lockedBurg.cell] = id;
cells.burg[lockedBurg.cell] = newId;
if (lockedBurg.capital) {
const stateId = lockedBurg.state;
states[stateId].capital = id;
states[stateId].capital = newId;
states[stateId].center = lockedBurg.cell;
}
}
@ -371,8 +386,8 @@ function regenerateBurgs() {
existingStatesCount;
const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** 0.7 / 66); // base min distance between towns
for (let i = 0; i < sorted.length && burgs.length < burgsCount; i++) {
const id = burgs.length;
for (let i = 0; i < sorted.length && newBurgs.length < burgsCount; i++) {
const id = newBurgs.length;
const cell = sorted[i];
const [x, y] = cells.p[cell];
@ -388,24 +403,27 @@ function regenerateBurgs() {
const culture = cells.culture[cell];
const name = Names.getCulture(culture);
burgs.push({cell, x, y, state: stateId, i: id, culture, name, capital, feature: cells.f[cell]});
newBurgs.push({cell, x, y, state: stateId, i: id, culture, name, capital, feature: cells.f[cell]});
burgsTree.add([x, y]);
cells.burg[cell] = id;
}
pack.burgs = newBurgs; // assign new burgs array
// add a capital at former place for states without added capitals
states
.filter(s => s.i && !s.removed && !s.capital)
.forEach(s => {
const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg
s.capital = burg;
s.center = pack.burgs[burg].cell;
pack.burgs[burg].capital = 1;
pack.burgs[burg].state = s.i;
moveBurgToGroup(burg, "cities");
const [x, y] = cells.p[s.center];
const burgId = addBurg([x, y]);
s.capital = burgId;
s.center = pack.burgs[burgId].cell;
pack.burgs[burgId].capital = 1;
pack.burgs[burgId].state = s.i;
moveBurgToGroup(burgId, "cities");
});
pack.features.forEach(f => {
features.forEach(f => {
if (f.port) f.port = 0; // reset features ports counter
});

View file

@ -3,7 +3,7 @@
function editZones() {
closeDialogs();
if (!layerIsOn("toggleZones")) toggleZones();
const body = document.getElementById("zonesBodySection");
const body = byId("zonesBodySection");
updateFilters();
zonesEditorAddLines();
@ -20,20 +20,20 @@ function editZones() {
});
// add listeners
document.getElementById("zonesFilterType").addEventListener("click", updateFilters);
document.getElementById("zonesFilterType").addEventListener("change", filterZonesByType);
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
document.getElementById("zonesPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("zonesManually").addEventListener("click", enterZonesManualAssignent);
document.getElementById("zonesManuallyApply").addEventListener("click", applyZonesManualAssignent);
document.getElementById("zonesManuallyCancel").addEventListener("click", cancelZonesManualAssignent);
document.getElementById("zonesAdd").addEventListener("click", addZonesLayer);
document.getElementById("zonesExport").addEventListener("click", downloadZonesData);
document.getElementById("zonesRemove").addEventListener("click", toggleEraseMode);
byId("zonesFilterType").on("click", updateFilters);
byId("zonesFilterType").on("change", filterZonesByType);
byId("zonesEditorRefresh").on("click", zonesEditorAddLines);
byId("zonesEditStyle").on("click", () => editStyle("zones"));
byId("zonesLegend").on("click", toggleLegend);
byId("zonesPercentage").on("click", togglePercentageMode);
byId("zonesManually").on("click", enterZonesManualAssignent);
byId("zonesManuallyApply").on("click", applyZonesManualAssignent);
byId("zonesManuallyCancel").on("click", cancelZonesManualAssignent);
byId("zonesAdd").on("click", addZonesLayer);
byId("zonesExport").on("click", downloadZonesData);
byId("zonesRemove").on("click", toggleEraseMode);
body.addEventListener("click", function (ev) {
body.on("click", function (ev) {
const el = ev.target,
cl = el.classList,
zone = el.parentNode.dataset.id;
@ -45,7 +45,7 @@ function editZones() {
if (customization) selectZone(el);
});
body.addEventListener("input", function (ev) {
body.on("input", function (ev) {
const el = ev.target;
const zone = zones.select("#" + el.parentNode.dataset.id);
@ -58,7 +58,7 @@ function editZones() {
const zones = Array.from(document.querySelectorAll("#zones > g"));
const types = unique(zones.map(zone => zone.dataset.type));
const filterSelect = document.getElementById("zonesFilterType");
const filterSelect = byId("zonesFilterType");
const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
filterSelect.innerHTML =
@ -70,7 +70,7 @@ function editZones() {
function zonesEditorAddLines() {
const unit = " " + getAreaUnit();
const typeToFilterBy = document.getElementById("zonesFilterType").value;
const typeToFilterBy = byId("zonesFilterType").value;
const zones = Array.from(document.querySelectorAll("#zones > g"));
const filteredZones = typeToFilterBy === "all" ? zones : zones.filter(zone => zone.dataset.type === typeToFilterBy);
@ -127,8 +127,8 @@ function editZones() {
zonesFooterPopulation.innerHTML = si(totalPop);
// add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => zoneHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => zoneHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", ev => zoneHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.on("mouseleave", ev => zoneHighlightOff(ev)));
if (body.dataset.type === "percentage") {
body.dataset.type = "absolute";
@ -182,7 +182,7 @@ function editZones() {
if (!layerIsOn("toggleZones")) toggleZones();
customization = 10;
document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "none"));
document.getElementById("zonesManuallyButtons").style.display = "inline-block";
byId("zonesManuallyButtons").style.display = "inline-block";
zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
zonesFooter.style.display = "none";
@ -215,24 +215,27 @@ function editZones() {
}
function dragZoneBrush() {
const r = +zonesBrush.value;
const radius = +byId("zonesBrush").value;
const eraseMode = byId("zonesRemove").classList.contains("pressed");
const landOnly = byId("zonesBrushLandOnly").checked;
const selected = body.querySelector("div.selected");
const zone = zones.select("#" + selected.dataset.id);
const base = zone.attr("id") + "_"; // id generic part
d3.event.on("drag", () => {
if (!d3.event.dx && !d3.event.dy) return;
const p = d3.mouse(this);
moveCircle(p[0], p[1], r);
const [x, y] = d3.mouse(this);
moveCircle(x, y, radius);
const selection = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1])];
let selection = radius > 5 ? findAll(x, y, radius) : [findCell(x, y, radius)];
if (landOnly) selection = selection.filter(i => pack.cells.h[i] >= 20);
if (!selection) return;
const selected = body.querySelector("div.selected");
const zone = zones.select("#" + selected.dataset.id);
const base = zone.attr("id") + "_"; // id generic part
const dataCells = zone.attr("data-cells");
let cells = dataCells ? dataCells.split(",").map(i => +i) : [];
const erase = document.getElementById("zonesRemove").classList.contains("pressed");
if (erase) {
if (eraseMode) {
// remove
selection.forEach(i => {
const index = cells.indexOf(i);
@ -300,7 +303,7 @@ function editZones() {
customization = 0;
removeCircle();
document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "inline-block"));
document.getElementById("zonesManuallyButtons").style.display = "none";
byId("zonesManuallyButtons").style.display = "none";
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
zonesFooter.style.display = "block";
@ -321,7 +324,7 @@ function editZones() {
const fill = el.getAttribute("fill");
const callback = newFill => {
el.fill = newFill;
document.getElementById(el.parentNode.dataset.id).setAttribute("fill", newFill);
byId(el.parentNode.dataset.id).setAttribute("fill", newFill);
};
openPicker(fill, callback);