mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 12:01:23 +01:00
Refactoring in the style of 2.0
Merge pull request #1 from CanisArtorus/new-faith+update of five(8) commits
This commit is contained in:
commit
4a0f2f1337
9 changed files with 413 additions and 379 deletions
33
index.html
33
index.html
|
|
@ -3,14 +3,33 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
<title>Azgaar's Fantasy Map Generator</title>
|
<title>Azgaar's Fantasy Map Generator</title>
|
||||||
<meta name="application-name" content="Azgaar's Fantasy Map Generator" />
|
<meta name="application-name" content="Azgaar's Fantasy Map Generator" />
|
||||||
<meta name="author" content="Azgaar (Max Ganiev)" />
|
<meta name="author" content="Azgaar" />
|
||||||
<meta name="description" content="Azgaar's Fantasy Map Generator and Editor" />
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Free web app that helps fantasy writers, game masters, and cartographers create and edit fantasy maps"
|
||||||
|
/>
|
||||||
|
|
||||||
<meta property="og:url" content="https://azgaar.github.io/Fantasy-Map-Generator" />
|
<meta property="og:url" content="https://azgaar.github.io/Fantasy-Map-Generator" />
|
||||||
<meta property="og:title" content="Azgaar's Fantasy Map Generator" />
|
<meta property="og:title" content="Azgaar's Fantasy Map Generator" />
|
||||||
<meta property="og:description" content="Web application generating interactive and customizable maps" />
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Free web app that helps fantasy writers, game masters, and cartographers create and edit fantasy maps"
|
||||||
|
/>
|
||||||
<meta property="og:image" content="images/preview.png" />
|
<meta property="og:image" content="images/preview.png" />
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:domain" content="azgaar.github.io" />
|
||||||
|
<meta property="twitter:url" content="https://azgaar.github.io/Fantasy-Map-Generator/" />
|
||||||
|
<meta name="twitter:title" content="Azgaar's Fantasy Map Generator" />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="Free web app that helps fantasy writers, game masters, and cartographers create and edit fantasy maps"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:image" content="images/preview.png" />
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="images/icons/favicon-32x32.png" sizes="32x32" />
|
<link rel="icon" type="image/png" href="images/icons/favicon-32x32.png" sizes="32x32" />
|
||||||
<link rel="icon" type="image/png" href="images/icons/favicon-16x16.png" sizes="16x16" />
|
<link rel="icon" type="image/png" href="images/icons/favicon-16x16.png" sizes="16x16" />
|
||||||
<link rel="apple-touch-icon" href="images/icons/maskable_icon_x192.png" />
|
<link rel="apple-touch-icon" href="images/icons/maskable_icon_x192.png" />
|
||||||
|
|
@ -2749,7 +2768,7 @@
|
||||||
<div id="iceEditor" class="dialog" style="display: none">
|
<div id="iceEditor" class="dialog" style="display: none">
|
||||||
<button id="iceEditStyle" data-tip="Edit style in Style Editor" class="icon-brush"></button>
|
<button id="iceEditStyle" data-tip="Edit style in Style Editor" class="icon-brush"></button>
|
||||||
<button id="iceRandomize" data-tip="Randomize Iceberg shape" class="icon-shuffle"></button>
|
<button id="iceRandomize" data-tip="Randomize Iceberg shape" class="icon-shuffle"></button>
|
||||||
<input id="iceSize" data-tip="Change Iceberg size" type="range" min=".05" max="1" step=".01" />
|
<input id="iceSize" data-tip="Change Iceberg size" type="range" min=".05" max="2" step=".01" />
|
||||||
<button id="iceNew" data-tip="Add an Iceberg (click on map)" class="icon-plus"></button>
|
<button id="iceNew" data-tip="Add an Iceberg (click on map)" class="icon-plus"></button>
|
||||||
<button
|
<button
|
||||||
id="iceRemove"
|
id="iceRemove"
|
||||||
|
|
@ -7830,7 +7849,7 @@
|
||||||
<script src="utils/colorUtils.js"></script>
|
<script src="utils/colorUtils.js"></script>
|
||||||
<script src="utils/graphUtils.js?v=1.88.02"></script>
|
<script src="utils/graphUtils.js?v=1.88.02"></script>
|
||||||
<script src="utils/nodeUtils.js"></script>
|
<script src="utils/nodeUtils.js"></script>
|
||||||
<script src="utils/numberUtils.js"></script>
|
<script src="utils/numberUtils.js?v=1.89.08"></script>
|
||||||
<script src="utils/polyfills.js"></script>
|
<script src="utils/polyfills.js"></script>
|
||||||
<script src="utils/probabilityUtils.js?v=1.88.00"></script>
|
<script src="utils/probabilityUtils.js?v=1.88.00"></script>
|
||||||
<script src="utils/stringUtils.js"></script>
|
<script src="utils/stringUtils.js"></script>
|
||||||
|
|
@ -7841,7 +7860,7 @@
|
||||||
<script src="config/heightmap-templates.js"></script>
|
<script src="config/heightmap-templates.js"></script>
|
||||||
<script src="config/precreated-heightmaps.js"></script>
|
<script src="config/precreated-heightmaps.js"></script>
|
||||||
<script src="modules/heightmap-generator.js?v=1.88.00"></script>
|
<script src="modules/heightmap-generator.js?v=1.88.00"></script>
|
||||||
<script src="modules/ocean-layers.js?v=1.87.15"></script>
|
<script src="modules/ocean-layers.js?v=1.89.08"></script>
|
||||||
<script src="modules/river-generator.js"></script>
|
<script src="modules/river-generator.js"></script>
|
||||||
<script src="modules/lakes.js"></script>
|
<script src="modules/lakes.js"></script>
|
||||||
<script src="modules/names-generator.js?v=1.87.14"></script>
|
<script src="modules/names-generator.js?v=1.87.14"></script>
|
||||||
|
|
@ -7877,7 +7896,7 @@
|
||||||
<script defer src="modules/ui/elevation-profile.js"></script>
|
<script defer src="modules/ui/elevation-profile.js"></script>
|
||||||
<script defer src="modules/ui/temperature-graph.js"></script>
|
<script defer src="modules/ui/temperature-graph.js"></script>
|
||||||
<script defer src="modules/ui/routes-editor.js?v=1.89.04"></script>
|
<script defer src="modules/ui/routes-editor.js?v=1.89.04"></script>
|
||||||
<script defer src="modules/ui/ice-editor.js"></script>
|
<script defer src="modules/ui/ice-editor.js?v=1.89.08"></script>
|
||||||
<script defer src="modules/ui/lakes-editor.js?v=1.87.10"></script>
|
<script defer src="modules/ui/lakes-editor.js?v=1.87.10"></script>
|
||||||
<script defer src="modules/ui/coastline-editor.js"></script>
|
<script defer src="modules/ui/coastline-editor.js"></script>
|
||||||
<script defer src="modules/ui/labels-editor.js"></script>
|
<script defer src="modules/ui/labels-editor.js"></script>
|
||||||
|
|
|
||||||
1
main.js
1
main.js
|
|
@ -684,6 +684,7 @@ async function generate(options) {
|
||||||
const timeStart = performance.now();
|
const timeStart = performance.now();
|
||||||
const {seed: precreatedSeed, graph: precreatedGraph} = options || {};
|
const {seed: precreatedSeed, graph: precreatedGraph} = options || {};
|
||||||
|
|
||||||
|
pack = {};
|
||||||
invokeActiveZooming();
|
invokeActiveZooming();
|
||||||
setSeed(precreatedSeed);
|
setSeed(precreatedSeed);
|
||||||
INFO && console.group("Generated Map " + seed);
|
INFO && console.group("Generated Map " + seed);
|
||||||
|
|
|
||||||
|
|
@ -543,7 +543,7 @@ function drawReligionCenters() {
|
||||||
.attr("stroke", "#444444")
|
.attr("stroke", "#444444")
|
||||||
.style("cursor", "move");
|
.style("cursor", "move");
|
||||||
|
|
||||||
const data = pack.religions.filter(r => r.i && r.center && !r.type==="Folk" && !r.removed);
|
const data = pack.religions.filter(r => r.i && r.center && !r.removed);
|
||||||
religionCenters
|
religionCenters
|
||||||
.selectAll("circle")
|
.selectAll("circle")
|
||||||
.data(data)
|
.data(data)
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,12 @@ window.Religions = (function () {
|
||||||
Heresy: {Heresy: 1}
|
Heresy: {Heresy: 1}
|
||||||
};
|
};
|
||||||
|
|
||||||
const methods = {
|
const namingMethods = {
|
||||||
|
Folk: {
|
||||||
|
"Culture + type": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
Organized: {
|
||||||
"Random + type": 3,
|
"Random + type": 3,
|
||||||
"Random + ism": 1,
|
"Random + ism": 1,
|
||||||
"Supreme + ism": 5,
|
"Supreme + ism": 5,
|
||||||
|
|
@ -313,6 +318,20 @@ window.Religions = (function () {
|
||||||
"Culture + ism": 2,
|
"Culture + ism": 2,
|
||||||
"Place + ian + type": 6,
|
"Place + ian + type": 6,
|
||||||
"Culture + type": 4
|
"Culture + type": 4
|
||||||
|
},
|
||||||
|
|
||||||
|
Cult: {
|
||||||
|
"Burg + ian + type": 2,
|
||||||
|
"Random + ian + type": 1,
|
||||||
|
"Type + of the + meaning": 2
|
||||||
|
},
|
||||||
|
|
||||||
|
Heresy: {
|
||||||
|
"Burg + ian + type": 3,
|
||||||
|
"Random + ism": 3,
|
||||||
|
"Random + ian + type": 2,
|
||||||
|
"Type + of the + meaning": 1
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const types = {
|
const types = {
|
||||||
|
|
@ -342,340 +361,343 @@ window.Religions = (function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generate = function () {
|
const expansionismMap = {
|
||||||
|
Folk: () => 0,
|
||||||
|
Organized: () => gauss(5, 3, 0, 10, 1), // was rand(3, 8)
|
||||||
|
Cult: () => gauss(0.5, 0.5, 0, 5, 1), // was gauss(1.1, 0.5, 0, 5)
|
||||||
|
Heresy: () => gauss(1, 0.5, 0, 5, 1) // was gauss(1.2, 0.5, 0, 5)
|
||||||
|
};
|
||||||
|
|
||||||
|
function generate() {
|
||||||
TIME && console.time("generateReligions");
|
TIME && console.time("generateReligions");
|
||||||
const {cells, states, cultures} = pack;
|
// const {cells, states, cultures, burgs} = pack;
|
||||||
|
|
||||||
const religionIds = new Uint16Array(cells.culture); // cell religion; initially based on culture
|
const lockedReligions = pack.religions?.filter(religion => religion.locked && !religion.removed) || [];
|
||||||
const religions = [];
|
|
||||||
const codes = [];
|
|
||||||
|
|
||||||
// add folk religions
|
const folkReligions = generateFolkReligions();
|
||||||
cultures.forEach(c => {
|
const basicReligions = generateOrganizedReligions(+religionsInput.value, lockedReligions);
|
||||||
if (!c.i) return religions.push({i: 0, name: "No religion"});
|
|
||||||
|
|
||||||
if (c.removed) {
|
const namedReligions = specifyReligions([...folkReligions, ...basicReligions]);
|
||||||
religions.push({
|
const indexedReligions = combineReligions(namedReligions, lockedReligions);
|
||||||
i: c.i,
|
const religionIds = expandReligions(indexedReligions);
|
||||||
name: "Extinct religion for " + c.name,
|
const religions = defineOrigins(religionIds, indexedReligions);
|
||||||
color: getMixedColor(c.color, 0.1, 0),
|
|
||||||
removed: true
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newId = c.i;
|
|
||||||
|
|
||||||
if (pack.religions) {
|
|
||||||
const lockedFolkReligion = pack.religions.find(
|
|
||||||
r => r.culture === c.i && !r.removed && r.lock && r.type === "Folk"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (lockedFolkReligion) {
|
|
||||||
for (const i of cells.i) {
|
|
||||||
if (cells.religion[i] === lockedFolkReligion.i) religionIds[i] = newId;
|
|
||||||
}
|
|
||||||
|
|
||||||
lockedFolkReligion.i = newId;
|
|
||||||
religions.push(lockedFolkReligion);
|
|
||||||
codes.push(lockedFolkReligion.code);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newFolk = createFolk(c, codes);
|
|
||||||
codes.push(newFolk.code);
|
|
||||||
religions.push(newFolk);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (religionsInput.value == 0 || pack.cultures.length < 2) {
|
|
||||||
religions.filter(r => r.i).forEach(r => (r.code = abbreviate(r.name)));
|
|
||||||
cells.religion = religionIds;
|
|
||||||
pack.religions = religions;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
|
||||||
const sorted =
|
|
||||||
burgs.length > +religionsInput.value
|
|
||||||
? burgs.sort((a, b) => b.population - a.population).map(b => b.cell)
|
|
||||||
: cells.i.filter(i => cells.s[i] > 2).sort((a, b) => cells.s[b] - cells.s[a]);
|
|
||||||
|
|
||||||
const religionsTree = d3.quadtree();
|
|
||||||
const spacing = (graphWidth + graphHeight) / 6 / religionsInput.value; // base min distance between towns
|
|
||||||
const cultsCount = Math.floor((rand(10, 40) / 100) * religionsInput.value);
|
|
||||||
const count = +religionsInput.value - cultsCount + religions.length;
|
|
||||||
|
|
||||||
function getReligionsInRadius({x, y, r, max}) {
|
|
||||||
if (max === 0) return [0];
|
|
||||||
const cellsInRadius = findAll(x, y, r);
|
|
||||||
const religions = unique(cellsInRadius.map(i => religionIds[i]).filter(r => r));
|
|
||||||
return religions.length ? religions.slice(0, max) : [0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore locked non-folk religions
|
|
||||||
if (pack.religions) {
|
|
||||||
const lockedNonFolkReligions = pack.religions.filter(r => r.lock && !r.removed && r.type !== "Folk");
|
|
||||||
for (const religion of lockedNonFolkReligions) {
|
|
||||||
const newId = religions.length;
|
|
||||||
for (const i of cells.i) {
|
|
||||||
if (cells.religion[i] === religion.i) religionIds[i] = newId;
|
|
||||||
}
|
|
||||||
|
|
||||||
religion.i = newId;
|
|
||||||
religion.origins = religion.origins.filter(origin => origin < newId);
|
|
||||||
religionsTree.add(cells.p[religion.center]);
|
|
||||||
religions.push(religion);
|
|
||||||
codes.push(religion.code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cells.religion = religionIds;
|
|
||||||
|
|
||||||
// generate organized religions
|
|
||||||
for (let i = 0; religions.length < count && i < 1000; i++) {
|
|
||||||
let center = sorted[biased(0, sorted.length - 1, 5)]; // religion center
|
|
||||||
const form = rw(forms.Organized);
|
|
||||||
const state = cells.state[center];
|
|
||||||
const culture = cells.culture[center];
|
|
||||||
|
|
||||||
const deity = form === "Non-theism" ? null : getDeityName(culture);
|
|
||||||
let [name, expansion] = getReligionName(form, deity, center);
|
|
||||||
if (expansion === "state" && !state) expansion = "global";
|
|
||||||
if (expansion === "culture" && !culture) expansion = "global";
|
|
||||||
|
|
||||||
if (expansion === "state" && Math.random() > 0.5) center = states[state].center;
|
|
||||||
if (expansion === "culture" && Math.random() > 0.5) center = cultures[culture].center;
|
|
||||||
|
|
||||||
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c]))
|
|
||||||
center = cells.c[center].find(c => cells.burg[c]);
|
|
||||||
const [x, y] = cells.p[center];
|
|
||||||
|
|
||||||
const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform
|
|
||||||
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
|
|
||||||
|
|
||||||
// add "Old" to name of the folk religion on this culture
|
|
||||||
const isFolkBased = expansion === "culture" || P(0.5);
|
|
||||||
const folk = isFolkBased && religions.find(r => r.culture === culture && r.type === "Folk");
|
|
||||||
if (folk && expansion === "culture" && folk.name.slice(0, 3) !== "Old") folk.name = "Old " + folk.name;
|
|
||||||
|
|
||||||
const origins = folk ? [folk.i] : getReligionsInRadius({x, y, r: 150 / count, max: 2});
|
|
||||||
const expansionism = rand(3, 8);
|
|
||||||
const baseColor = religions[culture]?.color || states[state]?.color || getRandomColor();
|
|
||||||
const color = getMixedColor(baseColor, 0.3, 0);
|
|
||||||
const code = abbreviate(name, codes);
|
|
||||||
|
|
||||||
codes.push(code);
|
|
||||||
religions.push({
|
|
||||||
i: religions.length,
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
culture,
|
|
||||||
type: "Organized",
|
|
||||||
form,
|
|
||||||
deity,
|
|
||||||
expansion,
|
|
||||||
expansionism,
|
|
||||||
center,
|
|
||||||
origins,
|
|
||||||
code
|
|
||||||
});
|
|
||||||
religionsTree.add([x, y]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate cults
|
|
||||||
for (let i = 0; religions.length < count + cultsCount && i < 1000; i++) {
|
|
||||||
const form = rw(forms.Cult);
|
|
||||||
let center = sorted[biased(0, sorted.length - 1, 1)]; // religion center
|
|
||||||
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c]))
|
|
||||||
center = cells.c[center].find(c => cells.burg[c]);
|
|
||||||
const [x, y] = cells.p[center];
|
|
||||||
|
|
||||||
const s = spacing * gauss(2, 0.3, 1, 3, 2); // randomize to make the placement not uniform
|
|
||||||
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
|
|
||||||
|
|
||||||
const culture = cells.culture[center];
|
|
||||||
const origins = getReligionsInRadius({x, y, r: 300 / count, max: rand(0, 4)});
|
|
||||||
|
|
||||||
const deity = getDeityName(culture);
|
|
||||||
const name = getCultName(form, center);
|
|
||||||
const expansionism = gauss(1.1, 0.5, 0, 5);
|
|
||||||
const color = getMixedColor(cultures[culture].color, 0.5, 0); // "url(#hatch7)";
|
|
||||||
const code = abbreviate(name, codes);
|
|
||||||
codes.push(code);
|
|
||||||
religions.push({
|
|
||||||
i: religions.length,
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
culture,
|
|
||||||
type: "Cult",
|
|
||||||
form,
|
|
||||||
deity,
|
|
||||||
expansion: "global",
|
|
||||||
expansionism,
|
|
||||||
center,
|
|
||||||
origins,
|
|
||||||
code
|
|
||||||
});
|
|
||||||
religionsTree.add([x, y]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pack.religions = religions;
|
pack.religions = religions;
|
||||||
|
pack.cells.religion = religionIds;
|
||||||
expandReligions();
|
|
||||||
|
|
||||||
// generate heresies
|
|
||||||
religions
|
|
||||||
.filter(r => r.type === "Organized")
|
|
||||||
.forEach(r => {
|
|
||||||
if (r.expansionism < 3) return;
|
|
||||||
const count = gauss(0, 1, 0, 3);
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
let center = ra(cells.i.filter(i => religionIds[i] === r.i && cells.c[i].some(c => religionIds[c] !== r.i)));
|
|
||||||
if (!center) continue;
|
|
||||||
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c]))
|
|
||||||
center = cells.c[center].find(c => cells.burg[c]);
|
|
||||||
const [x, y] = cells.p[center];
|
|
||||||
if (religionsTree.find(x, y, spacing / 10) !== undefined) continue; // to close to other
|
|
||||||
|
|
||||||
const culture = cells.culture[center];
|
|
||||||
const name = getCultName("Heresy", center);
|
|
||||||
const expansionism = gauss(1.2, 0.5, 0, 5);
|
|
||||||
const color = getMixedColor(r.color, 0.4, 0.2); // "url(#hatch6)";
|
|
||||||
const code = abbreviate(name, codes);
|
|
||||||
codes.push(code);
|
|
||||||
pack.religions.push({
|
|
||||||
i: pack.religions.length,
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
culture,
|
|
||||||
type: "Heresy",
|
|
||||||
form: r.form,
|
|
||||||
deity: r.deity,
|
|
||||||
expansion: "global",
|
|
||||||
expansionism,
|
|
||||||
center,
|
|
||||||
origins: [r.i],
|
|
||||||
code
|
|
||||||
});
|
|
||||||
religionsTree.add([x, y]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expandHeresies();
|
|
||||||
|
|
||||||
checkCenters();
|
|
||||||
|
|
||||||
TIME && console.timeEnd("generateReligions");
|
TIME && console.timeEnd("generateReligions");
|
||||||
};
|
};
|
||||||
|
|
||||||
function createFolk(c, codes) {
|
function generateFolkReligions() {
|
||||||
|
return pack.cultures.filter(c => c.i && !c.removed).map(culture => {
|
||||||
|
const {i: culutreId, center} = culture;
|
||||||
const form = rw(forms.Folk);
|
const form = rw(forms.Folk);
|
||||||
const name = c.name + " " + rw(types[form]);
|
|
||||||
const deity = form === "Animism" ? null : getDeityName(c.i);
|
return {type:"Folk", form, culture: culutreId, center};
|
||||||
const color = getMixedColor(c.color, 0.1, 0);
|
});
|
||||||
const code = abbreviate(name, codes);
|
}
|
||||||
return {
|
|
||||||
i: c.i,
|
function generateOrganizedReligions(desiredReligionNumber, lockedReligions) {
|
||||||
name,
|
const lockedReligionCount = lockedReligions.filter(({type}) => type !== "Folk").length || 0;
|
||||||
color,
|
const requiredReligionsNumber = desiredReligionNumber - lockedReligionCount;
|
||||||
culture: c.i,
|
if (requiredReligionsNumber < 1) return [];
|
||||||
type: "Folk",
|
|
||||||
form,
|
const candidateCells = getCandidateCells();
|
||||||
deity,
|
const religionCores = placeReligions();
|
||||||
center: c.center,
|
|
||||||
origins: [0],
|
const cultsCount = Math.floor((rand(1, 4) / 10) * religionCores.length); // 10 - 40%
|
||||||
code
|
const heresiesCount = Math.floor((rand(0, 2) / 10) * religionCores.length); // 0 - 20%, was gauss(0,1, 0,3) per organized with expansionism >= 3
|
||||||
|
const organizedCount = religionCores.length - cultsCount - heresiesCount;
|
||||||
|
|
||||||
|
const getType = (index) => {
|
||||||
|
if (index < organizedCount) return "Organized";
|
||||||
|
if (index < organizedCount + cultsCount) return "Cult";
|
||||||
|
return "Heresy";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return religionCores.map((cellId, index) => {
|
||||||
|
const type = getType(index);
|
||||||
|
const form = rw(forms[type]);
|
||||||
|
const cultureId = cells.culture[cellId];
|
||||||
|
|
||||||
|
return {type, form, culture: cultureId, center: cellId};
|
||||||
|
});
|
||||||
|
|
||||||
|
function placeReligions() {
|
||||||
|
const religionCells = [];
|
||||||
|
const religionsTree = d3.quadtree();
|
||||||
|
|
||||||
|
// pre-populate with locked centers
|
||||||
|
lockedReligions.forEach(({center}) => religionsTree.add(cells.p[center]));
|
||||||
|
|
||||||
|
// min distance between religion inceptions
|
||||||
|
const spacing = (graphWidth + graphHeight) / 2 / desiredReligionNumber; // was major gauss(1,0.3, 0.2,2, 2) / 6; cult gauss(2,0.3, 1,3, 2) /6; heresy /60
|
||||||
|
|
||||||
|
for (const cellId of candidateCells) { // was biased random major ^5, cult ^1
|
||||||
|
const [x, y] = cells.p[cellId];
|
||||||
|
|
||||||
|
if (religionsTree.find(x, y, spacing) === undefined) {
|
||||||
|
religionCells.push(cellId);
|
||||||
|
religionsTree.add([x,y]);
|
||||||
|
|
||||||
|
if (religionCells.length === requiredReligionsNumber) return religionCells;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WARN && console.warn(`Placed only ${religionCells.length} of ${requiredReligionsNumber} religions`);
|
||||||
|
return religionCells;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCandidateCells() {
|
||||||
|
const validBurgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||||
|
|
||||||
|
if (validBurgs.length >= requiredReligionsNumber)
|
||||||
|
return validBurgs.sort((a, b) => b.population - a.population).map(burg => burg.cell);
|
||||||
|
return cells.i.filter(i=> cells.s[i] > 2).sort((a, b) => cells.s[b] - cells.s[a]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function specifyReligions(newReligions) {
|
||||||
|
const {cells, cultures} = pack;
|
||||||
|
|
||||||
|
const rawReligions = newReligions.map(({type, form, culture: cultureId, center}) => {
|
||||||
|
const supreme = getDeityName(cultureId);
|
||||||
|
const deity = form === "Non-theism" || form === "Animism" ? null : supreme;
|
||||||
|
|
||||||
|
const stateId = cells.state[center];
|
||||||
|
|
||||||
|
let {name, expansion} = generateReligionName(type, form, supreme, center);
|
||||||
|
if (expansion === "state" && !stateId) expansion = "global";
|
||||||
|
|
||||||
|
const expansionism = expansionismMap[type]();
|
||||||
|
|
||||||
|
const color = getReligionColor(cultures[cultureId], type);
|
||||||
|
|
||||||
|
return {name, type, form, culture: cultureId, center, deity, expansion, expansionism, color};
|
||||||
|
});
|
||||||
|
|
||||||
|
return rawReligions;
|
||||||
|
|
||||||
|
function getReligionColor(culture, type) {
|
||||||
|
if (!culture.i) ERROR && console.error(`Culture ${culture.i} is not a valid culture`);
|
||||||
|
|
||||||
|
if (type === "Folk") return culture.color;
|
||||||
|
if (type === "Heresy") return getMixedColor(culture.color, 0.35, 0.2);
|
||||||
|
if (type === "Cult") return getMixedColor(culture.color, 0.5, 0);
|
||||||
|
return getMixedColor(culture.color, 0.25, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexes, conditionally renames, and abbreviates religions
|
||||||
|
function combineReligions(namedReligions, lockedReligions) {
|
||||||
|
const noReligion = {i: 0, name: "No religion"};
|
||||||
|
const indexedReligions = [noReligion];
|
||||||
|
|
||||||
|
const {lockedReligionQueue, highestLockedIndex, codes} = parseLockedReligions();
|
||||||
|
const maxIndex = Math.max(highestLockedIndex, namedReligions.length + lockedReligions.length + 1);
|
||||||
|
|
||||||
|
for (let index = 1, progress = 0; index < maxIndex; index++) {
|
||||||
|
// place locked religion back at its old index
|
||||||
|
if (index === lockedReligionQueue[0]?.i) {
|
||||||
|
const nextReligion = lockedReligionQueue.shift();
|
||||||
|
indexedReligions.push(nextReligion);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// slot the new religions
|
||||||
|
if (progress < namedReligions.length) {
|
||||||
|
const nextReligion = namedReligions[progress];
|
||||||
|
progress++;
|
||||||
|
if (nextReligion.type === "Folk" && lockedReligions.some(
|
||||||
|
({type, culture}) => type === "Folk" && culture === nextReligion.culture
|
||||||
|
)) continue; // when there is a locked Folk religion for this culture discard duplicate
|
||||||
|
|
||||||
|
const newName = renameOld(nextReligion);
|
||||||
|
const code = abbreviate(newName, codes);
|
||||||
|
codes.push(code);
|
||||||
|
indexedReligions.push({...nextReligion, i: index, name: newName, code});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
indexedReligions.push({i: index, name: "Padding", removed: true});
|
||||||
|
}
|
||||||
|
return indexedReligions;
|
||||||
|
|
||||||
|
function parseLockedReligions() {
|
||||||
|
const lockedReligionQueue = lockedReligions.map(r => r).sort((a, b) => a.i - b.i);
|
||||||
|
const highestLockedIndex = Math.max(lockedReligions.map(r => r.i));
|
||||||
|
|
||||||
|
const codes = lockedReligions.length > 0 ? lockedReligions.map(r => r.code) : [];
|
||||||
|
|
||||||
|
return {lockedReligionQueue, highestLockedIndex, codes};
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend 'Old' to names of folk religions which have organized competitors
|
||||||
|
function renameOld({name, type, culture: cultureId}) {
|
||||||
|
if (type !== "Folk") return name;
|
||||||
|
|
||||||
|
const haveOrganized = namedReligions.some(
|
||||||
|
({type, culture, expansion}) => culture === cultureId && type === "Organized" && expansion === "culture")
|
||||||
|
|| lockedReligions.some(({type, culture, expansion}) => culture === cultureId && type === "Organized" && expansion === "culture");
|
||||||
|
if (haveOrganized && name.slice(0, 3) !== "Old") return `Old ${name}`;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally generate and stores origins trees
|
||||||
|
function defineOrigins(religionIds, indexedReligions) {
|
||||||
|
const religionOriginsParamsMap = {
|
||||||
|
Organized: {clusterSize: 100, maxReligions: 2}, // was 150/count, 2
|
||||||
|
Cult: {clusterSize: 50, maxReligions: 3}, // was 300/count, rand(0,4)
|
||||||
|
Heresy: {clusterSize: 50, maxReligions: 4}
|
||||||
|
};
|
||||||
|
|
||||||
|
const origins = indexedReligions.map((religion, index) => {
|
||||||
|
if (religion.type === "Folk") return [0];
|
||||||
|
if (index === 0) return null;
|
||||||
|
|
||||||
|
const {i, type, culture: cultureId, expansion, center} = religion;
|
||||||
|
|
||||||
|
const folkReligion = indexedReligions.find(({culture, type}) => type === "Folk" && culture === cultureId);
|
||||||
|
const isFolkBased = folkReligion && cultureId && expansion === "culture" && each(2)(center); // P(0.5) -> isEven cellId
|
||||||
|
|
||||||
|
if (isFolkBased) return [folkReligion.i];
|
||||||
|
|
||||||
|
const {clusterSize, maxReligions} = religionOriginsParamsMap[type];
|
||||||
|
const origins = getReligionsInRadius(pack.cells.c, center, religionIds, i, clusterSize, maxReligions);
|
||||||
|
|
||||||
|
if (origins === [0]) return [folkReligion.i]; // hegemony has local roots
|
||||||
|
return origins;
|
||||||
|
});
|
||||||
|
|
||||||
|
return indexedReligions.map((religion, index) => ({
|
||||||
|
...religion,
|
||||||
|
origins: origins[index]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReligionsInRadius(neighbors, center, religionIds, religionId, clusterSize, maxReligions) {
|
||||||
|
const foundReligions = new Set();
|
||||||
|
const queue = [center];
|
||||||
|
const checked = {};
|
||||||
|
|
||||||
|
for (let size = 0; queue.length && size < clusterSize; size++) {
|
||||||
|
const cellId = queue.shift();
|
||||||
|
checked[cellId] = true;
|
||||||
|
|
||||||
|
for (const neibId of neighbors[cellId]) {
|
||||||
|
if (checked[neibId]) continue;
|
||||||
|
checked[neibId] = true;
|
||||||
|
|
||||||
|
const neibReligion = religionIds[neibId];
|
||||||
|
if (neibReligion && neibReligion !== religionId) foundReligions.add(neibReligion);
|
||||||
|
queue.push(neibId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundReligions.size ? [...foundReligions].slice(0, maxReligions) : [0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// growth algorithm to assign cells to religions
|
// growth algorithm to assign cells to religions
|
||||||
function expandReligions() {
|
function expandReligions(religions) {
|
||||||
|
const cells = pack.cells;
|
||||||
|
const religionIds = spreadFolkReligions(religions);
|
||||||
|
|
||||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||||
const cost = [];
|
const cost = [];
|
||||||
const religionIds = pack.cells.religion;
|
|
||||||
const {cells, religions} = pack;
|
const maxExpansionCost = (cells.i.length / 20) * gauss(1, 0.3, 0.2, 2, 2) * neutralInput.value; // limit cost for organized religions growth (was /25, heresy /10)
|
||||||
|
|
||||||
|
const biomePassageCost = (cellId) => biomesData.cost[cells.biome[cellId]];
|
||||||
|
|
||||||
religions
|
religions
|
||||||
.filter(r => !r.lock && (r.type === "Organized" || r.type === "Cult"))
|
.filter(r => r.i && !r.lock && r.type !== "Folk" && !r.removed)
|
||||||
.forEach(r => {
|
.forEach(r => {
|
||||||
religionIds[r.center] = r.i;
|
religionIds[r.center] = r.i;
|
||||||
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center], c: r.culture});
|
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center]});
|
||||||
cost[r.center] = 1;
|
cost[r.center] = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
const neutral = (cells.i.length / 5000) * 200 * gauss(1, 0.3, 0.2, 2, 2) * neutralInput.value; // limit cost for organized religions growth
|
const religionsMap = new Map(religions.map(r => [r.i, r]));
|
||||||
const popCost = d3.max(cells.pop) / 3; // enougth population to spered religion without penalty
|
|
||||||
|
const isMainRoad = (cellId) => (cells.road[cellId] - cells.crossroad[cellId]) > 4;
|
||||||
|
const isTrail = (cellId) => cells.h[cellId] > 19 && (cells.road[cellId] - cells.crossroad[cellId]) === 1;
|
||||||
|
const isSeaRoute = (cellId) => cells.h[cellId] < 20 && cells.road[cellId];
|
||||||
|
const isWater = (cellId) => cells.h[cellId] < 20;
|
||||||
|
// const popCost = d3.max(cells.pop) / 3; // enougth population to spered religion without penalty
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const {e, p, r, c, s} = queue.dequeue();
|
const {e: cellId, p, r, s: state} = queue.dequeue();
|
||||||
const expansion = religions[r].expansion;
|
const {culture, expansion, expansionism} = religionsMap.get(r);
|
||||||
|
|
||||||
cells.c[e].forEach(nextCell => {
|
cells.c[cellId].forEach(nextCell => {
|
||||||
if (expansion === "culture" && c !== cells.culture[nextCell]) return;
|
if (expansion === "culture" && culture !== cells.culture[nextCell]) return;
|
||||||
if (expansion === "state" && s !== cells.state[nextCell]) return;
|
if (expansion === "state" && state !== cells.state[nextCell]) return;
|
||||||
if (religions[religionIds[nextCell]]?.lock) return;
|
if (religionsMap.get(religionIds[nextCell])?.lock) return;
|
||||||
|
|
||||||
const cultureCost = c !== cells.culture[nextCell] ? 10 : 0;
|
const cultureCost = culture !== cells.culture[nextCell] ? 10 : 0;
|
||||||
const stateCost = s !== cells.state[nextCell] ? 10 : 0;
|
const stateCost = state !== cells.state[nextCell] ? 10 : 0;
|
||||||
const biomeCost = cells.road[nextCell] ? 1 : biomesData.cost[cells.biome[nextCell]];
|
const passageCost = getPassageCost(nextCell);
|
||||||
const populationCost = Math.max(rn(popCost - cells.pop[nextCell]), 0);
|
// const populationCost = Math.max(rn(popCost - cells.pop[nextCell]), 0);
|
||||||
const heightCost = Math.max(cells.h[nextCell], 20) - 20;
|
// const heightCost = Math.max(cells.h[nextCell], 20) - 20;
|
||||||
const waterCost = cells.h[nextCell] < 20 ? (cells.road[nextCell] ? 50 : 1000) : 0;
|
|
||||||
const totalCost =
|
const cellCost = cultureCost + stateCost + passageCost;
|
||||||
p +
|
const totalCost = p + 10 + cellCost / expansionism;
|
||||||
(cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism;
|
if (totalCost > maxExpansionCost) return;
|
||||||
if (totalCost > neutral) return;
|
|
||||||
|
|
||||||
if (!cost[nextCell] || totalCost < cost[nextCell]) {
|
if (!cost[nextCell] || totalCost < cost[nextCell]) {
|
||||||
if (cells.h[nextCell] >= 20 && cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
|
if (cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
|
||||||
cost[nextCell] = totalCost;
|
cost[nextCell] = totalCost;
|
||||||
queue.queue({e: nextCell, p: totalCost, r, c, s});
|
|
||||||
|
queue.queue({e: nextCell, p: totalCost, r, s});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return religionIds;
|
||||||
|
|
||||||
|
function getPassageCost(cellId) {
|
||||||
|
if (isWater(cellId)) return isSeaRoute ? 50 : 500; // was 50 : 1000
|
||||||
|
if (isMainRoad(cellId)) return 1;
|
||||||
|
const biomeCost = biomePassageCost(cellId);
|
||||||
|
return (isTrail(cellId)) ? biomeCost / 1.5 : biomeCost; // was same as main road
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// growth algorithm to assign cells to heresies
|
// folk religions initially get all cells of their culture, and locked religions are retained
|
||||||
function expandHeresies() {
|
function spreadFolkReligions(religions) {
|
||||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
const religionIds = new Uint16Array(cells.i.length);
|
||||||
const cost = [];
|
|
||||||
const religionIds = pack.cells.religion;
|
|
||||||
const {cells, religions} = pack;
|
|
||||||
|
|
||||||
religions
|
const folkReligions = religions.filter(religion => religion.type === "Folk" && !religion.removed);
|
||||||
.filter(r => !r.lock && r.type === "Heresy")
|
const cultureToReligionMap = new Map(folkReligions.map(({i, culture}) => [culture, i]));
|
||||||
.forEach(r => {
|
|
||||||
const b = religionIds[r.center]; // "base" religion id
|
|
||||||
religionIds[r.center] = r.i; // heresy id
|
|
||||||
queue.queue({e: r.center, p: 0, r: r.i, b});
|
|
||||||
cost[r.center] = 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth
|
for (const cellId of cells.i) {
|
||||||
|
const oldId = cells.religion[cellId] || 0;
|
||||||
while (queue.length) {
|
if (oldId && religions[oldId]?.lock && !religions[oldId]?.removed) {
|
||||||
const {e, p, r, b} = queue.dequeue();
|
religionIds[cellId] = oldId;
|
||||||
|
continue;
|
||||||
cells.c[e].forEach(nextCell => {
|
|
||||||
if (religions[religionIds[nextCell]]?.lock) return;
|
|
||||||
const religionCost = religionIds[nextCell] === b ? 0 : 2000;
|
|
||||||
const biomeCost = cells.road[nextCell] ? 0 : biomesData.cost[cells.biome[nextCell]];
|
|
||||||
const heightCost = Math.max(cells.h[nextCell], 20) - 20;
|
|
||||||
const waterCost = cells.h[nextCell] < 20 ? (cells.road[nextCell] ? 50 : 1000) : 0;
|
|
||||||
const totalCost =
|
|
||||||
p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1);
|
|
||||||
|
|
||||||
if (totalCost > neutral) return;
|
|
||||||
|
|
||||||
if (!cost[nextCell] || totalCost < cost[nextCell]) {
|
|
||||||
if (cells.h[nextCell] >= 20 && cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
|
|
||||||
cost[nextCell] = totalCost;
|
|
||||||
queue.queue({e: nextCell, p: totalCost, r});
|
|
||||||
}
|
}
|
||||||
|
const cultureId = cells.culture[cellId];
|
||||||
|
religionIds[cellId] = cultureToReligionMap.get(cultureId) || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return religionIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkCenters() {
|
||||||
|
religions.forEach(r => {
|
||||||
|
if (!r.i) return;
|
||||||
|
// move religion center if it's not within religion area after expansion
|
||||||
|
if (religionIds[r.center] === r.i) return; // in area
|
||||||
|
const firstCell = cells.i.find(i => religionIds[i] === r.i);
|
||||||
|
const cultureHome = pack.cultures[r.culture]?.center;
|
||||||
|
if (firstCell) r.center = firstCell; // move center, othervise it's an extinct religion
|
||||||
|
else if (type === "Folk" && cultureHome) r.center = cultureHome;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function recalculate() {
|
||||||
|
const newReligionIds = expandReligions(pack.religions);
|
||||||
|
pack.cells.religion = newReligionIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCenters() {
|
function checkCenters() {
|
||||||
|
|
@ -708,45 +730,47 @@ window.Religions = (function () {
|
||||||
const {cells, religions} = pack;
|
const {cells, religions} = pack;
|
||||||
const religionId = cells.religion[center];
|
const religionId = cells.religion[center];
|
||||||
|
|
||||||
const culture = cells.culture[center];
|
const cultureId = cells.culture[center];
|
||||||
const color = getMixedColor(religions[religionId].color, 0.3, 0);
|
const color = getMixedColor(religions[religionId].color, 0.3, 0);
|
||||||
|
const missingFolk = !religions.some(({type, culture}) => type === "Folk" && culture === cultureId);
|
||||||
|
|
||||||
const type =
|
const type =
|
||||||
religions[religionId].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2}) : rw({Organized: 5, Cult: 2});
|
missingFolk ? "Folk" :
|
||||||
|
religions[religionId].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2})
|
||||||
|
: rw({Organized: 5, Cult: 2});
|
||||||
const form = rw(forms[type]);
|
const form = rw(forms[type]);
|
||||||
const deity =
|
const deity =
|
||||||
type === "Heresy" ? religions[religionId].deity : form === "Non-theism" ? null : getDeityName(culture);
|
type === "Heresy" ? religions[religionId].deity :
|
||||||
|
(form === "Non-theism" || form === "Animism") ? null
|
||||||
|
: getDeityName(cultureId);
|
||||||
|
|
||||||
let name, expansion;
|
const {name, expansion} = generateReligionName(type, form, deity, center);
|
||||||
if (type === "Organized") [name, expansion] = getReligionName(form, deity, center);
|
|
||||||
else {
|
|
||||||
name = getCultName(form, center);
|
|
||||||
expansion = "global";
|
|
||||||
}
|
|
||||||
|
|
||||||
const formName = type === "Heresy" ? religions[religionId].form : form;
|
const formName = type === "Heresy" ? religions[religionId].form : form;
|
||||||
const code = abbreviate(
|
const code = abbreviate(
|
||||||
name,
|
name,
|
||||||
religions.map(r => r.code)
|
religions.map(r => r.code)
|
||||||
);
|
);
|
||||||
|
const influences = getReligionsInRadius(cells.c, center, cells.religion, 0, 25, 3);
|
||||||
|
const origins = type === "Folk" ? [0] : influences;
|
||||||
|
|
||||||
const i = religions.length;
|
const i = religions.length;
|
||||||
religions.push({
|
religions.push({
|
||||||
i,
|
i,
|
||||||
name,
|
name,
|
||||||
color,
|
color,
|
||||||
culture,
|
culture: cultureId,
|
||||||
type,
|
type,
|
||||||
form: formName,
|
form: formName,
|
||||||
deity,
|
deity,
|
||||||
expansion,
|
expansion,
|
||||||
expansionism: 0,
|
expansionism: expansionismMap[type](),
|
||||||
center,
|
center,
|
||||||
cells: 0,
|
cells: 0,
|
||||||
area: 0,
|
area: 0,
|
||||||
rural: 0,
|
rural: 0,
|
||||||
urban: 0,
|
urban: 0,
|
||||||
origins: [religionId],
|
origins,
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
cells.religion[center] = i;
|
cells.religion[center] = i;
|
||||||
|
|
@ -887,22 +911,24 @@ window.Religions = (function () {
|
||||||
if (a === "Number") return ra(base.number);
|
if (a === "Number") return ra(base.number);
|
||||||
if (a === "Being") return ra(base.being);
|
if (a === "Being") return ra(base.being);
|
||||||
if (a === "Adjective") return ra(base.adjective);
|
if (a === "Adjective") return ra(base.adjective);
|
||||||
if (a === "Color + Animal") return ra(base.color) + " " + ra(base.animal);
|
if (a === "Color + Animal") return `${ra(base.color)} ${ra(base.animal)}`;
|
||||||
if (a === "Adjective + Animal") return ra(base.adjective) + " " + ra(base.animal);
|
if (a === "Adjective + Animal") return `${ra(base.adjective)} ${ra(base.animal)}`;
|
||||||
if (a === "Adjective + Being") return ra(base.adjective) + " " + ra(base.being);
|
if (a === "Adjective + Being") return `${ra(base.adjective)} ${ra(base.being)}`;
|
||||||
if (a === "Adjective + Genitive") return ra(base.adjective) + " " + ra(base.genitive);
|
if (a === "Adjective + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`;
|
||||||
if (a === "Color + Being") return ra(base.color) + " " + ra(base.being);
|
if (a === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`;
|
||||||
if (a === "Color + Genitive") return ra(base.color) + " " + ra(base.genitive);
|
if (a === "Color + Genitive") return `${ra(base.color)} ${ra(base.genitive)}`;
|
||||||
if (a === "Being + of + Genitive") return ra(base.being) + " of " + ra(base.genitive);
|
if (a === "Being + of + Genitive") return `${ra(base.being)} of ${ra(base.genitive)}`;
|
||||||
if (a === "Being + of the + Genitive") return ra(base.being) + " of the " + ra(base.theGenitive);
|
if (a === "Being + of the + Genitive") return `${ra(base.being)} of the ${ra(base.theGenitive)}`;
|
||||||
if (a === "Animal + of + Genitive") return ra(base.animal) + " of " + ra(base.genitive);
|
if (a === "Animal + of + Genitive") return `${ra(base.animal)} of ${ra(base.genitive)}`;
|
||||||
if (a === "Adjective + Being + of + Genitive")
|
if (a === "Adjective + Being + of + Genitive")
|
||||||
return ra(base.adjective) + " " + ra(base.being) + " of " + ra(base.genitive);
|
return `${ra(base.adjective)} ${ra(base.being)} of ${ra(base.genitive)}`;
|
||||||
if (a === "Adjective + Animal + of + Genitive")
|
if (a === "Adjective + Animal + of + Genitive")
|
||||||
return ra(base.adjective) + " " + ra(base.animal) + " of " + ra(base.genitive);
|
return `${ra(base.adjective)} ${ra(base.animal)} of ${ra(base.genitive)}`;
|
||||||
|
|
||||||
|
ERROR && console.error("Unkown generation approach");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReligionName(form, deity, center) {
|
function generateReligionName(variety, form, deity, center) {
|
||||||
const {cells, cultures, burgs, states} = pack;
|
const {cells, cultures, burgs, states} = pack;
|
||||||
|
|
||||||
const random = () => Names.getCulture(cells.culture[center], null, null, "", 0);
|
const random = () => Names.getCulture(cells.culture[center], null, null, "", 0);
|
||||||
|
|
@ -918,7 +944,7 @@ window.Religions = (function () {
|
||||||
return adj ? getAdjective(name) : name;
|
return adj ? getAdjective(name) : name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const m = rw(methods);
|
const m = rw(namingMethods[variety]);
|
||||||
if (m === "Random + type") return [random() + " " + type(), "global"];
|
if (m === "Random + type") return [random() + " " + type(), "global"];
|
||||||
if (m === "Random + ism") return [trimVowels(random()) + "ism", "global"];
|
if (m === "Random + ism") return [trimVowels(random()) + "ism", "global"];
|
||||||
if (m === "Supreme + ism" && deity) return [trimVowels(supreme()) + "ism", "global"];
|
if (m === "Supreme + ism" && deity) return [trimVowels(supreme()) + "ism", "global"];
|
||||||
|
|
@ -928,24 +954,11 @@ window.Religions = (function () {
|
||||||
if (m === "Culture + ism") return [trimVowels(culture()) + "ism", "culture"];
|
if (m === "Culture + ism") return [trimVowels(culture()) + "ism", "culture"];
|
||||||
if (m === "Place + ian + type") return [place("adj") + " " + type(), "state"];
|
if (m === "Place + ian + type") return [place("adj") + " " + type(), "state"];
|
||||||
if (m === "Culture + type") return [culture() + " " + type(), "culture"];
|
if (m === "Culture + type") return [culture() + " " + type(), "culture"];
|
||||||
|
if (m === "Burg + ian + type") return [`${place("adj")} ${type()}`, "global"];
|
||||||
|
if (m === "Random + ian + type") return [`${getAdjective(random())} ${type()}`, "global"];
|
||||||
|
if (m === "Type + of the + meaning") return [`${type()} of the ${generateMeaning()}`, "global"];
|
||||||
return [trimVowels(random()) + "ism", "global"]; // else
|
return [trimVowels(random()) + "ism", "global"]; // else
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCultName(form, center) {
|
return {generate, add, getDeityName, updateCultures, recalculate};
|
||||||
const cells = pack.cells;
|
|
||||||
const type = function () {
|
|
||||||
return rw(types[form]);
|
|
||||||
};
|
|
||||||
const random = function () {
|
|
||||||
return trimVowels(Names.getCulture(cells.culture[center], null, null, "", 0).split(/[ ,]+/)[0]);
|
|
||||||
};
|
|
||||||
const burg = function () {
|
|
||||||
return trimVowels(pack.burgs[cells.burg[center]].name.split(/[ ,]+/)[0]);
|
|
||||||
};
|
|
||||||
if (cells.burg[center]) return burg() + "ian " + type();
|
|
||||||
if (Math.random() > 0.5) return random() + "ian " + type();
|
|
||||||
return type() + " of the " + generateMeaning();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {generate, add, addFolk, getDeityName, updateCultures, recalculate};
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,11 @@ function editIce() {
|
||||||
function addIcebergOnClick() {
|
function addIcebergOnClick() {
|
||||||
const [x, y] = d3.mouse(this);
|
const [x, y] = d3.mouse(this);
|
||||||
const i = findGridCell(x, y, grid);
|
const i = findGridCell(x, y, grid);
|
||||||
const c = grid.points[i];
|
const [cx, cy] = grid.points[i];
|
||||||
const s = +document.getElementById("iceSize").value;
|
const size = +document.getElementById("iceSize")?.value || 1;
|
||||||
|
|
||||||
const points = getGridPolygon(i).map(p => [(p[0] + (c[0] - p[0]) / s) | 0, (p[1] + (c[1] - p[1]) / s) | 0]);
|
const points = getGridPolygon(i).map(([x, y]) => [rn(lerp(cx, x, size), 2), rn(lerp(cy, y, size), 2)]);
|
||||||
const iceberg = ice.append("polygon").attr("points", points).attr("cell", i).attr("size", s);
|
const iceberg = ice.append("polygon").attr("points", points).attr("cell", i).attr("size", size);
|
||||||
iceberg.call(d3.drag().on("drag", dragElement));
|
iceberg.call(d3.drag().on("drag", dragElement));
|
||||||
if (d3.event.shiftKey === false) toggleAdd();
|
if (d3.event.shiftKey === false) toggleAdd();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -671,11 +671,10 @@ function toggleIce(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawIce() {
|
function drawIce() {
|
||||||
const cells = grid.cells,
|
const {cells, vertices} = grid;
|
||||||
vertices = grid.vertices,
|
const {temp, h} = cells;
|
||||||
n = cells.i.length,
|
const n = cells.i.length;
|
||||||
temp = cells.temp,
|
|
||||||
h = cells.h;
|
|
||||||
const used = new Uint8Array(cells.i.length);
|
const used = new Uint8Array(cells.i.length);
|
||||||
Math.random = aleaPRNG(seed);
|
Math.random = aleaPRNG(seed);
|
||||||
|
|
||||||
|
|
@ -700,23 +699,22 @@ function drawIce() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tNormalized = normalize(t, -8, 2);
|
||||||
|
const randomFactor = t > -5 ? 0.4 + rand() * 1.2 : 1;
|
||||||
|
|
||||||
// mildly cold: iceberd
|
// mildly cold: iceberd
|
||||||
if (P(normalize(t, -7, 2.5))) continue; // t[-5; 2] cold: skip some cells
|
if (P(tNormalized ** 0.5 * randomFactor)) continue; // cold: skip some cells
|
||||||
if (grid.features[cells.f[i]].type === "lake") continue; // lake: no icebers
|
if (grid.features[cells.f[i]].type === "lake") continue; // lake: no icebers
|
||||||
let size = (6.5 + t) / 10; // iceberg size: 0 = full size, 1 = zero size
|
|
||||||
if (cells.t[i] === -1) size *= 1.3; // coasline: smaller icebers
|
let size = 1 - tNormalized; // iceberg size: 0 = zero size, 1 = full size
|
||||||
size = Math.min(size * (0.4 + rand() * 1.2), 0.95); // randomize iceberg size
|
if (cells.t[i] === -1) size /= 1.3; // coasline: smaller icebers
|
||||||
resizePolygon(i, size);
|
resizePolygon(i, minmax(rn(size * randomFactor, 2), 0.08, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resizePolygon(i, s) {
|
function resizePolygon(i, size) {
|
||||||
const c = grid.points[i];
|
const [cx, cy] = grid.points[i];
|
||||||
const points = getGridPolygon(i).map(p => [(p[0] + (c[0] - p[0]) * s) | 0, (p[1] + (c[1] - p[1]) * s) | 0]);
|
const points = getGridPolygon(i).map(([x, y]) => [rn(lerp(cx, x, size), 2), rn(lerp(cy, y, size), 2)]);
|
||||||
ice
|
ice.append("polygon").attr("points", points).attr("cell", i).attr("size", size);
|
||||||
.append("polygon")
|
|
||||||
.attr("points", points)
|
|
||||||
.attr("cell", i)
|
|
||||||
.attr("size", rn(1 - s, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect vertices to chain
|
// connect vertices to chain
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,7 @@ function lim(v) {
|
||||||
function normalize(val, min, max) {
|
function normalize(val, min, max) {
|
||||||
return minmax((val - min) / (max - min), 0, 1);
|
return minmax((val - min) / (max - min), 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lerp(a, b, t) {
|
||||||
|
return a + (b - a) * t;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// version and caching control
|
// version and caching control
|
||||||
const version = "1.90.00"; // generator version, update each time
|
const version = "1.89.08"; // generator version, update each time
|
||||||
|
|
||||||
{
|
{
|
||||||
document.title += " v" + version;
|
document.title += " v" + version;
|
||||||
|
|
@ -28,8 +28,7 @@ const version = "1.90.00"; // generator version, update each time
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<strong>Latest changes:</strong>
|
<strong>Latest changes:</strong>
|
||||||
<li>Religions update by center and expansionism value</li>
|
<li>Religions can be edited and redrawn like cultures</li>
|
||||||
<li>Adding, removing, and regenerating cultures also affect the linked folk religion</li>
|
|
||||||
<li>Lock states, provinces, cultures, and religions from regeneration</li>
|
<li>Lock states, provinces, cultures, and religions from regeneration</li>
|
||||||
<li>Heightmap brushes: linear edit option</li>
|
<li>Heightmap brushes: linear edit option</li>
|
||||||
<li>Data Charts screen</li>
|
<li>Data Charts screen</li>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue