Religions live update UX (#915)

* preparatory bugfixes

* restructure religion generator for extensibility

* Add culture-like update functions and UI

* Syntax / Typo

* PR Versioning

* Relig Editor UI: hide on brush, limit -> potential

* add / remove Culture creates / removes Folk religion

* Religions UX: stable abbr, Folk non-selectable type & not separately removed

* Folk religions follow cultures through regeneration; extinct religion centers draw, folk centers are immutable and do not

* style

* regenerate bugs fixing

* when cultures regenerate a different number

* ability to lock culture in a easy-defined set

fixes uncaught error in commit dada419 from PR #910

* what is locked folk?

* New Map ignores old locked things

* No auto-update of religion on culture regeneration

* typo

* Bugfixes

* Refactor toward modern style

* Prepare to lock - Further refactor

* Complete locked religions functionality

* Prepare for live updates

* Editors fixing + re-versioning

* Fix merge snafu

* Bugs, toungue-twisting

* indexing bugfix

* Manual Add prefers to create missing Folk

* Locked origin regen; deterministic expansion

* version update; fix add wild folk

---------

Co-authored-by: A C James <james.ander31@gmail.com>
Co-authored-by: Azgaar <maxganiev@yandex.ru>
This commit is contained in:
Alexander James 2023-03-19 03:57:59 -06:00 committed by GitHub
parent efb5ad8091
commit 47ef8d938b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 511 additions and 394 deletions

View file

@ -7860,10 +7860,10 @@
<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>
<script src="modules/cultures-generator.js?v=1.89.07"></script> <script src="modules/cultures-generator.js?v=1.89.10"></script>
<script src="modules/burgs-and-states.js?v=1.89.07"></script> <script src="modules/burgs-and-states.js?v=1.89.07"></script>
<script src="modules/routes-generator.js"></script> <script src="modules/routes-generator.js"></script>
<script src="modules/religions-generator.js?v=1.89.07"></script> <script src="modules/religions-generator.js?v=1.89.10"></script>
<script src="modules/military-generator.js"></script> <script src="modules/military-generator.js"></script>
<script src="modules/markers-generator.js?v=1.87.13"></script> <script src="modules/markers-generator.js?v=1.87.13"></script>
<script src="modules/coa-generator.js"></script> <script src="modules/coa-generator.js"></script>
@ -7877,12 +7877,12 @@
<script src="modules/ui/stylePresets.js?v=1.89.11"></script> <script src="modules/ui/stylePresets.js?v=1.89.11"></script>
<script src="modules/ui/general.js?v=1.87.03"></script> <script src="modules/ui/general.js?v=1.87.03"></script>
<script src="modules/ui/options.js?v=1.88.10"></script> <script src="modules/ui/options.js?v=1.88.12"></script>
<script src="main.js?v=1.89.05"></script> <script src="main.js?v=1.89.12"></script>
<script defer src="modules/relief-icons.js"></script> <script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/style.js"></script> <script defer src="modules/ui/style.js"></script>
<script defer src="modules/ui/editors.js?v=1.89.09"></script> <script defer src="modules/ui/editors.js?v=1.89.12"></script>
<script defer src="modules/ui/tools.js?v=1.89.00"></script> <script defer src="modules/ui/tools.js?v=1.89.00"></script>
<script defer src="modules/ui/world-configurator.js"></script> <script defer src="modules/ui/world-configurator.js"></script>
<script defer src="modules/ui/heightmap-editor.js?v=1.89.06"></script> <script defer src="modules/ui/heightmap-editor.js?v=1.89.06"></script>

View file

@ -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);
@ -694,6 +695,7 @@ async function generate(options) {
if (shouldRegenerateGrid(grid, precreatedSeed)) grid = precreatedGraph || generateGrid(); if (shouldRegenerateGrid(grid, precreatedSeed)) grid = precreatedGraph || generateGrid();
else delete grid.cells.h; else delete grid.cells.h;
grid.cells.h = await HeightmapGenerator.generate(grid); grid.cells.h = await HeightmapGenerator.generate(grid);
pack = {};
markFeatures(); markFeatures();
markupGridOcean(); markupGridOcean();

View file

@ -118,21 +118,23 @@ window.Cultures = (function () {
function selectCultures(culturesNumber) { function selectCultures(culturesNumber) {
let def = getDefault(culturesNumber); let def = getDefault(culturesNumber);
if (culturesNumber === def.length) return def;
if (def.every(d => d.odd === 1)) return def.splice(0, culturesNumber);
const count = Math.min(culturesNumber, def.length);
const cultures = []; const cultures = [];
pack.cultures?.forEach(function (culture) { pack.cultures?.forEach(function (culture) {
if (culture.lock) cultures.push(culture); if (culture.lock) cultures.push(culture);
}); });
if (!cultures.length) {
if (culturesNumber === def.length) return def;
if (def.every(d => d.odd === 1)) return def.splice(0, culturesNumber);
}
for (let culture, rnd, i = 0; cultures.length < count && i < 200; i++) { for (let culture, rnd, i = 0; cultures.length < culturesNumber && def.length > 0;) {
do { do {
rnd = rand(def.length - 1); rnd = rand(def.length - 1);
culture = def[rnd]; culture = def[rnd];
} while (!P(culture.odd)); i++;
} while (i < 200 && !P(culture.odd));
cultures.push(culture); cultures.push(culture);
def.splice(rnd, 1); def.splice(rnd, 1);
} }

View file

@ -3,11 +3,11 @@ addListeners();
export function open() { export function open() {
closeDialogs("#religionsEditor, .stable"); closeDialogs("#religionsEditor, .stable");
if (!layerIsOn("toggleReligions")) toggleReligions();
if (layerIsOn("toggleStates")) toggleStates(); if (layerIsOn("toggleStates")) toggleStates();
if (layerIsOn("toggleBiomes")) toggleBiomes(); if (layerIsOn("toggleBiomes")) toggleBiomes();
if (layerIsOn("toggleCultures")) toggleReligions(); if (layerIsOn("toggleCultures")) toggleCultures();
if (layerIsOn("toggleProvinces")) toggleProvinces(); if (layerIsOn("toggleProvinces")) toggleProvinces();
if (!layerIsOn("toggleReligions")) toggleReligions();
refreshReligionsEditor(); refreshReligionsEditor();
drawReligionCenters(); drawReligionCenters();
@ -23,13 +23,15 @@ export function open() {
function insertEditorHtml() { function insertEditorHtml() {
const editorHtml = /* html */ `<div id="religionsEditor" class="dialog stable"> const editorHtml = /* html */ `<div id="religionsEditor" class="dialog stable">
<div id="religionsHeader" class="header" style="grid-template-columns: 13em 6em 7em 18em 5em 6em"> <div id="religionsHeader" class="header" style="grid-template-columns: 13em 6em 7em 18em 5em 6em 6em 8em">
<div data-tip="Click to sort by religion name" class="sortable alphabetically" data-sortby="name">Religion&nbsp;</div> <div data-tip="Click to sort by religion name" class="sortable alphabetically" data-sortby="name">Religion&nbsp;</div>
<div data-tip="Click to sort by religion type" class="sortable alphabetically icon-sort-name-down" data-sortby="type">Type&nbsp;</div> <div data-tip="Click to sort by religion type" class="sortable alphabetically icon-sort-name-down" data-sortby="type">Type&nbsp;</div>
<div data-tip="Click to sort by religion form" class="sortable alphabetically hide" data-sortby="form">Form&nbsp;</div> <div data-tip="Click to sort by religion form" class="sortable alphabetically hide" data-sortby="form">Form&nbsp;</div>
<div data-tip="Click to sort by supreme deity" class="sortable alphabetically hide" data-sortby="deity">Supreme Deity&nbsp;</div> <div data-tip="Click to sort by supreme deity" class="sortable alphabetically hide" data-sortby="deity">Supreme Deity&nbsp;</div>
<div data-tip="Click to sort by religion area" class="sortable hide" data-sortby="area">Area&nbsp;</div> <div data-tip="Click to sort by religion area" class="sortable hide" data-sortby="area">Area&nbsp;</div>
<div data-tip="Click to sort by number of believers (religion area population)" class="sortable hide" data-sortby="population">Believers&nbsp;</div> <div data-tip="Click to sort by number of believers (religion area population)" class="sortable hide" data-sortby="population">Believers&nbsp;</div>
<div data-tip="Click to sort by potential extent type" class="sortable hide" data-sortby="expansion">Potential&nbsp;</div>
<div data-tip="Click to sort by expansionism" class="sortable hide" data-sortby="expansionism">Expansion&nbsp;</div>
</div> </div>
<div id="religionsBody" class="table" data-type="absolute"></div> <div id="religionsBody" class="table" data-type="absolute"></div>
@ -88,6 +90,11 @@ function insertEditorHtml() {
</div> </div>
<button id="religionsAdd" data-tip="Add a new religion. Hold Shift to add multiple" class="icon-plus"></button> <button id="religionsAdd" data-tip="Add a new religion. Hold Shift to add multiple" class="icon-plus"></button>
<button id="religionsExport" data-tip="Download religions-related data" class="icon-download"></button> <button id="religionsExport" data-tip="Download religions-related data" class="icon-download"></button>
<button id="religionsRecalculate" data-tip="Recalculate religions based on current values of growth-related attributes" class="icon-retweet"></button>
<span data-tip="Allow religion center, extent, and expansionism changes to take an immediate effect">
<input id="religionsAutoChange" class="checkbox" type="checkbox" />
<label for="religionsAutoChange" class="checkbox-label"><i>auto-apply changes</i></label>
</span>
</div> </div>
</div>`; </div>`;
@ -109,6 +116,7 @@ function addListeners() {
byId("religionsManuallyCancel").on("click", () => exitReligionsManualAssignment()); byId("religionsManuallyCancel").on("click", () => exitReligionsManualAssignment());
byId("religionsAdd").on("click", enterAddReligionMode); byId("religionsAdd").on("click", enterAddReligionMode);
byId("religionsExport").on("click", downloadReligionsCsv); byId("religionsExport").on("click", downloadReligionsCsv);
byId("religionsRecalculate").on("click", () => recalculateReligions(true));
} }
function refreshReligionsEditor() { function refreshReligionsEditor() {
@ -166,6 +174,7 @@ function religionsEditorAddLines() {
data-type="" data-type=""
data-form="" data-form=""
data-deity="" data-deity=""
data-expansion=""
data-expansionism="" data-expansionism=""
> >
<svg width="11" height="11" class="placeholder"></svg> <svg width="11" height="11" class="placeholder"></svg>
@ -181,6 +190,9 @@ function religionsEditorAddLines() {
<div data-tip="Religion area" class="religionArea hide" style="width: 5em">${si(area) + unit}</div> <div data-tip="Religion area" class="religionArea hide" style="width: 5em">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="religionPopulation hide pointer">${si(population)}</div> <div data-tip="${populationTip}" class="religionPopulation hide pointer">${si(population)}</div>
<select data-tip="Potential religion extent" class="religionExtent placeholder hide" style="width: 5em"></select>
<span class="icon-resize-full placeholder hide"></span>
<input class="religionExpan placeholder hide" type="number" />
</div>`; </div>`;
continue; continue;
} }
@ -195,6 +207,7 @@ function religionsEditorAddLines() {
data-type="${r.type}" data-type="${r.type}"
data-form="${r.form}" data-form="${r.form}"
data-deity="${r.deity || ""}" data-deity="${r.deity || ""}"
data-expansion="${r.expansion}"
data-expansionism="${r.expansionism}" data-expansionism="${r.expansionism}"
> >
<fill-box fill="${r.color}"></fill-box> <fill-box fill="${r.color}"></fill-box>
@ -212,8 +225,9 @@ function religionsEditorAddLines() {
<div data-tip="Religion area" class="religionArea hide" style="width: 5em">${si(area) + unit}</div> <div data-tip="Religion area" class="religionArea hide" style="width: 5em">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="religionPopulation hide pointer">${si(population)}</div> <div data-tip="${populationTip}" class="religionPopulation hide pointer">${si(population)}</div>
${getExpansionColumns(r)}
<span <span
data-tip="Lock religion, will regenerate the origin folk and organized religion if they are not also locked" data-tip="Lock this religion"
class="icon-lock${r.lock ? "" : "-open"} hide" class="icon-lock${r.lock ? "" : "-open"} hide"
></span> ></span>
<span data-tip="Remove religion" class="icon-trash-empty hide"></span> <span data-tip="Remove religion" class="icon-trash-empty hide"></span>
@ -245,6 +259,8 @@ function religionsEditorAddLines() {
$body.querySelectorAll("div > input.religionDeity").forEach(el => el.on("input", religionChangeDeity)); $body.querySelectorAll("div > input.religionDeity").forEach(el => el.on("input", religionChangeDeity));
$body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.on("click", regenerateDeity)); $body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.on("click", regenerateDeity));
$body.querySelectorAll("div > div.religionPopulation").forEach(el => el.on("click", changePopulation)); $body.querySelectorAll("div > div.religionPopulation").forEach(el => el.on("click", changePopulation));
$body.querySelectorAll("div > select.religionExtent").forEach(el => el.on("change", religionChangeExtent));
$body.querySelectorAll("div > input.religionExpan").forEach(el => el.on("change", religionChangeExpansionism));
$body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", religionRemovePrompt)); $body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", religionRemovePrompt));
$body.querySelectorAll("div > span.icon-lock").forEach($el => $el.on("click", updateLockStatus)); $body.querySelectorAll("div > span.icon-lock").forEach($el => $el.on("click", updateLockStatus));
$body.querySelectorAll("div > span.icon-lock-open").forEach($el => $el.on("click", updateLockStatus)); $body.querySelectorAll("div > span.icon-lock-open").forEach($el => $el.on("click", updateLockStatus));
@ -264,6 +280,36 @@ function getTypeOptions(type) {
return options; return options;
} }
function getExpansionColumns(r) {
if (r.type === "Folk")
return `<span data-tip="Potential religion extent" class="religionExtent hide" style="width: 5em">
culture
</span>
<span class="icon-resize-full placeholder hide"></span>
<input class="religionExpan placeholder hide" type="number" />`
else
return `<select data-tip="Potential religion extent" class="religionExtent hide" style="width: 5em">
${getExtentOptions(r.expansion)}
</select>
<span data-tip="Religion expansionism. Defines competitive size" class="icon-resize-full hide"></span>
<input
data-tip="Religion expansionism. Defines competitive size. Click to change, then click Recalculate to apply change"
class="religionExpan hide"
type="number"
min="0"
max="99"
step=".1"
value=${r.expansionism}
/>`
}
function getExtentOptions(type) {
let options = "";
const types = ["global", "state", "culture"];
types.forEach(t => (options += `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`));
return options;
}
const religionHighlightOn = debounce(event => { const religionHighlightOn = debounce(event => {
const religionId = Number(event.id || event.target.dataset.id); const religionId = Number(event.id || event.target.dataset.id);
const $el = $body.querySelector(`div[data-id='${religionId}']`); const $el = $body.querySelector(`div[data-id='${religionId}']`);
@ -434,6 +480,20 @@ function changePopulation() {
} }
} }
function religionChangeExtent() {
const religion = +this.parentNode.dataset.id;
this.parentNode.dataset.expansion = this.value;
pack.religions[religion].expansion = this.value;
recalculateReligions();
}
function religionChangeExpansionism() {
const religion = +this.parentNode.dataset.id;
this.parentNode.dataset.expansionism = this.value;
pack.religions[religion].expansionism = +this.value;
recalculateReligions();
}
function religionRemovePrompt() { function religionRemovePrompt() {
if (customization) return; if (customization) return;
@ -475,7 +535,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.cells && !r.removed); const data = pack.religions.filter(r => r.i && r.center && !r.removed);
religionCenters religionCenters
.selectAll("circle") .selectAll("circle")
.data(data) .data(data)
@ -507,6 +567,7 @@ function religionCenterDrag() {
const cell = findCell(x, y); const cell = findCell(x, y);
if (pack.cells.h[cell] < 20) return; // ignore dragging on water if (pack.cells.h[cell] < 20) return; // ignore dragging on water
pack.religions[religionId].center = cell; pack.religions[religionId].center = cell;
recalculateReligions();
}); });
} }
@ -584,7 +645,7 @@ function enterReligionsManualAssignent() {
if (!layerIsOn("toggleReligions")) toggleReligions(); if (!layerIsOn("toggleReligions")) toggleReligions();
customization = 7; customization = 7;
relig.append("g").attr("id", "temp"); relig.append("g").attr("id", "temp");
document.querySelectorAll("#religionsBottom > button").forEach(el => (el.style.display = "none")); document.querySelectorAll("#religionsBottom > *").forEach(el => (el.style.display = "none"));
byId("religionsManuallyButtons").style.display = "inline-block"; byId("religionsManuallyButtons").style.display = "inline-block";
debug.select("#religionCenters").style("display", "none"); debug.select("#religionCenters").style("display", "none");
@ -686,7 +747,7 @@ function exitReligionsManualAssignment(close) {
customization = 0; customization = 0;
relig.select("#temp").remove(); relig.select("#temp").remove();
removeCircle(); removeCircle();
document.querySelectorAll("#religionsBottom > button").forEach(el => (el.style.display = "inline-block")); document.querySelectorAll("#religionsBottom > *").forEach(el => (el.style.display = "inline-block"));
byId("religionsManuallyButtons").style.display = "none"; byId("religionsManuallyButtons").style.display = "none";
byId("religionsEditor") byId("religionsEditor")
@ -740,15 +801,15 @@ function addReligion() {
function downloadReligionsCsv() { function downloadReligionsCsv() {
const unit = getAreaUnit("2"); const unit = getAreaUnit("2");
const headers = `Id,Name,Color,Type,Form,Supreme Deity,Area ${unit},Believers,Origins`; const headers = `Id,Name,Color,Type,Form,Supreme Deity,Area ${unit},Believers,Origins,Potential,Expansionism`;
const lines = Array.from($body.querySelectorAll(":scope > div")); const lines = Array.from($body.querySelectorAll(":scope > div"));
const data = lines.map($line => { const data = lines.map($line => {
const {id, name, color, type, form, deity, area, population} = $line.dataset; const {id, name, color, type, form, deity, area, population, expansion, expansionism} = $line.dataset;
const deityText = '"' + deity + '"'; const deityText = '"' + deity + '"';
const {origins} = pack.religions[+id]; const {origins} = pack.religions[+id];
const originList = (origins || []).filter(origin => origin).map(origin => pack.religions[origin].name); const originList = (origins || []).filter(origin => origin).map(origin => pack.religions[origin].name);
const originText = '"' + originList.join(", ") + '"'; const originText = '"' + originList.join(", ") + '"';
return [id, name, color, type, form, deityText, area, population, originText].join(","); return [id, name, color, type, form, deityText, area, population, originText, expansion, expansionism].join(",");
}); });
const csvData = [headers].concat(data).join("\n"); const csvData = [headers].concat(data).join("\n");
@ -773,3 +834,13 @@ function updateLockStatus() {
classList.toggle("icon-lock-open"); classList.toggle("icon-lock-open");
classList.toggle("icon-lock"); classList.toggle("icon-lock");
} }
function recalculateReligions(must) {
if (!must && !religionsAutoChange.checked) return;
Religions.recalculate();
drawReligions();
refreshReligionsEditor();
drawReligionCenters();
}

View file

@ -304,15 +304,34 @@ window.Religions = (function () {
Heresy: {Heresy: 1} Heresy: {Heresy: 1}
}; };
const methods = { const namingMethods = {
"Random + type": 3, Folk: {
"Random + ism": 1, "Culture + type": 1
"Supreme + ism": 5, },
"Faith of + Supreme": 5,
"Place + ism": 1, Organized: {
"Culture + ism": 2, "Random + type": 3,
"Place + ian + type": 6, "Random + ism": 1,
"Culture + type": 4 "Supreme + ism": 5,
"Faith of + Supreme": 5,
"Place + ism": 1,
"Culture + ism": 2,
"Place + ian + type": 6,
"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,372 +361,405 @@ 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.lock && !religion.removed) || [];
const religions = [];
// 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 pack.religions = religions;
}); pack.cells.religion = religionIds;
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);
return;
}
}
const form = rw(forms.Folk);
const name = c.name + " " + rw(types[form]);
const deity = form === "Animism" ? null : getDeityName(c.i);
const color = getMixedColor(c.color, 0.1, 0);
religions.push({
i: newId,
name,
color,
culture: newId,
type: "Folk",
form,
deity,
center: c.center,
origins: [0]
});
});
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);
}
}
// 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);
religions.push({
i: religions.length,
name,
color,
culture,
type: "Organized",
form,
deity,
expansion,
expansionism,
center,
origins
});
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)";
religions.push({
i: religions.length,
name,
color,
culture,
type: "Cult",
form,
deity,
expansion: "global",
expansionism,
center,
origins
});
religionsTree.add([x, y]);
}
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)";
religions.push({
i: religions.length,
name,
color,
culture,
type: "Heresy",
form: r.form,
deity: r.deity,
expansion: "global",
expansionism,
center,
origins: [r.i]
});
religionsTree.add([x, y]);
}
});
expandHeresies();
checkCenters(); checkCenters();
cells.religion = religionIds;
pack.religions = religions;
TIME && console.timeEnd("generateReligions"); TIME && console.timeEnd("generateReligions");
// growth algorithm to assign cells to religions
function expandReligions() {
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = [];
religions
.filter(r => !r.lock && (r.type === "Organized" || r.type === "Cult"))
.forEach(r => {
religionIds[r.center] = r.i;
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center], c: r.culture});
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 popCost = d3.max(cells.pop) / 3; // enougth population to spered religion without penalty
while (queue.length) {
const {e, p, r, c, s} = queue.dequeue();
const expansion = religions[r].expansion;
cells.c[e].forEach(nextCell => {
if (expansion === "culture" && c !== cells.culture[nextCell]) return;
if (expansion === "state" && s !== cells.state[nextCell]) return;
if (religions[religionIds[nextCell]]?.lock) return;
const cultureCost = c !== cells.culture[nextCell] ? 10 : 0;
const stateCost = s !== cells.state[nextCell] ? 10 : 0;
const biomeCost = cells.road[nextCell] ? 1 : biomesData.cost[cells.biome[nextCell]];
const populationCost = Math.max(rn(popCost - cells.pop[nextCell]), 0);
const heightCost = Math.max(cells.h[nextCell], 20) - 20;
const waterCost = cells.h[nextCell] < 20 ? (cells.road[nextCell] ? 50 : 1000) : 0;
const totalCost =
p +
(cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism;
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, c, s});
}
});
}
}
// growth algorithm to assign cells to heresies
function expandHeresies() {
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = [];
religions
.filter(r => !r.lock && r.type === "Heresy")
.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
while (queue.length) {
const {e, p, r, b} = queue.dequeue();
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});
}
});
}
}
function checkCenters() {
const codes = religions.map(r => r.code);
religions.forEach(r => {
if (!r.i) return;
r.code = abbreviate(r.name, codes);
// 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);
if (firstCell) r.center = firstCell; // move center, othervise it's an extinct religion
});
}
}; };
const add = function (center) { function generateFolkReligions() {
const {cells, religions} = pack; return pack.cultures.filter(c => c.i && !c.removed).map(culture => {
const religionId = cells.religion[center]; const {i: culutreId, center} = culture;
const form = rw(forms.Folk);
const culture = cells.culture[center]; return {type:"Folk", form, culture: culutreId, center};
const color = getMixedColor(religions[religionId].color, 0.3, 0); });
}
const type = function generateOrganizedReligions(desiredReligionNumber, lockedReligions) {
religions[religionId].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2}) : rw({Organized: 5, Cult: 2}); const cells = pack.cells;
const form = rw(forms[type]); const lockedReligionCount = lockedReligions.filter(({type}) => type !== "Folk").length || 0;
const deity = const requiredReligionsNumber = desiredReligionNumber - lockedReligionCount;
type === "Heresy" ? religions[religionId].deity : form === "Non-theism" ? null : getDeityName(culture); if (requiredReligionsNumber < 1) return [];
let name, expansion; const candidateCells = getCandidateCells();
if (type === "Organized") [name, expansion] = getReligionName(form, deity, center); const religionCores = placeReligions();
else {
name = getCultName(form, center); const cultsCount = Math.floor((rand(1, 4) / 10) * religionCores.length); // 10 - 40%
expansion = "global"; 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, numberLockedFolk} = parseLockedReligions();
const maxIndex = Math.max(highestLockedIndex, namedReligions.length + lockedReligions.length + 1 - numberLockedFolk);
for (let index = 1, progress = 0; index < maxIndex; index = indexedReligions.length) {
// 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, type: "Folk", culture: 0, name: "Padding", removed: true});
}
return indexedReligions;
function parseLockedReligions() {
// copy and sort the locked religions list
const lockedReligionQueue = lockedReligions.map(religion => {
// and filter their origins to locked religions
let newOrigin = religion.origins.filter(n => lockedReligions.some(({i: index}) => index === n));
if (newOrigin === []) newOrigin = [0];
return {...religion, origins: newOrigin};
}).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) : [];
const numberLockedFolk = lockedReligions.filter(({type}) => type === "Folk").length;
return {lockedReligionQueue, highestLockedIndex, codes, numberLockedFolk};
}
// 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
function expandReligions(religions) {
const cells = pack.cells;
const religionIds = spreadFolkReligions(religions);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = [];
const maxExpansionCost = (cells.i.length / 20) * neutralInput.value; // limit cost for organized religions growth (was /25, heresy /10)
const biomePassageCost = (cellId) => biomesData.cost[cells.biome[cellId]];
religions
.filter(r => r.i && !r.lock && r.type !== "Folk" && !r.removed)
.forEach(r => {
religionIds[r.center] = r.i;
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center]});
cost[r.center] = 1;
});
const religionsMap = new Map(religions.map(r => [r.i, r]));
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) {
const {e: cellId, p, r, s: state} = queue.dequeue();
const {culture, expansion, expansionism} = religionsMap.get(r);
cells.c[cellId].forEach(nextCell => {
if (expansion === "culture" && culture !== cells.culture[nextCell]) return;
if (expansion === "state" && state !== cells.state[nextCell]) return;
if (religionsMap.get(religionIds[nextCell])?.lock) return;
const cultureCost = culture !== cells.culture[nextCell] ? 10 : 0;
const stateCost = state !== cells.state[nextCell] ? 10 : 0;
const passageCost = getPassageCost(nextCell);
// const populationCost = Math.max(rn(popCost - cells.pop[nextCell]), 0);
// const heightCost = Math.max(cells.h[nextCell], 20) - 20;
const cellCost = cultureCost + stateCost + passageCost;
const totalCost = p + 10 + cellCost / expansionism;
if (totalCost > maxExpansionCost) return;
if (!cost[nextCell] || totalCost < cost[nextCell]) {
if (cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
cost[nextCell] = totalCost;
queue.queue({e: nextCell, p: totalCost, r, s: state});
}
});
}
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
}
}
// folk religions initially get all cells of their culture, and locked religions are retained
function spreadFolkReligions(religions) {
const cells = pack.cells;
const hasPrior = cells.religion && true;
const religionIds = new Uint16Array(cells.i.length);
const folkReligions = religions.filter(religion => religion.type === "Folk" && !religion.removed);
const cultureToReligionMap = new Map(folkReligions.map(({i, culture}) => [culture, i]));
for (const cellId of cells.i) {
const oldId = (hasPrior && cells.religion[cellId]) || 0;
if (oldId && religions[oldId]?.lock && !religions[oldId]?.removed) {
religionIds[cellId] = oldId;
continue;
}
const cultureId = cells.culture[cellId];
religionIds[cellId] = cultureToReligionMap.get(cultureId) || 0;
}
return religionIds;
}
function checkCenters() {
const cells = pack.cells;
pack.religions.forEach(r => {
if (!r.i) return;
// move religion center if it's not within religion area after expansion
if (cells.religion[r.center] === r.i) return; // in area
const firstCell = cells.i.find(i => cells.religion[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 (r.type === "Folk" && cultureHome) r.center = cultureHome; // reset extinct culture centers
});
}
function recalculate() {
const newReligionIds = expandReligions(pack.religions);
pack.cells.religion = newReligionIds;
checkCenters();
}
const add = function (center) {
const {cells, cultures, religions} = pack;
const religionId = cells.religion[center];
const cultureId = cells.culture[center];
const missingFolk = cultureId !== 0 && !religions.some(({type, culture, removed}) => type === "Folk" && culture === cultureId && !removed);
const color = missingFolk ? cultures[cultureId].color
: getMixedColor(religions[religionId].color, 0.3, 0);
const type =
missingFolk ? "Folk" :
religions[religionId].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2})
: rw({Organized: 5, Cult: 2});
const form = rw(forms[type]);
const deity =
type === "Heresy" ? religions[religionId].deity :
(form === "Non-theism" || form === "Animism") ? null
: getDeityName(cultureId);
const [name, expansion] = generateReligionName(type, form, deity, center);
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;
@ -736,22 +788,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);
@ -767,7 +821,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"];
@ -777,24 +831,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, getDeityName, updateCultures};
})(); })();

View file

@ -1188,6 +1188,6 @@ async function editCultures() {
async function editReligions() { async function editReligions() {
if (customization) return; if (customization) return;
const Editor = await import("../dynamic/editors/religions-editor.js?v=1.88.07"); const Editor = await import("../dynamic/editors/religions-editor.js?v=1.89.10");
Editor.open(); Editor.open();
} }

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
// version and caching control // version and caching control
const version = "1.89.11"; // generator version, update each time const version = "1.89.12"; // generator version, update each time
{ {
document.title += " v" + version; document.title += " v" + version;
@ -28,6 +28,7 @@ const version = "1.89.11"; // generator version, update each time
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<li>Religions can be edited and redrawn like cultures</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>