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
447
modules/zones-generator.js
Normal file
447
modules/zones-generator.js
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
"use strict";
|
||||
|
||||
window.Zones = (function () {
|
||||
const config = {
|
||||
invasion: {quantity: 2, generate: addInvasion}, // invasion of enemy lands
|
||||
rebels: {quantity: 1.5, generate: addRebels}, // rebels along a state border
|
||||
proselytism: {quantity: 1.6, generate: addProselytism}, // proselitism of organized religion
|
||||
crusade: {quantity: 1.6, generate: addCrusade}, // crusade on heresy lands
|
||||
disease: {quantity: 1.4, generate: addDisease}, // disease starting in a random city
|
||||
disaster: {quantity: 1, generate: addDisaster}, // disaster starting in a random city
|
||||
eruption: {quantity: 1, generate: addEruption}, // eruption aroung volcano
|
||||
avalanche: {quantity: 0.8, generate: addAvalanche}, // avalanche impacting highland road
|
||||
fault: {quantity: 1, generate: addFault}, // fault line in elevated areas
|
||||
flood: {quantity: 1, generate: addFlood}, // flood on river banks
|
||||
tsunami: {quantity: 1, 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 expectedNumber = type.quantity * globalModifier;
|
||||
let number = gauss(expectedNumber, expectedNumber / 2, 0, 100);
|
||||
while (number--) type.generate(usedCells);
|
||||
});
|
||||
|
||||
TIME && console.timeEnd("generateZones");
|
||||
};
|
||||
|
||||
function addInvasion(usedCells) {
|
||||
const {cells, states} = pack;
|
||||
|
||||
const ongoingConflicts = states
|
||||
.filter(s => s.i && !s.removed && s.campaigns)
|
||||
.map(s => s.campaigns)
|
||||
.flat()
|
||||
.filter(c => !c.end);
|
||||
if (!ongoingConflicts.length) return;
|
||||
const {defender, attacker} = ra(ongoingConflicts);
|
||||
|
||||
const borderCells = cells.i.filter(cellId => {
|
||||
if (usedCells[cellId]) return false;
|
||||
if (cells.state[cellId] !== defender) return false;
|
||||
return cells.c[cellId].some(c => cells.state[c] === attacker);
|
||||
});
|
||||
|
||||
const startCell = ra(borderCells);
|
||||
if (startCell === undefined) return;
|
||||
|
||||
const invationCells = [];
|
||||
const queue = [startCell];
|
||||
const maxCells = rand(5, 30);
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = P(0.4) ? queue.shift() : queue.pop();
|
||||
invationCells.push(cellId);
|
||||
if (invationCells.length >= maxCells) break;
|
||||
|
||||
cells.c[cellId].forEach(neibCellId => {
|
||||
if (usedCells[neibCellId]) return;
|
||||
if (cells.state[neibCellId] !== defender) return;
|
||||
usedCells[neibCellId] = 1;
|
||||
queue.push(neibCellId);
|
||||
});
|
||||
}
|
||||
|
||||
const subtype = rw({
|
||||
Invasion: 5,
|
||||
Occupation: 4,
|
||||
Conquest: 3,
|
||||
Incursion: 2,
|
||||
Intervention: 2,
|
||||
Subjugation: 1,
|
||||
Foray: 1,
|
||||
Skirmishes: 1,
|
||||
Pillaging: 1,
|
||||
Raid: 1
|
||||
});
|
||||
const name = getAdjective(states[attacker].name) + " " + subtype;
|
||||
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Invasion", cells: invationCells, 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 neibStateId = ra(state.neighbors.filter(n => n && !states[n].removed));
|
||||
if (!neibStateId) return;
|
||||
|
||||
const cellsArray = [];
|
||||
const queue = [];
|
||||
const borderCellId = cells.i.find(
|
||||
i => cells.state[i] === state.i && cells.c[i].some(c => cells.state[c] === neibStateId)
|
||||
);
|
||||
if (borderCellId) queue.push(borderCellId);
|
||||
const maxCells = rand(10, 30);
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = queue.shift();
|
||||
cellsArray.push(cellId);
|
||||
if (cellsArray.length >= maxCells) break;
|
||||
|
||||
cells.c[cellId].forEach(neibCellId => {
|
||||
if (usedCells[neibCellId]) return;
|
||||
if (cells.state[neibCellId] !== state.i) return;
|
||||
usedCells[neibCellId] = 1;
|
||||
if (neibCellId % 4 !== 0 && !cells.c[neibCellId].some(c => cells.state[c] === neibStateId)) return;
|
||||
queue.push(neibCellId);
|
||||
});
|
||||
}
|
||||
|
||||
const rebels = rw({
|
||||
Rebels: 5,
|
||||
Insurrection: 2,
|
||||
Mutineers: 1,
|
||||
Insurgents: 1,
|
||||
Rioters: 1,
|
||||
Separatists: 1,
|
||||
Secessionists: 1,
|
||||
Rebellion: 1,
|
||||
Conspiracy: 1
|
||||
});
|
||||
|
||||
const name = getAdjective(states[neibStateId].name) + " " + rebels;
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Rebels", cells: cellsArray, color: "url(#hatch3)"});
|
||||
}
|
||||
|
||||
function addProselytism(usedCells) {
|
||||
const {cells, religions} = pack;
|
||||
|
||||
const organizedReligions = religions.filter(r => r.i && !r.removed && r.type === "Organized");
|
||||
const religion = ra(organizedReligions);
|
||||
if (!religion) return;
|
||||
|
||||
const targetBorderCells = cells.i.filter(
|
||||
i =>
|
||||
cells.h[i] < 20 &&
|
||||
cells.pop[i] &&
|
||||
cells.religion[i] !== religion.i &&
|
||||
cells.c[i].some(c => cells.religion[c] === religion.i)
|
||||
);
|
||||
const startCell = ra(targetBorderCells);
|
||||
if (!startCell) return;
|
||||
|
||||
const targetReligionId = cells.religion[startCell];
|
||||
const proselytismCells = [];
|
||||
const queue = [startCell];
|
||||
const maxCells = rand(10, 30);
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = queue.shift();
|
||||
proselytismCells.push(cellId);
|
||||
if (proselytismCells.length >= maxCells) break;
|
||||
|
||||
cells.c[cellId].forEach(neibCellId => {
|
||||
if (usedCells[neibCellId]) return;
|
||||
if (cells.religion[neibCellId] !== targetReligionId) return;
|
||||
if (cells.h[neibCellId] < 20 || !cells.pop[i]) return;
|
||||
usedCells[neibCellId] = 1;
|
||||
queue.push(neibCellId);
|
||||
});
|
||||
}
|
||||
|
||||
const name = `${getAdjective(religion.name.split(" ")[0])} Proselytism`;
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Proselytism", cells: proselytismCells, color: "url(#hatch6)"});
|
||||
}
|
||||
|
||||
function addCrusade(usedCells) {
|
||||
const {cells, religions} = pack;
|
||||
|
||||
const heresies = religions.filter(r => !r.removed && r.type === "Heresy");
|
||||
if (!heresies.length) return;
|
||||
|
||||
const heresy = ra(heresies);
|
||||
const crusadeCells = cells.i.filter(i => !usedCells[i] && cells.religion[i] === heresy.i);
|
||||
if (!crusadeCells.length) return;
|
||||
crusadeCells.forEach(i => (usedCells[i] = 1));
|
||||
|
||||
const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade";
|
||||
pack.zones.push({
|
||||
i: pack.zones.length,
|
||||
name,
|
||||
type: "Crusade",
|
||||
cells: Array.from(crusadeCells),
|
||||
color: "url(#hatch6)"
|
||||
});
|
||||
}
|
||||
|
||||
function addDisease(usedCells) {
|
||||
const {cells, burgs} = pack;
|
||||
|
||||
const burg = ra(burgs.filter(b => !usedCells[b.cell] && b.i && !b.removed)); // random burg
|
||||
if (!burg) return;
|
||||
|
||||
const cellsArray = [];
|
||||
const cost = [];
|
||||
const maxCells = rand(20, 40);
|
||||
|
||||
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 > maxCells) return;
|
||||
|
||||
if (!cost[nextCellId] || p < cost[nextCellId]) {
|
||||
cost[nextCellId] = p;
|
||||
queue.queue({e: nextCellId, p});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
const name = `${(() => {
|
||||
const model = rw({color: 2, animal: 1, adjective: 1});
|
||||
if (model === "color") return ra(["Amber", "Azure", "Black", "Blue", "Brown", "Crimson", "Emerald", "Golden", "Green", "Grey", "Orange", "Pink", "Purple", "Red", "Ruby", "Scarlet", "Silver", "Violet", "White", "Yellow"]);
|
||||
if (model === "animal") return ra(["Ape", "Bear", "Bird", "Boar", "Cat", "Cow", "Dog", "Fox", "Horse", "Lion", "Pig", "Rat", "Raven", "Sheep", "Spider", "Tiger", "Viper", "Wolf", "Worm", "Wyrm"]);
|
||||
if (model === "adjective") return ra(["Blind", "Bloody", "Brutal", "Burning", "Deadly", "Fatal", "Furious", "Great", "Grim", "Horrible", "Invisible", "Lethal", "Loud", "Mortal", "Savage", "Severe", "Silent", "Unknown", "Venomous", "Vicious"]);
|
||||
})()} ${rw({Fever: 5, Plague: 3, Cough: 3, Flu: 2, Pox: 2, Cholera: 2, Typhoid: 2, Leprosy: 1, Smallpox: 1, Pestilence: 1, Consumption: 1, Malaria: 1, Dropsy: 1})}`;
|
||||
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Disease", cells: cellsArray, color: "url(#hatch12)"});
|
||||
}
|
||||
|
||||
function addDisaster(usedCells) {
|
||||
const {cells, burgs} = pack;
|
||||
|
||||
const burg = ra(burgs.filter(b => !usedCells[b.cell] && b.i && !b.removed));
|
||||
if (!burg) return;
|
||||
usedCells[burg.cell] = 1;
|
||||
|
||||
const cellsArray = [];
|
||||
const cost = [];
|
||||
const maxCells = 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 > maxCells) return;
|
||||
|
||||
if (!cost[e] || p < cost[e]) {
|
||||
cost[e] = p;
|
||||
queue.queue({e, p});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const type = rw({
|
||||
Famine: 5,
|
||||
Drought: 3,
|
||||
Earthquake: 3,
|
||||
Dearth: 1,
|
||||
Tornadoes: 1,
|
||||
Wildfires: 1,
|
||||
Storms: 1,
|
||||
Blight: 1
|
||||
});
|
||||
const name = getAdjective(burg.name) + " " + type;
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Disaster", cells: cellsArray, color: "url(#hatch5)"});
|
||||
}
|
||||
|
||||
function addEruption(usedCells) {
|
||||
const {cells, markers} = pack;
|
||||
|
||||
const volcanoe = markers.find(m => m.type === "volcanoes" && !usedCells[m.cell]);
|
||||
if (!volcanoe) return;
|
||||
usedCells[volcanoe.cell] = 1;
|
||||
|
||||
const note = notes.find(n => n.id === "marker" + volcanoe.i);
|
||||
if (note) note.legend = note.legend.replace("Active volcano", "Erupting volcano");
|
||||
const name = note ? note.name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption";
|
||||
|
||||
const cellsArray = [];
|
||||
const queue = [volcanoe.cell];
|
||||
const maxCells = rand(10, 30);
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = P(0.5) ? queue.shift() : queue.pop();
|
||||
cellsArray.push(cellId);
|
||||
if (cellsArray.length >= maxCells) break;
|
||||
|
||||
cells.c[cellId].forEach(neibCellId => {
|
||||
if (usedCells[neibCellId] || cells.h[neibCellId] < 20) return;
|
||||
usedCells[neibCellId] = 1;
|
||||
queue.push(neibCellId);
|
||||
});
|
||||
}
|
||||
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Eruption", cells: cellsArray, color: "url(#hatch7)"});
|
||||
}
|
||||
|
||||
function addAvalanche(usedCells) {
|
||||
const {cells} = pack;
|
||||
|
||||
const routeCells = cells.i.filter(i => !usedCells[i] && Routes.isConnected(i) && cells.h[i] >= 70);
|
||||
if (!routeCells.length) return;
|
||||
|
||||
const startCell = ra(routeCells);
|
||||
usedCells[startCell] = 1;
|
||||
|
||||
const cellsArray = [];
|
||||
const queue = [startCell];
|
||||
const maxCells = rand(3, 15);
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = P(0.3) ? queue.shift() : queue.pop();
|
||||
cellsArray.push(cellId);
|
||||
if (cellsArray.length >= maxCells) break;
|
||||
|
||||
cells.c[cellId].forEach(neibCellId => {
|
||||
if (usedCells[neibCellId] || cells.h[neibCellId] < 65) return;
|
||||
usedCells[neibCellId] = 1;
|
||||
queue.push(neibCellId);
|
||||
});
|
||||
}
|
||||
|
||||
const name = getAdjective(Names.getCultureShort(cells.culture[startCell])) + " Avalanche";
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Avalanche", cells: cellsArray, color: "url(#hatch5)"});
|
||||
}
|
||||
|
||||
function addFault(usedCells) {
|
||||
const cells = pack.cells;
|
||||
|
||||
const elevatedCells = cells.i.filter(i => !usedCells[i] && cells.h[i] > 50 && cells.h[i] < 70);
|
||||
if (!elevatedCells.length) return;
|
||||
|
||||
const startCell = ra(elevatedCells);
|
||||
usedCells[startCell] = 1;
|
||||
|
||||
const cellsArray = [];
|
||||
const queue = [startCell];
|
||||
const maxCells = rand(3, 15);
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = queue.pop();
|
||||
if (cells.h[cellId] >= 20) cellsArray.push(cellId);
|
||||
if (cellsArray.length >= maxCells) break;
|
||||
|
||||
cells.c[cellId].forEach(neibCellId => {
|
||||
if (usedCells[neibCellId] || cells.r[neibCellId]) return;
|
||||
usedCells[neibCellId] = 1;
|
||||
queue.push(neibCellId);
|
||||
});
|
||||
}
|
||||
|
||||
const name = getAdjective(Names.getCultureShort(cells.culture[startCell])) + " Fault";
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Fault", cells: cellsArray, color: "url(#hatch2)"});
|
||||
}
|
||||
|
||||
function addFlood(usedCells) {
|
||||
const cells = pack.cells;
|
||||
|
||||
const fl = cells.fl.filter(Boolean);
|
||||
const meanFlux = d3.mean(fl);
|
||||
const maxFlux = d3.max(fl);
|
||||
const fluxThreshold = (maxFlux - meanFlux) / 2 + meanFlux;
|
||||
|
||||
const bigRiverCells = cells.i.filter(
|
||||
i => !usedCells[i] && cells.h[i] < 50 && cells.r[i] && cells.fl[i] > fluxThreshold && cells.burg[i]
|
||||
);
|
||||
if (!bigRiverCells.length) return;
|
||||
|
||||
const startCell = ra(bigRiverCells);
|
||||
usedCells[startCell] = 1;
|
||||
|
||||
const riverId = cells.r[startCell];
|
||||
const cellsArray = [];
|
||||
const queue = [startCell];
|
||||
const maxCells = rand(5, 30);
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = queue.pop();
|
||||
cellsArray.push(cellId);
|
||||
if (cellsArray.length >= maxCells) break;
|
||||
|
||||
cells.c[cellId].forEach(neibCellId => {
|
||||
if (
|
||||
usedCells[neibCellId] ||
|
||||
cells.h[neibCellId] < 20 ||
|
||||
cells.r[neibCellId] !== riverId ||
|
||||
cells.h[neibCellId] > 50 ||
|
||||
cells.fl[neibCellId] < meanFlux
|
||||
)
|
||||
return;
|
||||
usedCells[neibCellId] = 1;
|
||||
queue.push(neibCellId);
|
||||
});
|
||||
}
|
||||
|
||||
const name = getAdjective(pack.burgs[cells.burg[startCell]].name) + " Flood";
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Flood", cells: cellsArray, color: "url(#hatch13)"});
|
||||
}
|
||||
|
||||
function addTsunami(usedCells) {
|
||||
const {cells, features} = pack;
|
||||
|
||||
const coastalCells = cells.i.filter(
|
||||
i => !usedCells[i] && cells.t[i] === -1 && features[cells.f[i]].type !== "lake"
|
||||
);
|
||||
if (!coastalCells.length) return;
|
||||
|
||||
const startCell = ra(coastalCells);
|
||||
usedCells[startCell] = 1;
|
||||
|
||||
const cellsArray = [];
|
||||
const queue = [startCell];
|
||||
const maxCells = rand(10, 30);
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = queue.shift();
|
||||
if (cells.t[cellId] === 1) cellsArray.push(cellId);
|
||||
if (cellsArray.length >= maxCells) break;
|
||||
|
||||
cells.c[cellId].forEach(neibCellId => {
|
||||
if (usedCells[neibCellId]) return;
|
||||
if (cells.t[neibCellId] > 2) return;
|
||||
if (pack.features[cells.f[neibCellId]].type === "lake") return;
|
||||
usedCells[neibCellId] = 1;
|
||||
queue.push(neibCellId);
|
||||
});
|
||||
}
|
||||
|
||||
const name = getAdjective(Names.getCultureShort(cells.culture[startCell])) + " Tsunami";
|
||||
pack.zones.push({i: pack.zones.length, name, type: "Tsunami", cells: cellsArray, color: "url(#hatch13)"});
|
||||
}
|
||||
|
||||
return {generate};
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue