diff --git a/index.html b/index.html
index 6b1ad48d..c9ef341e 100644
--- a/index.html
+++ b/index.html
@@ -8003,6 +8003,7 @@
+
diff --git a/main.js b/main.js
index 12f75328..83a7ef50 100644
--- a/main.js
+++ b/main.js
@@ -662,7 +662,7 @@ async function generate(options) {
Military.generate();
Markers.generate();
- addZones();
+ Zones.generate();
drawScaleBar(scaleBar, scale);
Names.getMapName();
@@ -1484,442 +1484,6 @@ function rankCells() {
TIME && console.timeEnd("rankCells");
}
-// generate zones
-function addZones(number = 1) {
- TIME && console.time("addZones");
- 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
- for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addProselytism(); // proselitism of organized religion
- for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addCrusade(); // crusade on heresy lands
- for (let i = 0; i < rn(Math.random() * 1.8 * number); i++) addDisease(); // disease starting in a random city
- for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addDisaster(); // disaster starting in a random city
- for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addEruption(); // volcanic eruption aroung volcano
- for (let i = 0; i < rn(Math.random() * 1.0 * number); i++) addAvalanche(); // avalanche impacting highland road
- for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addFault(); // fault line in elevated areas
- 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;
-
- const invader = ra(atWar);
- const target = invader.diplomacy.findIndex(d => d === "Enemy");
-
- const cell = ra(
- cells.i.filter(i => cells.state[i] === target && cells.c[i].some(c => cells.state[c] === invader.i))
- );
- if (!cell) return;
-
- const cellsArray = [],
- queue = [cell],
- power = rand(5, 30);
-
- while (queue.length) {
- const q = P(0.4) ? queue.shift() : queue.pop();
- cellsArray.push(q);
- if (cellsArray.length > power) break;
-
- cells.c[q].forEach(e => {
- if (used[e]) return;
- if (cells.state[e] !== target) return;
- used[e] = 1;
- queue.push(e);
- });
- }
-
- const invasion = rw({
- Invasion: 4,
- Occupation: 3,
- Raid: 2,
- Conquest: 2,
- Subjugation: 1,
- Foray: 1,
- Skirmishes: 1,
- Incursion: 2,
- Pillaging: 1,
- Intervention: 1
- });
- const name = getAdjective(invader.name) + " " + invasion;
- zonesData.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"});
- }
-
- function addRebels() {
- const state = ra(states.filter(s => s.i && !s.removed && s.neighbors.some(n => n)));
- if (!state) return;
-
- const neib = ra(state.neighbors.filter(n => n && !states[n].removed));
- if (!neib) return;
- const cell = cells.i.find(
- i => cells.state[i] === state.i && !state.removed && cells.c[i].some(c => cells.state[c] === neib)
- );
- const cellsArray = [];
- const queue = [];
- if (cell) queue.push(cell);
-
- const power = rand(10, 30);
-
- while (queue.length) {
- const q = queue.shift();
- cellsArray.push(q);
- if (cellsArray.length > power) break;
-
- cells.c[q].forEach(e => {
- if (used[e]) return;
- if (cells.state[e] !== state.i) return;
- used[e] = 1;
- if (e % 4 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return;
- queue.push(e);
- });
- }
-
- 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;
- zonesData.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"});
- }
-
- function addProselytism() {
- const organized = ra(pack.religions.filter(r => r.type === "Organized"));
- if (!organized) return;
-
- const cell = ra(
- cells.i.filter(
- i =>
- cells.religion[i] &&
- cells.religion[i] !== organized.i &&
- cells.c[i].some(c => cells.religion[c] === organized.i)
- )
- );
- if (!cell) return;
- const target = cells.religion[cell];
- const cellsArray = [],
- queue = [cell],
- power = rand(10, 30);
-
- while (queue.length) {
- const q = queue.shift();
- cellsArray.push(q);
- if (cellsArray.length > power) break;
-
- cells.c[q].forEach(e => {
- if (used[e]) return;
- if (cells.religion[e] !== target) return;
- if (cells.h[e] < 20) return;
- used[e] = 1;
- //if (e%2 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return;
- queue.push(e);
- });
- }
-
- const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism";
- zonesData.push({name, type: "Proselytism", cells: cellsArray, fill: "url(#hatch6)"});
- }
-
- function addCrusade() {
- const heresy = ra(pack.religions.filter(r => r.type === "Heresy"));
- if (!heresy) return;
-
- const cellsArray = cells.i.filter(i => !used[i] && cells.religion[i] === heresy.i);
- if (!cellsArray.length) return;
- cellsArray.forEach(i => (used[i] = 1));
-
- const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade";
- zonesData.push({name, type: "Crusade", cells: cellsArray, fill: "url(#hatch6)"});
- }
-
- function addDisease() {
- const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg
- if (!burg) return;
-
- const cellsArray = [];
- const cost = [];
- const power = rand(20, 37);
-
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- queue.queue({e: burg.cell, p: 0});
-
- while (queue.length) {
- const next = queue.dequeue();
- if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
- used[next.e] = 1;
-
- cells.c[next.e].forEach(nextCellId => {
- const c = Routes.getRoute(next.e, nextCellId) ? 5 : 100;
- const p = next.p + c;
- if (p > power) return;
-
- if (!cost[nextCellId] || p < cost[nextCellId]) {
- cost[nextCellId] = p;
- queue.queue({e: nextCellId, p});
- }
- });
- }
-
- const adjective = () =>
- ra(["Great", "Silent", "Severe", "Blind", "Unknown", "Loud", "Deadly", "Burning", "Bloody", "Brutal", "Fatal"]);
- const animal = () =>
- ra([
- "Ape",
- "Bear",
- "Boar",
- "Cat",
- "Cow",
- "Dog",
- "Pig",
- "Fox",
- "Bird",
- "Horse",
- "Rat",
- "Raven",
- "Sheep",
- "Spider",
- "Wolf"
- ]);
- const color = () =>
- ra([
- "Golden",
- "White",
- "Black",
- "Red",
- "Pink",
- "Purple",
- "Blue",
- "Green",
- "Yellow",
- "Amber",
- "Orange",
- "Brown",
- "Grey"
- ]);
-
- 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;
- zonesData.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
- }
-
- function addDisaster() {
- const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg
- if (!burg) return;
-
- const cellsArray = [],
- cost = [],
- power = rand(5, 25);
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- queue.queue({e: burg.cell, p: 0});
-
- while (queue.length) {
- const next = queue.dequeue();
- if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
- used[next.e] = 1;
-
- cells.c[next.e].forEach(function (e) {
- const c = rand(1, 10);
- const p = next.p + c;
- if (p > power) return;
-
- if (!cost[e] || p < cost[e]) {
- cost[e] = p;
- queue.queue({e, p});
- }
- });
- }
-
- const type = rw({Famine: 5, Dearth: 1, Drought: 3, Earthquake: 3, Tornadoes: 1, Wildfires: 1});
- const name = getAdjective(burg.name) + " " + type;
- zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
- }
-
- function addEruption() {
- const volcano = byId("markers").querySelector("use[data-id='#marker_volcano']");
- if (!volcano) return;
-
- const x = +volcano.dataset.x,
- y = +volcano.dataset.y,
- cell = findCell(x, y);
- const id = volcano.id;
- const note = notes.filter(n => n.id === id);
-
- if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano");
- const name = note[0] ? note[0].name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption";
-
- const cellsArray = [],
- queue = [cell],
- power = rand(10, 30);
-
- while (queue.length) {
- const q = P(0.5) ? queue.shift() : queue.pop();
- cellsArray.push(q);
- if (cellsArray.length > power) break;
- cells.c[q].forEach(e => {
- if (used[e] || cells.h[e] < 20) return;
- used[e] = 1;
- queue.push(e);
- });
- }
-
- zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch7)"});
- }
-
- function addAvalanche() {
- const routes = cells.i.filter(i => !used[i] && Routes.isConnected(i) && cells.h[i] >= 70);
- if (!routes.length) return;
-
- const cell = +ra(routes);
- const cellsArray = [],
- queue = [cell],
- power = rand(3, 15);
-
- while (queue.length) {
- const q = P(0.3) ? queue.shift() : queue.pop();
- cellsArray.push(q);
- if (cellsArray.length > power) break;
- cells.c[q].forEach(e => {
- if (used[e] || cells.h[e] < 65) return;
- used[e] = 1;
- queue.push(e);
- });
- }
-
- const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
- const name = proper + " Avalanche";
- zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
- }
-
- function addFault() {
- const elevated = cells.i.filter(i => !used[i] && cells.h[i] > 50 && cells.h[i] < 70);
- if (!elevated.length) return;
-
- const cell = ra(elevated);
- const cellsArray = [],
- queue = [cell],
- power = rand(3, 15);
-
- while (queue.length) {
- const q = queue.pop();
- if (cells.h[q] >= 20) cellsArray.push(q);
- if (cellsArray.length > power) break;
- cells.c[q].forEach(e => {
- if (used[e] || cells.r[e]) return;
- used[e] = 1;
- queue.push(e);
- });
- }
-
- const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
- const name = proper + " Fault";
- zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch2)"});
- }
-
- function addFlood() {
- const fl = cells.fl.filter(fl => fl),
- meanFlux = d3.mean(fl),
- maxFlux = d3.max(fl),
- flux = (maxFlux - meanFlux) / 2 + meanFlux;
- const rivers = cells.i.filter(
- i => !used[i] && cells.h[i] < 50 && cells.r[i] && cells.fl[i] > flux && cells.burg[i]
- );
- if (!rivers.length) return;
-
- const cell = +ra(rivers),
- river = cells.r[cell];
- const cellsArray = [],
- queue = [cell],
- power = rand(5, 30);
-
- while (queue.length) {
- const q = queue.pop();
- cellsArray.push(q);
- if (cellsArray.length > power) break;
-
- cells.c[q].forEach(e => {
- if (used[e] || cells.h[e] < 20 || cells.r[e] !== river || cells.h[e] > 50 || cells.fl[e] < meanFlux) return;
- used[e] = 1;
- queue.push(e);
- });
- }
-
- const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood";
- zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
- }
-
- function addTsunami() {
- const coastal = cells.i.filter(i => !used[i] && cells.t[i] === -1 && pack.features[cells.f[i]].type !== "lake");
- if (!coastal.length) return;
-
- const cell = +ra(coastal);
- const cellsArray = [],
- queue = [cell],
- power = rand(10, 30);
-
- while (queue.length) {
- const q = queue.shift();
- if (cells.t[q] === 1) cellsArray.push(q);
- if (cellsArray.length > power) break;
-
- cells.c[q].forEach(e => {
- if (used[e]) return;
- if (cells.t[e] > 2) return;
- if (pack.features[cells.f[e]].type === "lake") return;
- used[e] = 1;
- queue.push(e);
- });
- }
-
- const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
- const name = proper + " Tsunami";
- zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
- }
-
- function drawZones() {
- zones
- .selectAll("g")
- .data(zonesData)
- .enter()
- .append("g")
- .attr("id", (d, i) => "zone" + i)
- .attr("data-description", d => d.name)
- .attr("data-type", d => d.type)
- .attr("data-cells", d => d.cells.join(","))
- .attr("fill", d => d.fill)
- .selectAll("polygon")
- .data(d => d.cells)
- .enter()
- .append("polygon")
- .attr("points", d => getPackPolygon(d))
- .attr("id", function (d) {
- return this.parentNode.id + "_" + d;
- });
- }
-
- TIME && console.timeEnd("addZones");
-}
-
// show map stats on generation complete
function showStatistics() {
const heightmap = byId("templateInput").value;
diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js
index 4bbb778d..2363a355 100644
--- a/modules/dynamic/auto-update.js
+++ b/modules/dynamic/auto-update.js
@@ -63,7 +63,7 @@ export function resolveVersionConflicts(version) {
.attr("stroke-width", 0)
.attr("stroke-dasharray", null)
.attr("stroke-linecap", "butt");
- addZones();
+ Zones.generate();
if (!markers.selectAll("*").size()) {
Markers.generate();
turnButtonOn("toggleMarkers");
diff --git a/modules/submap.js b/modules/submap.js
index 6a9593ca..8804db9d 100644
--- a/modules/submap.js
+++ b/modules/submap.js
@@ -310,7 +310,7 @@ window.Submap = (function () {
stage("Redraw emblems.");
drawEmblems();
stage("Regenerating Zones.");
- addZones();
+ Zones.generate();
Names.getMapName();
stage("Restoring Notes.");
notes = parentMap.notes;
diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js
index ac270299..a88c698c 100644
--- a/modules/ui/heightmap-editor.js
+++ b/modules/ui/heightmap-editor.js
@@ -261,7 +261,7 @@ function editHeightmap(options) {
Military.generate();
Markers.generate();
- addZones();
+ Zones.generate();
TIME && console.timeEnd("regenerateErasedData");
INFO && console.groupEnd("Edit Heightmap");
}
diff --git a/modules/ui/tools.js b/modules/ui/tools.js
index 9c75ac20..7d2a0e1f 100644
--- a/modules/ui/tools.js
+++ b/modules/ui/tools.js
@@ -546,7 +546,7 @@ function regenerateZones(event) {
function addNumberOfZones(number) {
zones.selectAll("g").remove(); // remove existing zones
- addZones(number);
+ Zones.generate(number);
if (document.getElementById("zonesEditorRefresh").offsetParent) zonesEditorRefresh.click();
if (!layerIsOn("toggleZones")) toggleZones();
}
diff --git a/modules/zones-generator.js b/modules/zones-generator.js
new file mode 100644
index 00000000..6454e895
--- /dev/null
+++ b/modules/zones-generator.js
@@ -0,0 +1,465 @@
+"use strict";
+
+window.Zones = (function () {
+ const config = {
+ invasion: {modifier: 1.8, generate: addInvasion}, // invasion of enemy lands
+ rebels: {modifier: 1.6, generate: addRebels}, // rebels along a state border
+ proselytism: {modifier: 1.6, generate: addProselytism}, // proselitism of organized religion
+ crusade: {modifier: 1.6, generate: addCrusade}, // crusade on heresy lands
+ disease: {modifier: 1.8, generate: addDisease}, // disease starting in a random city
+ disaster: {modifier: 1.4, generate: addDisaster}, // disaster starting in a random city
+ eruption: {modifier: 1.4, generate: addEruption}, // volcanic eruption aroung volcano
+ avalanche: {modifier: 1.0, generate: addAvalanche}, // avalanche impacting highland road
+ fault: {modifier: 1.4, generate: addFault}, // fault line in elevated areas
+ flood: {modifier: 1.4, generate: addFlood}, // flood on river banks
+ tsunami: {modifier: 1.2, generate: addTsunami} // tsunami starting near coast
+ };
+
+ const generate = function (globalModifier = 1) {
+ TIME && console.time("generateZones");
+
+ const usedCells = new Uint8Array(pack.cells.i.length);
+ pack.zones = [];
+
+ Object.values(config).forEach(type => {
+ const count = rn(Math.random() * type.modifier * globalModifier);
+ for (let i = 0; i < count; i++) {
+ type.generate(usedCells);
+ }
+ });
+
+ drawZones();
+
+ function drawZones() {
+ zones
+ .selectAll("g")
+ .data(pack.zones)
+ .enter()
+ .append("g")
+ .attr("id", d => "zone" + d.i)
+ .attr("data-description", d => d.name)
+ .attr("data-type", d => d.type)
+ .attr("data-cells", d => d.cells.join(","))
+ .attr("fill", d => d.color)
+ .selectAll("polygon")
+ .data(d => d.cells)
+ .enter()
+ .append("polygon")
+ .attr("points", d => getPackPolygon(d))
+ .attr("id", function (d) {
+ return this.parentNode.id + "_" + d;
+ });
+ }
+
+ TIME && console.timeEnd("generateZones");
+ };
+
+ function addInvasion(usedCells) {
+ const atWar = pack.states.filter(s => s.diplomacy && s.diplomacy.some(d => d === "Enemy"));
+ if (!atWar.length) return;
+
+ const invader = ra(atWar);
+ const target = invader.diplomacy.findIndex(d => d === "Enemy");
+
+ const cells = pack.cells;
+ const cell = ra(
+ cells.i.filter(i => cells.state[i] === target && cells.c[i].some(c => cells.state[c] === invader.i))
+ );
+ if (!cell) return;
+
+ const cellsArray = [],
+ queue = [cell],
+ power = rand(5, 30);
+
+ while (queue.length) {
+ const q = P(0.4) ? queue.shift() : queue.pop();
+ cellsArray.push(q);
+ if (cellsArray.length > power) break;
+
+ cells.c[q].forEach(e => {
+ if (usedCells[e]) return;
+ if (cells.state[e] !== target) return;
+ usedCells[e] = 1;
+ queue.push(e);
+ });
+ }
+
+ const invasion = rw({
+ Invasion: 4,
+ Occupation: 3,
+ Raid: 2,
+ Conquest: 2,
+ Subjugation: 1,
+ Foray: 1,
+ Skirmishes: 1,
+ Incursion: 2,
+ Pillaging: 1,
+ Intervention: 1
+ });
+ const name = getAdjective(invader.name) + " " + invasion;
+ pack.zones.push({name, type: "Invasion", cells: cellsArray, color: "url(#hatch1)"});
+ }
+
+ function addRebels(usedCells) {
+ const {cells, states} = pack;
+ const state = ra(states.filter(s => s.i && !s.removed && s.neighbors.some(Boolean)));
+ if (!state) return;
+
+ const neib = ra(state.neighbors.filter(n => n && !states[n].removed));
+ if (!neib) return;
+
+ const cell = cells.i.find(
+ i => cells.state[i] === state.i && !state.removed && cells.c[i].some(c => cells.state[c] === neib)
+ );
+ const cellsArray = [];
+ const queue = [];
+ if (cell) queue.push(cell);
+
+ const power = rand(10, 30);
+
+ while (queue.length) {
+ const q = queue.shift();
+ cellsArray.push(q);
+ if (cellsArray.length > power) break;
+
+ cells.c[q].forEach(e => {
+ if (usedCells[e]) return;
+ if (cells.state[e] !== state.i) return;
+ usedCells[e] = 1;
+ if (e % 4 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return;
+ queue.push(e);
+ });
+ }
+
+ 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;
+ pack.zones.push({name, type: "Rebels", cells: cellsArray, color: "url(#hatch3)"});
+ }
+
+ function addProselytism(usedCells) {
+ const organized = ra(pack.religions.filter(r => r.type === "Organized"));
+ if (!organized) return;
+
+ const cells = pack.cells;
+ const cell = ra(
+ cells.i.filter(
+ i =>
+ cells.religion[i] &&
+ cells.religion[i] !== organized.i &&
+ cells.c[i].some(c => cells.religion[c] === organized.i)
+ )
+ );
+ if (!cell) return;
+ const target = cells.religion[cell];
+ const cellsArray = [],
+ queue = [cell],
+ power = rand(10, 30);
+
+ while (queue.length) {
+ const q = queue.shift();
+ cellsArray.push(q);
+ if (cellsArray.length > power) break;
+
+ cells.c[q].forEach(e => {
+ if (usedCells[e]) return;
+ if (cells.religion[e] !== target) return;
+ if (cells.h[e] < 20) return;
+ usedCells[e] = 1;
+ //if (e%2 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return;
+ queue.push(e);
+ });
+ }
+
+ const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism";
+ pack.zones.push({name, type: "Proselytism", cells: cellsArray, color: "url(#hatch6)"});
+ }
+
+ function addCrusade(usedCells) {
+ const heresy = ra(pack.religions.filter(r => r.type === "Heresy"));
+ if (!heresy) return;
+
+ const cells = pack.cells;
+ const cellsArray = cells.i.filter(i => !usedCells[i] && cells.religion[i] === heresy.i);
+ if (!cellsArray.length) return;
+ cellsArray.forEach(i => (usedCells[i] = 1));
+
+ const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade";
+ pack.zones.push({name, type: "Crusade", cells: cellsArray, color: "url(#hatch6)"});
+ }
+
+ function addDisease(usedCells) {
+ const burg = ra(pack.burgs.filter(b => !usedCells[b.cell] && b.i && !b.removed)); // random burg
+ if (!burg) return;
+
+ const cells = pack.cells;
+ const cellsArray = [];
+ const cost = [];
+ const power = rand(20, 37);
+
+ const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
+ queue.queue({e: burg.cell, p: 0});
+
+ while (queue.length) {
+ const next = queue.dequeue();
+ if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
+ usedCells[next.e] = 1;
+
+ cells.c[next.e].forEach(nextCellId => {
+ const c = Routes.getRoute(next.e, nextCellId) ? 5 : 100;
+ const p = next.p + c;
+ if (p > power) return;
+
+ if (!cost[nextCellId] || p < cost[nextCellId]) {
+ cost[nextCellId] = p;
+ queue.queue({e: nextCellId, p});
+ }
+ });
+ }
+
+ const adjective = () =>
+ ra(["Great", "Silent", "Severe", "Blind", "Unknown", "Loud", "Deadly", "Burning", "Bloody", "Brutal", "Fatal"]);
+ const animal = () =>
+ ra([
+ "Ape",
+ "Bear",
+ "Boar",
+ "Cat",
+ "Cow",
+ "Dog",
+ "Pig",
+ "Fox",
+ "Bird",
+ "Horse",
+ "Rat",
+ "Raven",
+ "Sheep",
+ "Spider",
+ "Wolf"
+ ]);
+ const color = () =>
+ ra([
+ "Golden",
+ "White",
+ "Black",
+ "Red",
+ "Pink",
+ "Purple",
+ "Blue",
+ "Green",
+ "Yellow",
+ "Amber",
+ "Orange",
+ "Brown",
+ "Grey"
+ ]);
+
+ 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;
+ pack.zones.push({name, type: "Disease", cells: cellsArray, color: "url(#hatch12)"});
+ }
+
+ function addDisaster(usedCells) {
+ const burg = ra(pack.burgs.filter(b => !usedCells[b.cell] && b.i && !b.removed)); // random burg
+ if (!burg) return;
+
+ const cells = pack.cells;
+ const cellsArray = [],
+ cost = [],
+ power = rand(5, 25);
+ const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
+ queue.queue({e: burg.cell, p: 0});
+
+ while (queue.length) {
+ const next = queue.dequeue();
+ if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
+ usedCells[next.e] = 1;
+
+ cells.c[next.e].forEach(function (e) {
+ const c = rand(1, 10);
+ const p = next.p + c;
+ if (p > power) return;
+
+ if (!cost[e] || p < cost[e]) {
+ cost[e] = p;
+ queue.queue({e, p});
+ }
+ });
+ }
+
+ const type = rw({Famine: 5, Dearth: 1, Drought: 3, Earthquake: 3, Tornadoes: 1, Wildfires: 1});
+ const name = getAdjective(burg.name) + " " + type;
+ pack.zones.push({name, type: "Disaster", cells: cellsArray, color: "url(#hatch5)"});
+ }
+
+ function addEruption(usedCells) {
+ const volcano = byId("markers").querySelector("use[data-id='#marker_volcano']");
+ if (!volcano) return;
+
+ const cells = pack.cells;
+ const x = +volcano.dataset.x,
+ y = +volcano.dataset.y,
+ cell = findCell(x, y);
+ const id = volcano.id;
+ const note = notes.filter(n => n.id === id);
+
+ if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano");
+ const name = note[0] ? note[0].name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption";
+
+ const cellsArray = [],
+ queue = [cell],
+ power = rand(10, 30);
+
+ while (queue.length) {
+ const q = P(0.5) ? queue.shift() : queue.pop();
+ cellsArray.push(q);
+ if (cellsArray.length > power) break;
+ cells.c[q].forEach(e => {
+ if (usedCells[e] || cells.h[e] < 20) return;
+ usedCells[e] = 1;
+ queue.push(e);
+ });
+ }
+
+ pack.zones.push({name, type: "Disaster", cells: cellsArray, color: "url(#hatch7)"});
+ }
+
+ function addAvalanche(usedCells) {
+ const cells = pack.cells;
+ const routes = cells.i.filter(i => !usedCells[i] && Routes.isConnected(i) && cells.h[i] >= 70);
+ if (!routes.length) return;
+
+ const cell = +ra(routes);
+ const cellsArray = [],
+ queue = [cell],
+ power = rand(3, 15);
+
+ while (queue.length) {
+ const q = P(0.3) ? queue.shift() : queue.pop();
+ cellsArray.push(q);
+ if (cellsArray.length > power) break;
+ cells.c[q].forEach(e => {
+ if (usedCells[e] || cells.h[e] < 65) return;
+ usedCells[e] = 1;
+ queue.push(e);
+ });
+ }
+
+ const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
+ const name = proper + " Avalanche";
+ pack.zones.push({name, type: "Disaster", cells: cellsArray, color: "url(#hatch5)"});
+ }
+
+ function addFault(usedCells) {
+ const cells = pack.cells;
+ const elevated = cells.i.filter(i => !usedCells[i] && cells.h[i] > 50 && cells.h[i] < 70);
+ if (!elevated.length) return;
+
+ const cell = ra(elevated);
+ const cellsArray = [],
+ queue = [cell],
+ power = rand(3, 15);
+
+ while (queue.length) {
+ const q = queue.pop();
+ if (cells.h[q] >= 20) cellsArray.push(q);
+ if (cellsArray.length > power) break;
+ cells.c[q].forEach(e => {
+ if (usedCells[e] || cells.r[e]) return;
+ usedCells[e] = 1;
+ queue.push(e);
+ });
+ }
+
+ const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
+ const name = proper + " Fault";
+ pack.zones.push({name, type: "Disaster", cells: cellsArray, color: "url(#hatch2)"});
+ }
+
+ function addFlood(usedCells) {
+ const cells = pack.cells;
+ const fl = cells.fl.filter(fl => fl),
+ meanFlux = d3.mean(fl),
+ maxFlux = d3.max(fl),
+ flux = (maxFlux - meanFlux) / 2 + meanFlux;
+ const rivers = cells.i.filter(
+ i => !usedCells[i] && cells.h[i] < 50 && cells.r[i] && cells.fl[i] > flux && cells.burg[i]
+ );
+ if (!rivers.length) return;
+
+ const cell = +ra(rivers),
+ river = cells.r[cell];
+ const cellsArray = [],
+ queue = [cell],
+ power = rand(5, 30);
+
+ while (queue.length) {
+ const q = queue.pop();
+ cellsArray.push(q);
+ if (cellsArray.length > power) break;
+
+ cells.c[q].forEach(e => {
+ if (usedCells[e] || cells.h[e] < 20 || cells.r[e] !== river || cells.h[e] > 50 || cells.fl[e] < meanFlux)
+ return;
+ usedCells[e] = 1;
+ queue.push(e);
+ });
+ }
+
+ const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood";
+ pack.zones.push({name, type: "Disaster", cells: cellsArray, color: "url(#hatch13)"});
+ }
+
+ function addTsunami(usedCells) {
+ const cells = pack.cells;
+ const coastal = cells.i.filter(
+ i => !usedCells[i] && cells.t[i] === -1 && pack.features[cells.f[i]].type !== "lake"
+ );
+ if (!coastal.length) return;
+
+ const cell = +ra(coastal);
+ const cellsArray = [],
+ queue = [cell],
+ power = rand(10, 30);
+
+ while (queue.length) {
+ const q = queue.shift();
+ if (cells.t[q] === 1) cellsArray.push(q);
+ if (cellsArray.length > power) break;
+
+ cells.c[q].forEach(e => {
+ if (usedCells[e]) return;
+ if (cells.t[e] > 2) return;
+ if (pack.features[cells.f[e]].type === "lake") return;
+ usedCells[e] = 1;
+ queue.push(e);
+ });
+ }
+
+ const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
+ const name = proper + " Tsunami";
+ pack.zones.push({name, type: "Disaster", cells: cellsArray, color: "url(#hatch13)"});
+ }
+
+ return {generate};
+})();
diff --git a/versioning.js b/versioning.js
index b0b15315..91aa8060 100644
--- a/versioning.js
+++ b/versioning.js
@@ -1,7 +1,7 @@
"use strict";
// version and caching control
-const version = "1.99.10"; // generator version, update each time
+const version = "1.100.00"; // generator version, update each time
{
document.title += " v" + version;