diff --git a/main.js b/main.js
index 9753d659..3401212a 100644
--- a/main.js
+++ b/main.js
@@ -435,6 +435,7 @@ function showWelcomeMessage() {
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version ${version}.
This version is compatible with ${changelog}, loaded .map files will be auto-updated.
Latest changes:
+
Zones editor: fitler by type
Color picker: new hatchings
New style presets: Cyberpunk and Atlas
Burg temperature graph
@@ -1504,14 +1505,12 @@ function rankCells() {
TIME && console.timeEnd("rankCells");
}
-// regenerate some zones
+// generate zones
function addZones(number = 1) {
TIME && console.time("addZones");
- const data = [],
- cells = pack.cells,
- states = pack.states,
- burgs = pack.burgs;
+ const {cells, states, burgs} = pack;
const used = new Uint8Array(cells.i.length); // to store used cells
+ const zonesData = [];
for (let i = 0; i < rn(Math.random() * 1.8 * number); i++) addInvasion(); // invasion of enemy lands
for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addRebels(); // rebels along a state border
@@ -1525,6 +1524,8 @@ function addZones(number = 1) {
for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addFlood(); // flood on river banks
for (let i = 0; i < rn(Math.random() * 1.2 * number); i++) addTsunami(); // tsunami starting near coast
+ drawZones();
+
function addInvasion() {
const atWar = states.filter(s => s.diplomacy && s.diplomacy.some(d => d === "Enemy"));
if (!atWar.length) return;
@@ -1565,7 +1566,7 @@ function addZones(number = 1) {
Intervention: 1
});
const name = getAdjective(invader.name) + " " + invasion;
- data.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"});
+ zonesData.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"});
}
function addRebels() {
@@ -1594,7 +1595,7 @@ function addZones(number = 1) {
const rebels = rw({Rebels: 5, Insurgents: 2, Mutineers: 1, Rioters: 1, Separatists: 1, Secessionists: 1, Insurrection: 2, Rebellion: 1, Conspiracy: 2});
const name = getAdjective(states[neib].name) + " " + rebels;
- data.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"});
+ zonesData.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"});
}
function addProselytism() {
@@ -1624,7 +1625,7 @@ function addZones(number = 1) {
}
const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism";
- data.push({name, type: "Proselytism", cells: cellsArray, fill: "url(#hatch6)"});
+ zonesData.push({name, type: "Proselytism", cells: cellsArray, fill: "url(#hatch6)"});
}
function addCrusade() {
@@ -1636,7 +1637,7 @@ function addZones(number = 1) {
cellsArray.forEach(i => (used[i] = 1));
const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade";
- data.push({name, type: "Crusade", cells: cellsArray, fill: "url(#hatch6)"});
+ zonesData.push({name, type: "Crusade", cells: cellsArray, fill: "url(#hatch6)"});
}
function addDisease() {
@@ -1673,7 +1674,7 @@ function addZones(number = 1) {
const type = rw({Fever: 5, Pestilence: 2, Flu: 2, Pox: 2, Smallpox: 2, Plague: 4, Cholera: 2, Dropsy: 1, Leprosy: 2});
const name = rw({[color()]: 4, [animal()]: 2, [adjective()]: 1}) + " " + type;
- data.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
+ zonesData.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
}
function addDisaster() {
@@ -1705,7 +1706,7 @@ function addZones(number = 1) {
const type = rw({Famine: 5, Dearth: 1, Drought: 3, Earthquake: 3, Tornadoes: 1, Wildfires: 1});
const name = getAdjective(burg.name) + " " + type;
- data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
+ zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
}
function addEruption() {
@@ -1736,7 +1737,7 @@ function addZones(number = 1) {
});
}
- data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch7)"});
+ zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch7)"});
}
function addAvalanche() {
@@ -1761,7 +1762,7 @@ function addZones(number = 1) {
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Avalanche";
- data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
+ zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
}
function addFault() {
@@ -1786,7 +1787,7 @@ function addZones(number = 1) {
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Fault";
- data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch2)"});
+ zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch2)"});
}
function addFlood() {
@@ -1816,7 +1817,7 @@ function addZones(number = 1) {
}
const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood";
- data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
+ zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
}
function addTsunami() {
@@ -1844,13 +1845,13 @@ function addZones(number = 1) {
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Tsunami";
- data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
+ zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
}
- void (function drawZones() {
+ function drawZones() {
zones
.selectAll("g")
- .data(data)
+ .data(zonesData)
.enter()
.append("g")
.attr("id", (d, i) => "zone" + i)
@@ -1866,7 +1867,7 @@ function addZones(number = 1) {
.attr("id", function (d) {
return this.parentNode.id + "_" + d;
});
- })();
+ }
TIME && console.timeEnd("addZones");
}
diff --git a/modules/load.js b/modules/load.js
index a170e056..a8893592 100644
--- a/modules/load.js
+++ b/modules/load.js
@@ -940,6 +940,12 @@ function parseLoadedData(data) {
if (version < 1.73) {
// v1.73 moved the hatching patterns out of the user's SVG
document.getElementById("hatching")?.remove();
+
+ // v1.73 added zone type to UI, ensure type is populated
+ const zones = Array.from(document.querySelectorAll("#zones > g"));
+ zones.forEach(zone => {
+ if (!zone.dataset.type) zone.dataset.type = "Unknown";
+ });
}
})();
diff --git a/modules/ui/zones-editor.js b/modules/ui/zones-editor.js
index 13e8b31b..122ea083 100644
--- a/modules/ui/zones-editor.js
+++ b/modules/ui/zones-editor.js
@@ -4,6 +4,8 @@ function editZones() {
closeDialogs();
if (!layerIsOn("toggleZones")) toggleZones();
const body = document.getElementById("zonesBodySection");
+
+ updateFilters();
zonesEditorAddLines();
if (modules.editZones) return;
@@ -18,6 +20,8 @@ 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);
@@ -38,37 +42,55 @@ function editZones() {
else if (cl.contains("icon-trash-empty")) zoneRemove(zone);
else if (cl.contains("icon-eye")) toggleVisibility(el);
else if (cl.contains("icon-pin")) toggleFog(zone, cl);
-
if (customization) selectZone(el);
});
body.addEventListener("input", function (ev) {
- const el = ev.target,
- zone = el.parentNode.dataset.id;
- if (el.classList.contains("religionName")) zones.select("#" + zone).attr("data-description", el.value);
+ const el = ev.target;
+ const zone = zones.select("#" + el.parentNode.dataset.id);
+
+ if (el.classList.contains("zoneName")) zone.attr("data-description", el.value);
+ else if (el.classList.contains("zoneType")) zone.attr("data-type", el.value);
});
+ // update type filter with a list of used types
+ function updateFilters() {
+ const zones = Array.from(document.querySelectorAll("#zones > g"));
+ const types = unique(zones.map(zone => zone.dataset.type));
+
+ const filterSelect = document.getElementById("zonesFilterType");
+ const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
+
+ filterSelect.innerHTML = "" + types.map(type => ``).join("");
+ filterSelect.value = typeToFilterBy;
+ }
+
// add line for each zone
function zonesEditorAddLines() {
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
- let lines = "";
- zones.selectAll("g").each(function () {
- const c = this.dataset.cells ? this.dataset.cells.split(",").map(c => +c) : [];
- const description = this.dataset.description;
- const fill = this.getAttribute("fill");
+ const typeToFilterBy = document.getElementById("zonesFilterType").value;
+ const zones = Array.from(document.querySelectorAll("#zones > g"));
+ const filteredZones = typeToFilterBy === "all" ? zones : zones.filter(zone => zone.dataset.type === typeToFilterBy);
+
+ const lines = filteredZones.map(zoneEl => {
+ const c = zoneEl.dataset.cells ? zoneEl.dataset.cells.split(",").map(c => +c) : [];
+ const description = zoneEl.dataset.description;
+ const type = zoneEl.dataset.type;
+ const fill = zoneEl.getAttribute("fill");
const area = d3.sum(c.map(i => pack.cells.area[i])) * distanceScaleInput.value ** 2;
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate;
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
const population = rural + urban;
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
- const inactive = this.style.display === "none";
- const focused = defs.select("#fog #focus" + this.id).size();
+ const inactive = zoneEl.style.display === "none";
+ const focused = defs.select("#fog #focus" + zoneEl.id).size();
- lines += `