mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
Zones generator update (#1113)
* feat: style - store emblem size mod in style (v1.99.10) * fix the isOutdated function for versions past 1.99 * fix: showUploadMessage function not called correctly for isUpdated case * feat: load - improve version detection * feat: improve version detection and update process * feat: Update version and use constant for VERSION in multiple files * Update versioning.js to fix incorrect message display for stored version * feat: zones editor - update to work with pack data * feat: zones editor - update editor * feat: zones editor - update editor * chore: update version * feat: zones - regenerate * feat: zones - render zones as continuius line * feat: zones - editot changes * feat: zones - auto-update * feat: zones - generation fixes * feat: zones - generation fixes * feat: zones - restore layer * feat: zones - proselytism - check population --------- Co-authored-by: Azgaar <azgaar.fmg@yandex.com>
This commit is contained in:
parent
e77202a08a
commit
eb29c5ec5d
25 changed files with 1057 additions and 885 deletions
445
main.js
445
main.js
|
|
@ -62,7 +62,7 @@ let regions = viewbox.append("g").attr("id", "regions");
|
|||
let statesBody = regions.append("g").attr("id", "statesBody");
|
||||
let statesHalo = regions.append("g").attr("id", "statesHalo");
|
||||
let provs = viewbox.append("g").attr("id", "provs");
|
||||
let zones = viewbox.append("g").attr("id", "zones").style("display", "none");
|
||||
let zones = viewbox.append("g").attr("id", "zones");
|
||||
let borders = viewbox.append("g").attr("id", "borders");
|
||||
let stateBorders = borders.append("g").attr("id", "stateBorders");
|
||||
let provinceBorders = borders.append("g").attr("id", "provinceBorders");
|
||||
|
|
@ -662,7 +662,7 @@ async function generate(options) {
|
|||
|
||||
Military.generate();
|
||||
Markers.generate();
|
||||
addZones();
|
||||
Zones.generate();
|
||||
|
||||
drawScaleBar(scaleBar, scale);
|
||||
Names.getMapName();
|
||||
|
|
@ -684,7 +684,7 @@ async function generate(options) {
|
|||
buttons: {
|
||||
"Clear data": function () {
|
||||
localStorage.clear();
|
||||
localStorage.setItem("version", version);
|
||||
localStorage.setItem("version", VERSION);
|
||||
},
|
||||
Regenerate: function () {
|
||||
regenerateMap("generation error");
|
||||
|
|
@ -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;
|
||||
|
|
@ -1971,8 +1535,7 @@ function undraw() {
|
|||
viewbox
|
||||
.selectAll("path, circle, polygon, line, text, use, #texture > image, #zones > g, #armies > g, #ruler > g")
|
||||
.remove();
|
||||
document
|
||||
.getElementById("deftemp")
|
||||
byId("deftemp")
|
||||
.querySelectorAll("path, clipPath, svg")
|
||||
.forEach(el => el.remove());
|
||||
byId("coas").innerHTML = ""; // remove auto-generated emblems
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue