mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 12:01:23 +01:00
commit
8fd9b82554
30 changed files with 1761 additions and 613 deletions
|
|
@ -359,7 +359,7 @@ window.BurgsAndStates = (function () {
|
|||
TIME && console.timeEnd("drawBurgs");
|
||||
};
|
||||
|
||||
// growth algorithm to assign cells to states like we did for cultures
|
||||
// expand cultures across the map (Dijkstra-like algorithm)
|
||||
const expandStates = function () {
|
||||
TIME && console.time("expandStates");
|
||||
const {cells, states, cultures, burgs} = pack;
|
||||
|
|
@ -367,18 +367,28 @@ window.BurgsAndStates = (function () {
|
|||
cells.state = cells.state || new Uint16Array(cells.i.length);
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
const neutral = (cells.i.length / 5000) * 2500 * neutralInput.value * statesNeutral; // limit cost for state growth
|
||||
|
||||
states
|
||||
.filter(s => s.i && !s.removed)
|
||||
.forEach(s => {
|
||||
const capitalCell = burgs[s.capital].cell;
|
||||
cells.state[capitalCell] = s.i;
|
||||
const cultureCenter = cultures[s.culture].center;
|
||||
const b = cells.biome[cultureCenter]; // state native biome
|
||||
queue.queue({e: s.center, p: 0, s: s.i, b});
|
||||
cost[s.center] = 1;
|
||||
});
|
||||
const globalNeutralRate = byId("neutralInput")?.valueAsNumber || 1;
|
||||
const statesNeutralRate = byId("statesNeutral")?.valueAsNumber || 1;
|
||||
const neutral = (cells.i.length / 2) * globalNeutralRate * statesNeutralRate; // limit cost for state growth
|
||||
|
||||
// remove state from all cells except of locked
|
||||
for (const cellId of cells.i) {
|
||||
const state = states[cells.state[cellId]];
|
||||
if (state.lock) continue;
|
||||
cells.state[cellId] = 0;
|
||||
}
|
||||
|
||||
for (const state of states) {
|
||||
if (!state.i || state.removed) continue;
|
||||
|
||||
const capitalCell = burgs[state.capital].cell;
|
||||
cells.state[capitalCell] = state.i;
|
||||
const cultureCenter = cultures[state.culture].center;
|
||||
const b = cells.biome[cultureCenter]; // state native biome
|
||||
queue.queue({e: state.center, p: 0, s: state.i, b});
|
||||
cost[state.center] = 1;
|
||||
}
|
||||
|
||||
while (queue.length) {
|
||||
const next = queue.dequeue();
|
||||
|
|
@ -608,7 +618,7 @@ window.BurgsAndStates = (function () {
|
|||
if (list && !list.includes(state.i)) continue;
|
||||
|
||||
byId(`stateLabel${state.i}`)?.remove();
|
||||
byId(`textPath_stateLabel6${state.i}`)?.remove();
|
||||
byId(`textPath_stateLabel${state.i}`)?.remove();
|
||||
}
|
||||
|
||||
const example = g.append("text").attr("x", 0).attr("x", 0).text("Average");
|
||||
|
|
|
|||
|
|
@ -116,23 +116,25 @@ window.Cultures = (function () {
|
|||
|
||||
cultures.forEach(c => (c.base = c.base % nameBases.length));
|
||||
|
||||
function selectCultures(c) {
|
||||
let def = getDefault(c);
|
||||
if (c === def.length) return def;
|
||||
if (def.every(d => d.odd === 1)) return def.splice(0, c);
|
||||
|
||||
const count = Math.min(c, def.length);
|
||||
function selectCultures(culturesNumber) {
|
||||
let def = getDefault(culturesNumber);
|
||||
const cultures = [];
|
||||
|
||||
|
||||
pack.cultures?.forEach(function (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 {
|
||||
rnd = rand(def.length - 1);
|
||||
culture = def[rnd];
|
||||
} while (!P(culture.odd));
|
||||
i++;
|
||||
} while (i < 200 && !P(culture.odd));
|
||||
cultures.push(culture);
|
||||
def.splice(rnd, 1);
|
||||
}
|
||||
|
|
@ -507,28 +509,37 @@ window.Cultures = (function () {
|
|||
// expand cultures across the map (Dijkstra-like algorithm)
|
||||
const expand = function () {
|
||||
TIME && console.time("expandCultures");
|
||||
cells = pack.cells;
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
pack.cultures.forEach(function (c) {
|
||||
if (!c.i || c.removed || c.lock) return;
|
||||
queue.queue({e: c.center, p: 0, c: c.i});
|
||||
});
|
||||
|
||||
const neutral = (cells.i.length / 5000) * 3000 * neutralInput.value; // limit cost for culture growth
|
||||
const cost = [];
|
||||
|
||||
const neutralRate = byId("neutralRate")?.valueAsNumber || 1;
|
||||
const neutral = cells.i.length * 0.6 * neutralRate; // limit cost for culture growth
|
||||
|
||||
// remove culture from all cells except of locked
|
||||
for (const cellId of cells.i) {
|
||||
const culture = cultures[cells.culture[cellId]];
|
||||
if (culture.lock) continue;
|
||||
cells.culture[cellId] = 0;
|
||||
}
|
||||
|
||||
for (const culture of cultures) {
|
||||
if (!culture.i || culture.removed) continue;
|
||||
queue.queue({e: culture.center, p: 0, c: culture.i});
|
||||
}
|
||||
|
||||
while (queue.length) {
|
||||
const next = queue.dequeue(),
|
||||
n = next.e,
|
||||
p = next.p,
|
||||
c = next.c;
|
||||
const type = pack.cultures[c].type;
|
||||
cells.c[n].forEach(e => {
|
||||
if (pack.cultures[cells.culture[e]]?.lock) return;
|
||||
const {e, p, c} = queue.dequeue();
|
||||
const {type} = pack.cultures[c];
|
||||
|
||||
cells.c[e].forEach(e => {
|
||||
const culture = cells.culture[e];
|
||||
if (culture?.lock) return; // do not overwrite cell of locked culture
|
||||
|
||||
const biome = cells.biome[e];
|
||||
const biomeCost = getBiomeCost(c, biome, type);
|
||||
const biomeChangeCost = biome === cells.biome[n] ? 0 : 20; // penalty on biome change
|
||||
const biomeChangeCost = biome === cells.biome[e] ? 0 : 20; // penalty on biome change
|
||||
const heightCost = getHeightCost(e, cells.h[e], type);
|
||||
const riverCost = getRiverCost(cells.r[e], e, type);
|
||||
const typeCost = getTypeCost(cells.t[e], type);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function open() {
|
|||
|
||||
function insertEditorHtml() {
|
||||
const editorHtml = /* html */ `<div id="culturesEditor" class="dialog stable">
|
||||
<div id="culturesHeader" class="header" style="grid-template-columns: 10em 7em 8em 4em 8em 5em 8em 8em">
|
||||
<div id="culturesHeader" class="header" style="grid-template-columns: 10em 7em 9em 4em 8em 5em 7em 8em">
|
||||
<div data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="name">Culture </div>
|
||||
<div data-tip="Click to sort by type" class="sortable alphabetically" data-sortby="type">Type </div>
|
||||
<div data-tip="Click to sort by culture namesbase" class="sortable" data-sortby="base">Namesbase </div>
|
||||
|
|
@ -171,6 +171,7 @@ function culturesEditorAddLines() {
|
|||
value="${c.name}" autocorrect="off" spellcheck="false" />
|
||||
<span class="icon-cw placeholder"></span>
|
||||
<select class="cultureType placeholder">${getTypeOptions(c.type)}</select>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names"
|
||||
class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
|
|
@ -181,8 +182,7 @@ function culturesEditorAddLines() {
|
|||
<div data-tip="Culture area" class="cultureArea hide" style="width: 6em">${si(area)} ${unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide pointer"
|
||||
style="width: 5em">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
style="width: 4em">${si(population)}</div>
|
||||
${getShapeOptions(selectShape, c.shield)}
|
||||
</div>`;
|
||||
continue;
|
||||
|
|
@ -207,6 +207,7 @@ function culturesEditorAddLines() {
|
|||
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
|
||||
<select data-tip="Culture type. Defines growth model. Click to change"
|
||||
class="cultureType">${getTypeOptions(c.type)}</select>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names"
|
||||
class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
|
|
@ -225,10 +226,9 @@ function culturesEditorAddLines() {
|
|||
<div data-tip="Culture area" class="cultureArea hide" style="width: 6em">${si(area)} ${unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide pointer"
|
||||
style="width: 5em">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
style="width: 4em">${si(population)}</div>
|
||||
${getShapeOptions(selectShape, c.shield)}
|
||||
<span data-tip="Lock culture" class="icon-lock${c.lock ? '' : '-open'} hide"></span>
|
||||
<span data-tip="Lock culture" class="icon-lock${c.lock ? "" : "-open"} hide"></span>
|
||||
<span data-tip="Remove culture" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -251,7 +251,7 @@ function culturesEditorAddLines() {
|
|||
$body.querySelectorAll("fill-box").forEach($el => $el.on("click", cultureChangeColor));
|
||||
$body.querySelectorAll("div > input.cultureName").forEach($el => $el.on("input", cultureChangeName));
|
||||
$body.querySelectorAll("div > span.icon-cw").forEach($el => $el.on("click", cultureRegenerateName));
|
||||
$body.querySelectorAll("div > input.cultureExpan").forEach($el => $el.on("input", cultureChangeExpansionism));
|
||||
$body.querySelectorAll("div > input.cultureExpan").forEach($el => $el.on("change", cultureChangeExpansionism));
|
||||
$body.querySelectorAll("div > select.cultureType").forEach($el => $el.on("change", cultureChangeType));
|
||||
$body.querySelectorAll("div > select.cultureBase").forEach($el => $el.on("change", cultureChangeBase));
|
||||
$body.querySelectorAll("div > select.cultureEmblems").forEach($el => $el.on("change", cultureChangeEmblemsShape));
|
||||
|
|
@ -590,16 +590,23 @@ function drawCultureCenters() {
|
|||
}
|
||||
|
||||
function cultureCenterDrag() {
|
||||
const $el = d3.select(this);
|
||||
const cultureId = +this.id.slice(13);
|
||||
d3.event.on("drag", () => {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const x0 = +tr[0] - d3.event.x;
|
||||
const y0 = +tr[1] - d3.event.y;
|
||||
|
||||
function handleDrag() {
|
||||
const {x, y} = d3.event;
|
||||
$el.attr("cx", x).attr("cy", y);
|
||||
this.setAttribute("transform", `translate(${x0 + x},${y0 + y})`);
|
||||
const cell = findCell(x, y);
|
||||
if (pack.cells.h[cell] < 20) return; // ignore dragging on water
|
||||
|
||||
pack.cultures[cultureId].center = cell;
|
||||
recalculateCultures();
|
||||
});
|
||||
}
|
||||
|
||||
const dragDebounced = debounce(handleDrag, 50);
|
||||
d3.event.on("drag", dragDebounced);
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
|
|
@ -666,17 +673,10 @@ async function showHierarchy() {
|
|||
function recalculateCultures(must) {
|
||||
if (!must && !culturesAutoChange.checked) return;
|
||||
|
||||
pack.cells.culture = new Uint16Array(pack.cells.i.length);
|
||||
pack.cultures.forEach(function (c) {
|
||||
if (!c.i || c.removed) return;
|
||||
pack.cells.culture[c.center] = c.i;
|
||||
});
|
||||
|
||||
Cultures.expand();
|
||||
drawCultures();
|
||||
pack.burgs.forEach(b => (b.culture = pack.cells.culture[b.cell]));
|
||||
refreshCulturesEditor();
|
||||
document.querySelector("input.cultureExpan").focus(); // to not trigger hotkeys
|
||||
}
|
||||
|
||||
function enterCultureManualAssignent() {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ addListeners();
|
|||
|
||||
export function open() {
|
||||
closeDialogs("#religionsEditor, .stable");
|
||||
if (!layerIsOn("toggleReligions")) toggleCultures();
|
||||
if (!layerIsOn("toggleReligions")) toggleReligions();
|
||||
if (layerIsOn("toggleStates")) toggleStates();
|
||||
if (layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (layerIsOn("toggleCultures")) toggleReligions();
|
||||
if (layerIsOn("toggleCultures")) toggleCultures();
|
||||
if (layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
|
||||
refreshReligionsEditor();
|
||||
|
|
@ -23,13 +23,15 @@ export function open() {
|
|||
|
||||
function insertEditorHtml() {
|
||||
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 6em 7em 6em 7em">
|
||||
<div data-tip="Click to sort by religion name" class="sortable alphabetically" data-sortby="name">Religion </div>
|
||||
<div data-tip="Click to sort by religion type" class="sortable alphabetically icon-sort-name-down" data-sortby="type">Type </div>
|
||||
<div data-tip="Click to sort by religion form" class="sortable alphabetically hide" data-sortby="form">Form </div>
|
||||
<div data-tip="Click to sort by supreme deity" class="sortable alphabetically hide" data-sortby="deity">Supreme Deity </div>
|
||||
<div data-tip="Click to sort by religion area" class="sortable hide" data-sortby="area">Area </div>
|
||||
<div data-tip="Click to sort by number of believers (religion area population)" class="sortable hide" data-sortby="population">Believers </div>
|
||||
<div data-tip="Click to sort by potential extent type" class="sortable alphabetically hide" data-sortby="expansion">Potential </div>
|
||||
<div data-tip="Click to sort by expansionism" class="sortable hide" data-sortby="expansionism">Expansion </div>
|
||||
</div>
|
||||
<div id="religionsBody" class="table" data-type="absolute"></div>
|
||||
|
||||
|
|
@ -88,6 +90,11 @@ function insertEditorHtml() {
|
|||
</div>
|
||||
<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="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>`;
|
||||
|
||||
|
|
@ -109,6 +116,7 @@ function addListeners() {
|
|||
byId("religionsManuallyCancel").on("click", () => exitReligionsManualAssignment());
|
||||
byId("religionsAdd").on("click", enterAddReligionMode);
|
||||
byId("religionsExport").on("click", downloadReligionsCsv);
|
||||
byId("religionsRecalculate").on("click", () => recalculateReligions(true));
|
||||
}
|
||||
|
||||
function refreshReligionsEditor() {
|
||||
|
|
@ -166,9 +174,10 @@ function religionsEditorAddLines() {
|
|||
data-type=""
|
||||
data-form=""
|
||||
data-deity=""
|
||||
data-expansion=""
|
||||
data-expansionism=""
|
||||
>
|
||||
<svg width="11" height="11" class="placeholder"></svg>
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<input data-tip="Religion name. Click and type to change" class="religionName italic" style="width: 11em"
|
||||
value="${r.name}" autocorrect="off" spellcheck="false" />
|
||||
<select data-tip="Religion type" class="religionType placeholder" style="width: 5em">
|
||||
|
|
@ -178,9 +187,11 @@ function religionsEditorAddLines() {
|
|||
<span data-tip="Click to re-generate supreme deity" class="icon-arrows-cw placeholder hide"></span>
|
||||
<input data-tip="Religion supreme deity" class="religionDeity placeholder hide" style="width: 17em" value="" autocorrect="off" spellcheck="false" />
|
||||
<span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<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: 6em">${si(area) + unit}</div>
|
||||
<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" style="width: 5em">${si(
|
||||
population
|
||||
)}</div>
|
||||
</div>`;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -195,6 +206,7 @@ function religionsEditorAddLines() {
|
|||
data-type="${r.type}"
|
||||
data-form="${r.form}"
|
||||
data-deity="${r.deity || ""}"
|
||||
data-expansion="${r.expansion}"
|
||||
data-expansionism="${r.expansionism}"
|
||||
>
|
||||
<fill-box fill="${r.color}"></fill-box>
|
||||
|
|
@ -209,13 +221,13 @@ function religionsEditorAddLines() {
|
|||
<input data-tip="Religion supreme deity" class="religionDeity hide" style="width: 17em"
|
||||
value="${r.deity || ""}" autocorrect="off" spellcheck="false" />
|
||||
<span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<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: 6em">${si(area) + unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="religionPopulation hide pointer">${si(population)}</div>
|
||||
<span
|
||||
data-tip="Lock religion, will regenerate the origin folk and organized religion if they are not also locked"
|
||||
class="icon-lock${r.lock ? '' : '-open'} hide"
|
||||
></span>
|
||||
<div data-tip="${populationTip}" class="religionPopulation hide pointer" style="width: 5em">${si(
|
||||
population
|
||||
)}</div>
|
||||
${getExpansionColumns(r)}
|
||||
<span data-tip="Lock this religion" class="icon-lock${r.lock ? "" : "-open"} hide"></span>
|
||||
<span data-tip="Remove religion" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -245,6 +257,8 @@ function religionsEditorAddLines() {
|
|||
$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 > div.religionPopulation").forEach(el => el.on("click", changePopulation));
|
||||
$body.querySelectorAll("div > select.religionExtent").forEach(el => el.on("change", religionChangeExtent));
|
||||
$body.querySelectorAll("div > input.religionExpantion").forEach(el => el.on("change", religionChangeExpansionism));
|
||||
$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-open").forEach($el => $el.on("click", updateLockStatus));
|
||||
|
|
@ -253,6 +267,7 @@ function religionsEditorAddLines() {
|
|||
$body.dataset.type = "absolute";
|
||||
togglePercentageMode();
|
||||
}
|
||||
|
||||
applySorting(religionsHeader);
|
||||
$("#religionsEditor").dialog({width: fitContent()});
|
||||
}
|
||||
|
|
@ -264,6 +279,41 @@ function getTypeOptions(type) {
|
|||
return options;
|
||||
}
|
||||
|
||||
function getExpansionColumns(r) {
|
||||
if (r.type === "Folk") {
|
||||
const tip =
|
||||
"Folk religions are not competitive and do not expand. Initially they cover all cells of their parent culture, but get ousted by organized religions when they expand";
|
||||
return /* html */ `
|
||||
<span data-tip="${tip}" class="icon-resize-full-alt hide" style="padding-right: 2px"></span>
|
||||
<span data-tip="${tip}" class="religionExtent hide" style="width: 5em">culture</span>
|
||||
<span data-tip="${tip}" class="icon-resize-full hide"></span>
|
||||
<input data-tip="${tip}" class="religionExpantion hide" disabled type="number" value='0' />`;
|
||||
}
|
||||
|
||||
return /* html */ `
|
||||
<span data-tip="Potential religion extent" class="icon-resize-full-alt hide" style="padding-right: 2px"></span>
|
||||
<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="religionExpantion 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 religionId = Number(event.id || event.target.dataset.id);
|
||||
const $el = $body.querySelector(`div[data-id='${religionId}']`);
|
||||
|
|
@ -272,20 +322,19 @@ const religionHighlightOn = debounce(event => {
|
|||
if (!layerIsOn("toggleReligions")) return;
|
||||
if (customization) return;
|
||||
|
||||
const animate = d3.transition().duration(1500).ease(d3.easeSinIn);
|
||||
const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||
relig
|
||||
.select("#religion" + religionId)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr("stroke-width", 2.5)
|
||||
.attr("stroke", "#c13119");
|
||||
.attr("stroke", "#d0240f");
|
||||
debug
|
||||
.select("#religionsCenter" + religionId)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr("r", 8)
|
||||
.attr("stroke-width", 2)
|
||||
.attr("stroke", "#c13119");
|
||||
.attr("r", 3)
|
||||
.attr("stroke", "#d0240f");
|
||||
}, 200);
|
||||
|
||||
function religionHighlightOff(event) {
|
||||
|
|
@ -301,8 +350,7 @@ function religionHighlightOff(event) {
|
|||
debug
|
||||
.select("#religionsCenter" + religionId)
|
||||
.transition()
|
||||
.attr("r", 4)
|
||||
.attr("stroke-width", 1.2)
|
||||
.attr("r", 2)
|
||||
.attr("stroke", null);
|
||||
}
|
||||
|
||||
|
|
@ -434,6 +482,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() {
|
||||
if (customization) return;
|
||||
|
||||
|
|
@ -471,11 +533,14 @@ function drawReligionCenters() {
|
|||
const religionCenters = debug
|
||||
.append("g")
|
||||
.attr("id", "religionCenters")
|
||||
.attr("stroke-width", 1.2)
|
||||
.attr("stroke-width", 0.8)
|
||||
.attr("stroke", "#444444")
|
||||
.style("cursor", "move");
|
||||
|
||||
const data = pack.religions.filter(r => r.i && r.center && r.cells && !r.removed);
|
||||
let data = pack.religions.filter(r => r.i && r.center && !r.removed);
|
||||
const showExtinct = $body.dataset.extinct === "show";
|
||||
if (!showExtinct) data = data.filter(r => r.cells > 0);
|
||||
|
||||
religionCenters
|
||||
.selectAll("circle")
|
||||
.data(data)
|
||||
|
|
@ -483,7 +548,7 @@ function drawReligionCenters() {
|
|||
.append("circle")
|
||||
.attr("id", d => "religionsCenter" + d.i)
|
||||
.attr("data-id", d => d.i)
|
||||
.attr("r", 4)
|
||||
.attr("r", 2)
|
||||
.attr("fill", d => d.color)
|
||||
.attr("cx", d => pack.cells.p[d.center][0])
|
||||
.attr("cy", d => pack.cells.p[d.center][1])
|
||||
|
|
@ -499,15 +564,23 @@ function drawReligionCenters() {
|
|||
}
|
||||
|
||||
function religionCenterDrag() {
|
||||
const $el = d3.select(this);
|
||||
const religionId = +this.dataset.id;
|
||||
d3.event.on("drag", () => {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const x0 = +tr[0] - d3.event.x;
|
||||
const y0 = +tr[1] - d3.event.y;
|
||||
|
||||
function handleDrag() {
|
||||
const {x, y} = d3.event;
|
||||
$el.attr("cx", x).attr("cy", y);
|
||||
this.setAttribute("transform", `translate(${x0 + x},${y0 + y})`);
|
||||
const cell = findCell(x, y);
|
||||
if (pack.cells.h[cell] < 20) return; // ignore dragging on water
|
||||
|
||||
pack.religions[religionId].center = cell;
|
||||
});
|
||||
recalculateReligions();
|
||||
}
|
||||
|
||||
const dragDebounced = debounce(handleDrag, 50);
|
||||
d3.event.on("drag", dragDebounced);
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
|
|
@ -578,13 +651,14 @@ async function showHierarchy() {
|
|||
function toggleExtinct() {
|
||||
$body.dataset.extinct = $body.dataset.extinct !== "show" ? "show" : "hide";
|
||||
religionsEditorAddLines();
|
||||
drawReligionCenters();
|
||||
}
|
||||
|
||||
function enterReligionsManualAssignent() {
|
||||
if (!layerIsOn("toggleReligions")) toggleReligions();
|
||||
customization = 7;
|
||||
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";
|
||||
debug.select("#religionCenters").style("display", "none");
|
||||
|
||||
|
|
@ -686,7 +760,7 @@ function exitReligionsManualAssignment(close) {
|
|||
customization = 0;
|
||||
relig.select("#temp").remove();
|
||||
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("religionsEditor")
|
||||
|
|
@ -740,15 +814,15 @@ function addReligion() {
|
|||
|
||||
function downloadReligionsCsv() {
|
||||
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 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 {origins} = pack.religions[+id];
|
||||
const originList = (origins || []).filter(origin => origin).map(origin => pack.religions[origin].name);
|
||||
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");
|
||||
|
||||
|
|
@ -773,3 +847,13 @@ function updateLockStatus() {
|
|||
classList.toggle("icon-lock-open");
|
||||
classList.toggle("icon-lock");
|
||||
}
|
||||
|
||||
function recalculateReligions(must) {
|
||||
if (!must && !religionsAutoChange.checked) return;
|
||||
|
||||
Religions.recalculate();
|
||||
|
||||
drawReligions();
|
||||
refreshReligionsEditor();
|
||||
drawReligionCenters();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,8 +163,6 @@ function addListeners() {
|
|||
const line = $element.parentNode;
|
||||
const state = +line.dataset.id;
|
||||
if (classList.contains("stateCapital")) stateChangeCapitalName(state, line, $element.value);
|
||||
else if (classList.contains("cultureType")) stateChangeType(state, line, $element.value);
|
||||
else if (classList.contains("statePower")) stateChangeExpansionism(state, line, $element.value);
|
||||
});
|
||||
|
||||
$body.on("change", function (ev) {
|
||||
|
|
@ -173,6 +171,8 @@ function addListeners() {
|
|||
const line = $element.parentNode;
|
||||
const state = +line.dataset.id;
|
||||
if (classList.contains("stateCulture")) stateChangeCulture(state, line, $element.value);
|
||||
else if (classList.contains("cultureType")) stateChangeType(state, line, $element.value);
|
||||
else if (classList.contains("statePower")) stateChangeExpansionism(state, line, $element.value);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -883,7 +883,6 @@ function changeStatesGrowthRate() {
|
|||
const growthRate = +this.value;
|
||||
byId("statesNeutral").value = growthRate;
|
||||
byId("statesNeutralNumber").value = growthRate;
|
||||
statesNeutral = growthRate;
|
||||
tip("Growth rate: " + growthRate);
|
||||
recalculateStates(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,481 @@
|
|||
const capitalize = text => text.charAt(0).toUpperCase() + text.slice(1);
|
||||
|
||||
const format = rawList =>
|
||||
rawList
|
||||
.replace(/(?:\r\n|\r|\n)/g, "")
|
||||
.split(",")
|
||||
.map(name => capitalize(name.trim()))
|
||||
.sort();
|
||||
|
||||
export const supporters = format(`
|
||||
Aaron Meyer,Ahmad Amerih,AstralJacks,aymeric,Billy Dean Goehring,Branndon Edwards,Chase Mayers,Curt Flood,cyninge,Dino Princip,
|
||||
E.M. White,es,Fondue,Fritjof Olsson,Gatsu,Johan Fröberg,Jonathan Moore,Joseph Miranda,Kate,KC138,Luke Nelson,Markus Finster,Massimo Vella,Mikey,
|
||||
Nathan Mitchell,Paavi1,Pat,Ryan Westcott,Sasquatch,Shawn Spencer,Sizz_TV,Timothée CALLET,UTG community,Vlad Tomash,Wil Sisney,William Merriott,
|
||||
Xariun,Gun Metal Games,Scott Marner,Spencer Sherman,Valerii Matskevych,Alloyed Clavicle,Stewart Walsh,Ruthlyn Mollett (Javan),Benjamin Mair-Pratt,
|
||||
Diagonath,Alexander Thomas,Ashley Wilson-Savoury,William Henry,Preston Brooks,JOSHUA QUALTIERI,Hilton Williams,Katharina Haase,Hisham Bedri,
|
||||
Ian arless,Karnat,Bird,Kevin,Jessica Thomas,Steve Hyatt,Logicspren,Alfred García,Jonathan Killstring,John Ackley,Invad3r233,Norbert Žigmund,Jennifer,
|
||||
PoliticsBuff,_gfx_,Maggie,Connor McMartin,Jared McDaris,BlastWind,Franc Casanova Ferrer,Dead & Devil,Michael Carmody,Valerie Elise,naikibens220,
|
||||
Jordon Phillips,William Pucs,The Dungeon Masters,Brady R Rathbun,J,Shadow,Matthew Tiffany,Huw Williams,Joseph Hamilton,FlippantFeline,Tamashi Toh,
|
||||
kms,Stephen Herron,MidnightMoon,Whakomatic x,Barished,Aaron bateson,Brice Moss,Diklyquill,PatronUser,Michael Greiner,Steven Bennett,Jacob Harrington,
|
||||
Miguel C.,Reya C.,Giant Monster Games,Noirbard,Brian Drennen,Ben Craigie,Alex Smolin,Endwords,Joshua E Goodwin,SirTobit ,Allen S. Rout,Allen Bull Bear,
|
||||
Pippa Mitchell,R K,G0atfather,Ryan Lege,Caner Oleas Pekgönenç,Bradley Edwards,Tertiary ,Austin Miller,Jesse Holmes,Jan Dvořák,Marten F,Erin D. Smale,
|
||||
Maxwell Hill,Drunken_Legends,rob bee,Jesse Holmes,YYako,Detocroix,Anoplexian,Hannah,Paul,Sandra Krohn,Lucid,Richard Keating,Allen Varney,Rick Falkvinge,
|
||||
Seth Fusion,Adam Butler,Gus,StroboWolf,Sadie Blackthorne,Zewen Senpai,Dell McKnight,Oneiris,Darinius Dragonclaw Studios,Christopher Whitney,Rhodes HvZ,
|
||||
Jeppe Skov Jensen,María Martín López,Martin Seeger,Annie Rishor,Aram Sabatés,MadNomadMedia,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,
|
||||
Thirty-OneR,ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,
|
||||
Andrew Rostaing,Daniel Gill,Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,
|
||||
Alex Debus,Joshua Vaught,Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,
|
||||
Radovan Zapletal,Jmmat6,Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,
|
||||
Guilherme Aguiar,Jarno Hallikainen,Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,
|
||||
Cooper Counts,Patrick Jones,Clonetone,PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,
|
||||
Page One Project,Spencer Morris,Paul Ingram,Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,
|
||||
Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,
|
||||
PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,
|
||||
Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,
|
||||
Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,
|
||||
Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky,
|
||||
Dario Spadavecchia,Bas Kroot,John Patrick Callahan Jr,Alexandra Vesey,D,Exp1nt,james,Braxton Istace,w,Rurikid,AntiBlock,Redsauz,BigE0021,
|
||||
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger,Joshua Xiong,Moist mongol,
|
||||
Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut,Emarandzeb,Trentin Bergeron,Damon Gallaty,Pleaseworkforonce,
|
||||
Jordan,William Markus,Sidr Dim,Alexander Whittaker,The Next Level,Patrick Valverde,Markus Peham,Daniel Cooper,the Beagles of Neorbus,Marley Moule,
|
||||
Maximilian Schielke,Johnathan Xavier Hutchinson,Ele,Rita,Randy Ross,John Wick,RedSpaz,cameron cannon,Ian Grau-Fay,Kyle Barrett,Charlotte Wiland,
|
||||
David Kaul,E. Jason Davis,Cyberate,Atenfox,Sea Wolf,Holly Loveless,Roekai,Alden Z,angel carrillo,Sam Spoerle,S A Rudy,Bird Law Expert,Mira Cyr,
|
||||
Aaron Blair,Neyimadd,RLKZ1022,DerWolf,Kenji Yamada,Zion,Robert Rinne,Actual_Dio,Kyarou
|
||||
`);
|
||||
export const supporters = `ken burgan
|
||||
Sera's Nafitlaan
|
||||
Richard Rogers
|
||||
Hylobate
|
||||
Colin deSousa
|
||||
Aurelia De La Silla
|
||||
Maciej Kontny
|
||||
Ricky L Cain
|
||||
Iggyflare
|
||||
Garrett Renner
|
||||
Michael Harris
|
||||
Joshua Maly
|
||||
Nigel Guest
|
||||
Theo Hodges
|
||||
BERTHEAS Frédéric
|
||||
lilMoni
|
||||
Δημήτρης Μάρκογιαννακης
|
||||
Lee S.
|
||||
Chris Dibbs
|
||||
jarrad tait
|
||||
Jacen Solo
|
||||
Hannes Rotestam
|
||||
Preston Hicks
|
||||
Лонгин
|
||||
Will Fink
|
||||
ControlFreq
|
||||
IllAngel
|
||||
John Giardina
|
||||
Thiago Prado
|
||||
Zhang Dijon
|
||||
NoBurny
|
||||
thibault tersinet
|
||||
scarletsky
|
||||
Nich Smith
|
||||
Omegus
|
||||
Karl Abrahamsson
|
||||
Sara Fernandes
|
||||
peetey897
|
||||
Cooper Janse
|
||||
G F
|
||||
Glen Aultman-Bettridge
|
||||
Nathan Rogers
|
||||
Benjamin Mock
|
||||
CadmiumMan
|
||||
Kirk Edwards
|
||||
Leigh G
|
||||
Thom Colyer
|
||||
Frederik
|
||||
C pstj
|
||||
Zachary Pecora
|
||||
Trevor D'Arcey
|
||||
Ryan Gauvin
|
||||
Shawn Moore
|
||||
Jim Channon
|
||||
Kyarou
|
||||
Actual_Dio
|
||||
Jim B Johnson
|
||||
Robert Rinne
|
||||
Zion
|
||||
Kenji Yamada
|
||||
DerWolf
|
||||
RLKZ1022
|
||||
Neyimadd
|
||||
Aaron Blair
|
||||
Mira Cyr
|
||||
Bird Law Expert
|
||||
S A Rudy
|
||||
Sam Spoerle
|
||||
angel carrillo
|
||||
Alden Z
|
||||
Holly Loveless
|
||||
Sea Wolf
|
||||
Atenfox
|
||||
Cyberate
|
||||
E. Jason Davis
|
||||
Caro Lyns
|
||||
David Kaul
|
||||
Charlotte Wiland
|
||||
Kyle Barrett
|
||||
Ian Grau-Fay
|
||||
cameron cannon
|
||||
RedSpaz
|
||||
John Wick
|
||||
Randy Ross
|
||||
Rita
|
||||
Ele
|
||||
Johnathan Xavier Hutchinson
|
||||
Andrew Stein
|
||||
Ghettov Milan
|
||||
Malke
|
||||
TameMoon
|
||||
Daniel Cooper
|
||||
Markus Peham
|
||||
The Next Level
|
||||
Alexander Whittaker
|
||||
Sidr Dim
|
||||
William Markus
|
||||
Jordan
|
||||
Pleaseworkforonce
|
||||
Damon Gallaty
|
||||
Trentin Bergeron
|
||||
Emarandzeb
|
||||
Laulajatar
|
||||
Dale McBane
|
||||
Chris Kohut
|
||||
Preston Mitchell
|
||||
Andrew Kircher
|
||||
Frank Fewkes
|
||||
Moist mongol
|
||||
Joshua Xiong
|
||||
Jan Bundesmann
|
||||
www15o
|
||||
Game Master Pro
|
||||
jason baldrick
|
||||
Exp1nt
|
||||
w
|
||||
Shubham Jakhotiya
|
||||
Braxton Istace
|
||||
LesterThePossum
|
||||
Rurikid
|
||||
ojacid .
|
||||
james
|
||||
A Patreon of the Ahts
|
||||
BigE0021
|
||||
Angelique Badger
|
||||
Jonathan Williams
|
||||
AntiBlock
|
||||
Redsauz
|
||||
Florian Kelber
|
||||
John Patrick Callahan Jr
|
||||
Alexandra Vesey
|
||||
Bas Kroot
|
||||
Dzmitry Malyshau
|
||||
PedanticSteve
|
||||
Josh Wren
|
||||
BLRageQuit
|
||||
Dario Spadavecchia
|
||||
Neutrix
|
||||
Markell
|
||||
Rocky
|
||||
Robert Landry
|
||||
Skylar Mangum-Turner
|
||||
Nick Mowry
|
||||
Anjen Pai
|
||||
Hope You're Well
|
||||
Alexandre Boivin
|
||||
Racussa
|
||||
Mike Conley
|
||||
Karen Blythe
|
||||
Mark Sprietsma
|
||||
Xavier privé
|
||||
Tommy Mayfield
|
||||
Václav Švec
|
||||
Binks
|
||||
Mackenzie
|
||||
Linn Browning
|
||||
Writer's Consultant Page by George J.Lekkas
|
||||
Andrew Hines
|
||||
Wexxler
|
||||
Jason Matthew Wuerfel
|
||||
Milo Cohen
|
||||
Alan Buehne
|
||||
Dominick Ormsby
|
||||
Espen Sæverud
|
||||
Rasmus Legêne
|
||||
rbbalderama
|
||||
Nobody679
|
||||
Prince of Morgoth
|
||||
Jaryd Armstrong
|
||||
Gary Smith
|
||||
ThyHolyDevil
|
||||
良义 金
|
||||
Andrew Pirkola
|
||||
Dig
|
||||
Chris Gray
|
||||
Tyshaun Wise
|
||||
Phoenix
|
||||
Ethan Cook
|
||||
Jordan Bellah
|
||||
Petro Lombaard
|
||||
Kass Frisson
|
||||
Lazer Elf
|
||||
Gavin Madrigal
|
||||
Rox
|
||||
PinkEvil
|
||||
Martin Lorber
|
||||
Emanuel Pietri
|
||||
Alex Beard
|
||||
Jeffrey Henning
|
||||
Eric Alexander Cartaya
|
||||
Dust Bunny
|
||||
GameNight
|
||||
Beingus
|
||||
Crys Cain
|
||||
Lon Varnadore
|
||||
Thomas Mortensen Hansen
|
||||
Drinarius
|
||||
Ed Wright
|
||||
Adrian Wright
|
||||
Zklaus
|
||||
Chris Bloom
|
||||
PlayByMail.Net
|
||||
Maxim Lowe
|
||||
Aquelion
|
||||
Tiber
|
||||
Daydream1013
|
||||
Page One Project
|
||||
Clonetone
|
||||
Egoensis
|
||||
Brad Wardell
|
||||
Heaven N Lee
|
||||
BarnabyJones
|
||||
Paul Ingram
|
||||
Lance Saba
|
||||
Chad Riley
|
||||
Austin
|
||||
Rowland Kingman
|
||||
Decimus Vitalis
|
||||
Grayson McClead
|
||||
Battleturtle1
|
||||
Kristin Chernoff
|
||||
Justin Mcclain
|
||||
Patrick Jones
|
||||
Esther Busch
|
||||
Chance Mena
|
||||
JimmyTheBob
|
||||
Antiroo
|
||||
Dalton Clark
|
||||
Guilherme Aguiar
|
||||
Simon Drapeau
|
||||
Akirsop
|
||||
Radovan Zapletal
|
||||
Vanessa Anjos
|
||||
Rikard Wolff
|
||||
Justa Badge
|
||||
teco 47
|
||||
Jake
|
||||
Miguel Alejandro
|
||||
Blargh Blarghmoomoo
|
||||
Jakob Siegel
|
||||
Grant A. Murray
|
||||
Jarno Hallikainen
|
||||
Jan Ka
|
||||
Joshua Vaught
|
||||
MaxOliver
|
||||
WarWizardGames
|
||||
Evan-DiLeo
|
||||
Eric Moore
|
||||
Kyle S
|
||||
Alex Debus
|
||||
Uniquenameosaurus
|
||||
Dean Dunakin
|
||||
Jack
|
||||
Bryan Brake
|
||||
McNeil Atticus Inksmudge
|
||||
Char
|
||||
Tom Van Orden jr
|
||||
Kendall Patterson
|
||||
Akylos
|
||||
Barna Csíkos
|
||||
Nicholas Grabstas
|
||||
OldFarkas
|
||||
Riley Seaman
|
||||
Daniel Gill
|
||||
Kyle Robertson
|
||||
Natasha Taylor
|
||||
Pierrick Bertrand
|
||||
Jared.K
|
||||
Dylan Devenny
|
||||
logic_error
|
||||
SashaTK
|
||||
Steve Johnson
|
||||
MontyBoosh
|
||||
Achillain
|
||||
Jaden
|
||||
Vito Martono
|
||||
Thirty-OneR
|
||||
Eric Foley
|
||||
ThatGuyGW
|
||||
Dee Chiu
|
||||
James H. Anthony
|
||||
Kevin Cossutta
|
||||
MadNomadMedia
|
||||
Darinius Dragonclaw Studios
|
||||
Tsahyla (Triston Lightyear)
|
||||
Christopher Whitney
|
||||
María Martín López
|
||||
Annie Rishor
|
||||
Aram Sabatés
|
||||
Jeppe Skov Jensen
|
||||
Martin Seeger
|
||||
Oneiris (Oni)
|
||||
EternalDeiwos
|
||||
Richard Keating
|
||||
StroboWolf
|
||||
Rick Falkvinge
|
||||
Zewen Senpai
|
||||
Adam Butler
|
||||
Kassidy
|
||||
Sadie Blackthorne
|
||||
ErrorForever
|
||||
Seth Fusion
|
||||
Gus
|
||||
Paul
|
||||
Lucid
|
||||
Allen Varney
|
||||
Hannah May
|
||||
Sankroh
|
||||
Eliot Miller
|
||||
Detocroix
|
||||
Meg Ziegler
|
||||
rob bee
|
||||
Anoplexian
|
||||
Marten F
|
||||
Erin D. Smale
|
||||
Johnpaul Morrow
|
||||
Roekai
|
||||
Drunken_Legends
|
||||
Jesse Holmes
|
||||
Maxwell Hill
|
||||
Jan Dvořák
|
||||
SirTobit
|
||||
G0atfather
|
||||
Allen S. Rout
|
||||
Pippa Mitchell
|
||||
Austin Miller
|
||||
Caner Oleas Pekgönenç
|
||||
Alison Bull Bear
|
||||
Bradley Edwards
|
||||
Tertiary
|
||||
Daniel
|
||||
Joshua E Goodwin
|
||||
Shaun Alexander
|
||||
Ryan Lege
|
||||
Myrrhlin
|
||||
Jesper Cockx
|
||||
Noirbard
|
||||
Dice
|
||||
Brian Drennen
|
||||
Giant Monster Games
|
||||
Reya C.
|
||||
Krk
|
||||
Endwords
|
||||
Jacob Harrington
|
||||
RK
|
||||
Michael Greiner
|
||||
Steven Bennett
|
||||
Brice Moss
|
||||
Whakomatic x
|
||||
Stephen Herron
|
||||
kosmobius
|
||||
ZizRenanim
|
||||
Barished
|
||||
Maur Razimtheth
|
||||
Aaron bateson
|
||||
Diklyquill
|
||||
Shawn Taylor
|
||||
Brady R Rathbun
|
||||
FlippantFeline
|
||||
Shadow
|
||||
J
|
||||
Tamashi Toh
|
||||
Huw Williams
|
||||
Graves
|
||||
ShadeByTheSea
|
||||
The Dungeon Masters
|
||||
Valerie Elise
|
||||
Empi3
|
||||
William Pucs
|
||||
Michael Carmody
|
||||
Marco Veldman
|
||||
naikibens220
|
||||
Jordon Phillips
|
||||
_gfx_
|
||||
F. Casanova
|
||||
Jared McDaris
|
||||
BlastWind
|
||||
Taldonix
|
||||
Connor McMartin
|
||||
Nexoness
|
||||
Guy
|
||||
Maggie
|
||||
AdvancedAzrielAngel
|
||||
Alfred García
|
||||
Norbert Žigmund
|
||||
Jennifer
|
||||
Titanium Tomes
|
||||
John Ackley
|
||||
Invad3r233
|
||||
Jonathan Killstring
|
||||
Jessica Thomas
|
||||
Nikita Kondratjuks
|
||||
Steve Hyatt
|
||||
PoliticsBuff
|
||||
Ian arless
|
||||
Karnat
|
||||
Hilton Williams
|
||||
Kevin
|
||||
Katharina Haase
|
||||
Hisham Bedri
|
||||
Bird
|
||||
JOSHUA QUALTIERI
|
||||
Preston Brooks
|
||||
Troy Schuler
|
||||
DerGeisterbär
|
||||
L. V. Werneck
|
||||
Marcus Hellyrr
|
||||
yami
|
||||
Daniel Eric Crosby
|
||||
Augusto Chiarle
|
||||
Doug Churchman
|
||||
David Roza
|
||||
Alexander Thomas
|
||||
Ashley Wilson-Savoury
|
||||
Nathan L Myers
|
||||
Theresa Walsh
|
||||
JP Roberts III
|
||||
William Henry
|
||||
OldbeanOldboy
|
||||
Javasharp
|
||||
Diagonath
|
||||
Gun Metal Games
|
||||
Scott Marner
|
||||
Alloyed Clavicle
|
||||
Valerii Matskevych
|
||||
Spencer Sherman
|
||||
Nolan Moore
|
||||
James Schellenger
|
||||
Pat
|
||||
Dino Princip
|
||||
Shawn Spencer
|
||||
Timothée CALLET
|
||||
KC138
|
||||
Nylian
|
||||
Kate
|
||||
Markus Finster
|
||||
CanadianGold
|
||||
AstralJacks
|
||||
Keith Marshall
|
||||
Scott Davis
|
||||
Joseph Miranda
|
||||
Shaptarshi Joarder
|
||||
Branndon
|
||||
EP
|
||||
Johan Fröberg
|
||||
Sasquatch
|
||||
Chase Mayers
|
||||
Sizz_TV
|
||||
Ryan Westcott
|
||||
Nathan Mitchell
|
||||
Curt Flood
|
||||
Mikey
|
||||
E.M. White
|
||||
Billy
|
||||
Vlad Tomash
|
||||
Xariun
|
||||
Luke Nelson
|
||||
W Maxwell Cassity-Guilliom
|
||||
Marty H
|
||||
Aaron Meyer
|
||||
Max Amillios
|
||||
chris
|
||||
cyninge
|
||||
Omegavoid
|
||||
Fritjof Olsson
|
||||
Crazypedia
|
||||
Duncan Thomson
|
||||
William Merriott
|
||||
Gold Tamarin
|
||||
Lhoris
|
||||
Jonathan
|
||||
Jon
|
||||
Massimo Vella
|
||||
Feuver
|
||||
aymeric
|
||||
Eric Schumann
|
||||
Rei
|
||||
Fondue
|
||||
Paavi1
|
||||
Wil Sisney
|
||||
David Patterson`;
|
||||
|
|
|
|||
|
|
@ -304,15 +304,34 @@ window.Religions = (function () {
|
|||
Heresy: {Heresy: 1}
|
||||
};
|
||||
|
||||
const methods = {
|
||||
"Random + type": 3,
|
||||
"Random + ism": 1,
|
||||
"Supreme + ism": 5,
|
||||
"Faith of + Supreme": 5,
|
||||
"Place + ism": 1,
|
||||
"Culture + ism": 2,
|
||||
"Place + ian + type": 6,
|
||||
"Culture + type": 4
|
||||
const namingMethods = {
|
||||
Folk: {
|
||||
"Culture + type": 1
|
||||
},
|
||||
|
||||
Organized: {
|
||||
"Random + type": 3,
|
||||
"Random + ism": 1,
|
||||
"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 = {
|
||||
|
|
@ -342,381 +361,416 @@ window.Religions = (function () {
|
|||
}
|
||||
};
|
||||
|
||||
const generate = function () {
|
||||
const expansionismMap = {
|
||||
Folk: () => 0,
|
||||
Organized: () => gauss(5, 3, 0, 10, 1),
|
||||
Cult: () => gauss(0.5, 0.5, 0, 5, 1),
|
||||
Heresy: () => gauss(1, 0.5, 0, 5, 1)
|
||||
};
|
||||
|
||||
function generate() {
|
||||
TIME && console.time("generateReligions");
|
||||
const {cells, states, cultures} = pack;
|
||||
const lockedReligions = pack.religions?.filter(r => r.i && r.lock && !r.removed) || [];
|
||||
|
||||
const religionIds = new Uint16Array(cells.culture); // cell religion; initially based on culture
|
||||
const religions = [];
|
||||
const folkReligions = generateFolkReligions();
|
||||
const organizedReligions = generateOrganizedReligions(+religionsInput.value, lockedReligions);
|
||||
|
||||
// add folk religions
|
||||
pack.cultures.forEach(c => {
|
||||
const newId = c.i;
|
||||
if (!newId) return religions.push({i: 0, name: "No religion"});
|
||||
const namedReligions = specifyReligions([...folkReligions, ...organizedReligions]);
|
||||
const indexedReligions = combineReligions(namedReligions, lockedReligions);
|
||||
const religionIds = expandReligions(indexedReligions);
|
||||
const religions = defineOrigins(religionIds, indexedReligions);
|
||||
|
||||
if (c.removed) {
|
||||
religions.push({
|
||||
i: c.i,
|
||||
name: "Extinct religion for " + c.name,
|
||||
color: getMixedColor(c.color, 0.1, 0),
|
||||
removed: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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); // `url(#hatch${rand(8,13)})`;
|
||||
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)
|
||||
return religions.filter(r => r.i).forEach(r => (r.code = abbreviate(r.name)));
|
||||
|
||||
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();
|
||||
pack.religions = religions;
|
||||
pack.cells.religion = religionIds;
|
||||
|
||||
checkCenters();
|
||||
|
||||
cells.religion = religionIds;
|
||||
pack.religions = religions;
|
||||
|
||||
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 = [];
|
||||
function generateFolkReligions() {
|
||||
return pack.cultures
|
||||
.filter(c => c.i && !c.removed)
|
||||
.map(culture => ({type: "Folk", form: rw(forms.Folk), culture: culture.i, center: culture.center}));
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
function generateOrganizedReligions(desiredReligionNumber, lockedReligions) {
|
||||
const cells = pack.cells;
|
||||
const lockedReligionCount = lockedReligions.filter(({type}) => type !== "Folk").length || 0;
|
||||
const requiredReligionsNumber = desiredReligionNumber - lockedReligionCount;
|
||||
if (requiredReligionsNumber < 1) return [];
|
||||
|
||||
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
|
||||
const candidateCells = getCandidateCells();
|
||||
const religionCores = placeReligions();
|
||||
|
||||
while (queue.length) {
|
||||
const {e, p, r, c, s} = queue.dequeue();
|
||||
const expansion = religions[r].expansion;
|
||||
const cultsCount = Math.floor((rand(1, 4) / 10) * religionCores.length); // 10-40%
|
||||
const heresiesCount = Math.floor((rand(0, 3) / 10) * religionCores.length); // 0-30%
|
||||
const organizedCount = religionCores.length - cultsCount - heresiesCount;
|
||||
|
||||
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 getType = index => {
|
||||
if (index < organizedCount) return "Organized";
|
||||
if (index < organizedCount + cultsCount) return "Cult";
|
||||
return "Heresy";
|
||||
};
|
||||
|
||||
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;
|
||||
return religionCores.map((cellId, index) => {
|
||||
const type = getType(index);
|
||||
const form = rw(forms[type]);
|
||||
const cultureId = cells.culture[cellId];
|
||||
|
||||
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});
|
||||
}
|
||||
});
|
||||
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;
|
||||
|
||||
for (const cellId of candidateCells) {
|
||||
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) return getRandomColor();
|
||||
|
||||
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 indexedReligions = [{name: "No religion", i: 0}];
|
||||
|
||||
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: "Removed religion", 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},
|
||||
Cult: {clusterSize: 50, maxReligions: 3},
|
||||
Heresy: {clusterSize: 50, maxReligions: 4}
|
||||
};
|
||||
|
||||
const origins = indexedReligions.map(({i, type, culture: cultureId, expansion, center}) => {
|
||||
if (i === 0) return null; // no religion
|
||||
if (type === "Folk") return [0]; // folk religions originate from its parent culture only
|
||||
|
||||
const folkReligion = indexedReligions.find(({culture, type}) => type === "Folk" && culture === cultureId);
|
||||
const isFolkBased = folkReligion && cultureId && expansion === "culture" && each(2)(center);
|
||||
if (isFolkBased) return [folkReligion.i];
|
||||
|
||||
const {clusterSize, maxReligions} = religionOriginsParamsMap[type];
|
||||
const fallbackOrigin = folkReligion?.i || 0;
|
||||
return getReligionsInRadius(pack.cells.c, center, religionIds, i, clusterSize, maxReligions, fallbackOrigin);
|
||||
});
|
||||
|
||||
return indexedReligions.map((religion, index) => ({...religion, origins: origins[index]}));
|
||||
}
|
||||
|
||||
function getReligionsInRadius(neighbors, center, religionIds, religionId, clusterSize, maxReligions, fallbackOrigin) {
|
||||
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);
|
||||
if (foundReligions.size >= maxReligions) return [...foundReligions];
|
||||
queue.push(neibId);
|
||||
}
|
||||
}
|
||||
|
||||
// growth algorithm to assign cells to heresies
|
||||
function expandHeresies() {
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
return foundReligions.size ? [...foundReligions] : [fallbackOrigin];
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
// growth algorithm to assign cells to religions
|
||||
function expandReligions(religions) {
|
||||
const cells = pack.cells;
|
||||
const religionIds = spreadFolkReligions(religions);
|
||||
|
||||
const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
|
||||
while (queue.length) {
|
||||
const {e, p, r, b} = queue.dequeue();
|
||||
const maxExpansionCost = (cells.i.length / 20) * neutralInput.value; // limit cost for organized religions growth
|
||||
|
||||
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);
|
||||
const biomePassageCost = cellId => biomesData.cost[cells.biome[cellId]];
|
||||
|
||||
if (totalCost > neutral) return;
|
||||
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;
|
||||
});
|
||||
|
||||
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 religionsMap = new Map(religions.map(r => [r.i, r]));
|
||||
|
||||
function checkCenters() {
|
||||
const codes = religions.map(r => r.code);
|
||||
religions.forEach(r => {
|
||||
if (!r.i) return;
|
||||
r.code = abbreviate(r.name, codes);
|
||||
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;
|
||||
|
||||
// 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
|
||||
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 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;
|
||||
if (isMainRoad(cellId)) return 1;
|
||||
const biomeCost = biomePassageCost(cellId);
|
||||
return isTrail(cellId) ? biomeCost / 1.5 : biomeCost;
|
||||
}
|
||||
}
|
||||
|
||||
// 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, religions} = pack;
|
||||
const {cells, cultures, religions} = pack;
|
||||
const religionId = cells.religion[center];
|
||||
const i = religions.length;
|
||||
|
||||
const culture = cells.culture[center];
|
||||
const color = getMixedColor(religions[religionId].color, 0.3, 0);
|
||||
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 =
|
||||
religions[religionId].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2}) : rw({Organized: 5, Cult: 2});
|
||||
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" ? null : getDeityName(culture);
|
||||
type === "Heresy"
|
||||
? religions[religionId].deity
|
||||
: form === "Non-theism" || form === "Animism"
|
||||
? null
|
||||
: getDeityName(cultureId);
|
||||
|
||||
let name, expansion;
|
||||
if (type === "Organized") [name, expansion] = getReligionName(form, deity, center);
|
||||
else {
|
||||
name = getCultName(form, center);
|
||||
expansion = "global";
|
||||
}
|
||||
const [name, expansion] = generateReligionName(type, form, deity, center);
|
||||
|
||||
const formName = type === "Heresy" ? religions[religionId].form : form;
|
||||
const code = abbreviate(
|
||||
name,
|
||||
religions.map(r => r.code)
|
||||
);
|
||||
const influences = getReligionsInRadius(cells.c, center, cells.religion, i, 25, 3, 0);
|
||||
const origins = type === "Folk" ? [0] : influences;
|
||||
|
||||
const i = religions.length;
|
||||
religions.push({
|
||||
i,
|
||||
name,
|
||||
color,
|
||||
culture,
|
||||
culture: cultureId,
|
||||
type,
|
||||
form: formName,
|
||||
deity,
|
||||
expansion,
|
||||
expansionism: 0,
|
||||
expansionism: expansionismMap[type](),
|
||||
center,
|
||||
cells: 0,
|
||||
area: 0,
|
||||
rural: 0,
|
||||
urban: 0,
|
||||
origins: [religionId],
|
||||
origins,
|
||||
code
|
||||
});
|
||||
cells.religion[center] = i;
|
||||
};
|
||||
|
||||
function updateCultures() {
|
||||
TIME && console.time("updateCulturesForReligions");
|
||||
pack.religions = pack.religions.map((religion, index) => {
|
||||
if (index === 0) {
|
||||
return religion;
|
||||
}
|
||||
if (index === 0) return religion;
|
||||
return {...religion, culture: pack.cells.culture[religion.center]};
|
||||
});
|
||||
TIME && console.timeEnd("updateCulturesForReligions");
|
||||
}
|
||||
|
||||
// get supreme deity name
|
||||
|
|
@ -735,22 +789,24 @@ window.Religions = (function () {
|
|||
if (a === "Number") return ra(base.number);
|
||||
if (a === "Being") return ra(base.being);
|
||||
if (a === "Adjective") return ra(base.adjective);
|
||||
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 + Being") return ra(base.adjective) + " " + ra(base.being);
|
||||
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 + 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 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 === "Color + Animal") return `${ra(base.color)} ${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 + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`;
|
||||
if (a === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`;
|
||||
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 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 === "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")
|
||||
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 random = () => Names.getCulture(cells.culture[center], null, null, "", 0);
|
||||
|
|
@ -766,7 +822,7 @@ window.Religions = (function () {
|
|||
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 + ism") return [trimVowels(random()) + "ism", "global"];
|
||||
if (m === "Supreme + ism" && deity) return [trimVowels(supreme()) + "ism", "global"];
|
||||
|
|
@ -776,24 +832,11 @@ window.Religions = (function () {
|
|||
if (m === "Culture + ism") return [trimVowels(culture()) + "ism", "culture"];
|
||||
if (m === "Place + ian + type") return [place("adj") + " " + type(), "state"];
|
||||
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
|
||||
}
|
||||
|
||||
function getCultName(form, center) {
|
||||
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};
|
||||
return {generate, add, getDeityName, updateCultures, recalculate};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -48,7 +48,9 @@ window.Rivers = (function () {
|
|||
cells.fl[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation
|
||||
|
||||
// create lake outlet if lake is not in deep depression and flux > evaporation
|
||||
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
|
||||
const lakes = lakeOutCells[i]
|
||||
? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation)
|
||||
: [];
|
||||
for (const lake of lakes) {
|
||||
const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i);
|
||||
cells.fl[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet
|
||||
|
|
@ -191,7 +193,18 @@ window.Rivers = (function () {
|
|||
const length = getApproximateLength(meanderedPoints);
|
||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
|
||||
|
||||
pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells});
|
||||
pack.rivers.push({
|
||||
i: riverId,
|
||||
source,
|
||||
mouth,
|
||||
discharge,
|
||||
length,
|
||||
width,
|
||||
widthFactor,
|
||||
sourceWidth: 0,
|
||||
parent,
|
||||
cells: riverCells
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -479,6 +492,10 @@ window.Rivers = (function () {
|
|||
return getBasin(parent);
|
||||
};
|
||||
|
||||
const getNextId = function (rivers) {
|
||||
return rivers.length ? Math.max(...rivers.map(r => r.i)) + 1 : 1;
|
||||
};
|
||||
|
||||
return {
|
||||
generate,
|
||||
alterHeights,
|
||||
|
|
@ -493,6 +510,7 @@ window.Rivers = (function () {
|
|||
getOffset,
|
||||
getApproximateLength,
|
||||
getRiverPoints,
|
||||
remove
|
||||
remove,
|
||||
getNextId
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1176,18 +1176,18 @@ function refreshAllEditors() {
|
|||
// dynamically loaded editors
|
||||
async function editStates() {
|
||||
if (customization) return;
|
||||
const Editor = await import("../dynamic/editors/states-editor.js?v=12062022");
|
||||
const Editor = await import("../dynamic/editors/states-editor.js?v=1.89.05");
|
||||
Editor.open();
|
||||
}
|
||||
|
||||
async function editCultures() {
|
||||
if (customization) return;
|
||||
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.88.06");
|
||||
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.89.09");
|
||||
Editor.open();
|
||||
}
|
||||
|
||||
async function editReligions() {
|
||||
if (customization) return;
|
||||
const Editor = await import("../dynamic/editors/religions-editor.js?v=1.88.06");
|
||||
const Editor = await import("../dynamic/editors/religions-editor.js?v=1.89.10");
|
||||
Editor.open();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,6 +204,13 @@ function editHeightmap(options) {
|
|||
INFO && console.group("Edit Heightmap");
|
||||
TIME && console.time("regenerateErasedData");
|
||||
|
||||
// remove data
|
||||
pack.cultures = [];
|
||||
pack.burgs = [];
|
||||
pack.states = [];
|
||||
pack.provinces = [];
|
||||
pack.religions = [];
|
||||
|
||||
const erosionAllowed = allowErosion.checked;
|
||||
markFeatures();
|
||||
markupGridOcean();
|
||||
|
|
@ -231,8 +238,10 @@ function editHeightmap(options) {
|
|||
Lakes.defineGroup();
|
||||
defineBiomes();
|
||||
rankCells();
|
||||
|
||||
Cultures.generate();
|
||||
Cultures.expand();
|
||||
|
||||
BurgsAndStates.generate();
|
||||
Religions.generate();
|
||||
BurgsAndStates.defineStateForms();
|
||||
|
|
|
|||
|
|
@ -67,11 +67,11 @@ function editIce() {
|
|||
function addIcebergOnClick() {
|
||||
const [x, y] = d3.mouse(this);
|
||||
const i = findGridCell(x, y, grid);
|
||||
const c = grid.points[i];
|
||||
const s = +document.getElementById("iceSize").value;
|
||||
const [cx, cy] = grid.points[i];
|
||||
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 iceberg = ice.append("polygon").attr("points", points).attr("cell", i).attr("size", s);
|
||||
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", size);
|
||||
iceberg.call(d3.drag().on("drag", dragElement));
|
||||
if (d3.event.shiftKey === false) toggleAdd();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -671,11 +671,10 @@ function toggleIce(event) {
|
|||
}
|
||||
|
||||
function drawIce() {
|
||||
const cells = grid.cells,
|
||||
vertices = grid.vertices,
|
||||
n = cells.i.length,
|
||||
temp = cells.temp,
|
||||
h = cells.h;
|
||||
const {cells, vertices} = grid;
|
||||
const {temp, h} = cells;
|
||||
const n = cells.i.length;
|
||||
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
Math.random = aleaPRNG(seed);
|
||||
|
||||
|
|
@ -700,23 +699,22 @@ function drawIce() {
|
|||
continue;
|
||||
}
|
||||
|
||||
const tNormalized = normalize(t, -8, 2);
|
||||
const randomFactor = t > -5 ? 0.4 + rand() * 1.2 : 1;
|
||||
|
||||
// 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
|
||||
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
|
||||
size = Math.min(size * (0.4 + rand() * 1.2), 0.95); // randomize iceberg size
|
||||
resizePolygon(i, size);
|
||||
|
||||
let size = 1 - tNormalized; // iceberg size: 0 = zero size, 1 = full size
|
||||
if (cells.t[i] === -1) size /= 1.3; // coasline: smaller icebers
|
||||
resizePolygon(i, minmax(rn(size * randomFactor, 2), 0.08, 1));
|
||||
}
|
||||
|
||||
function resizePolygon(i, s) {
|
||||
const c = 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]);
|
||||
ice
|
||||
.append("polygon")
|
||||
.attr("points", points)
|
||||
.attr("cell", i)
|
||||
.attr("size", rn(1 - s, 2));
|
||||
function resizePolygon(i, size) {
|
||||
const [cx, cy] = grid.points[i];
|
||||
const points = getGridPolygon(i).map(([x, y]) => [rn(lerp(cx, x, size), 2), rn(lerp(cy, y, size), 2)]);
|
||||
ice.append("polygon").attr("points", points).attr("cell", i).attr("size", size);
|
||||
}
|
||||
|
||||
// connect vertices to chain
|
||||
|
|
|
|||
|
|
@ -42,12 +42,11 @@ function editNotes(id, name) {
|
|||
|
||||
$("#notesEditor").dialog({
|
||||
title: "Notes Editor",
|
||||
width: "minmax(80vw, 540px)",
|
||||
width: window.innerWidth * 0.8,
|
||||
height: window.innerHeight * 0.75,
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
close: removeEditor
|
||||
});
|
||||
$("[aria-describedby='notesEditor']").css("top", "10vh");
|
||||
|
||||
if (modules.editNotes) return;
|
||||
modules.editNotes = true;
|
||||
|
|
|
|||
|
|
@ -77,12 +77,15 @@ document
|
|||
// show popup with a list of Patreon supportes (updated manually)
|
||||
async function showSupporters() {
|
||||
const {supporters} = await import("../dynamic/supporters.js?v=19062022");
|
||||
const list = supporters.split("\n").sort();
|
||||
const columns = window.innerWidth < 800 ? 2 : 5;
|
||||
|
||||
alertMessage.innerHTML =
|
||||
"<ul style='column-count: 5; column-gap: 2em'>" + supporters.map(n => `<li>${n}</li>`).join("") + "</ul>";
|
||||
`<ul style='column-count: ${columns}; column-gap: 2em'>` + list.map(n => `<li>${n}</li>`).join("") + "</ul>";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Patreon Supporters",
|
||||
width: "54vw",
|
||||
width: "min-width",
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
}
|
||||
|
|
@ -157,9 +160,20 @@ optionsContent.addEventListener("click", function (event) {
|
|||
});
|
||||
|
||||
function mapSizeInputChange() {
|
||||
const $mapWidthInput = byId("mapWidthInput");
|
||||
const $mapHeightInput = byId("mapHeightInput");
|
||||
|
||||
changeMapSize();
|
||||
localStorage.setItem("mapWidth", mapWidthInput.value);
|
||||
localStorage.setItem("mapHeight", mapHeightInput.value);
|
||||
localStorage.setItem("mapWidth", $mapWidthInput.value);
|
||||
localStorage.setItem("mapHeight", $mapHeightInput.value);
|
||||
|
||||
const tooWide = +$mapWidthInput.value > window.innerWidth;
|
||||
const tooHigh = +$mapHeightInput.value > window.innerHeight;
|
||||
|
||||
if (tooWide || tooHigh) {
|
||||
const message = `Canvas size is larger than actual window size (${window.innerWidth} x ${window.innerHeight}). It can affect the performance if you are going to create a new map`;
|
||||
tip(message, false, "warn", 4000);
|
||||
}
|
||||
}
|
||||
|
||||
// change svg size on manual size change or window resize, do not change graph size
|
||||
|
|
@ -534,7 +548,7 @@ function applyStoredOptions() {
|
|||
options.stateLabelsMode = stateLabelsModeInput.value;
|
||||
}
|
||||
|
||||
// randomize options if randomization is allowed (not locked or options='default')
|
||||
// randomize options if randomization is allowed (not locked or queryParam options='default')
|
||||
function randomizeOptions() {
|
||||
const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options
|
||||
|
||||
|
|
@ -546,7 +560,7 @@ function randomizeOptions() {
|
|||
manorsInput.value = 1000;
|
||||
manorsOutput.value = "auto";
|
||||
}
|
||||
if (randomize || !locked("religions")) religionsInput.value = religionsOutput.value = gauss(5, 2, 2, 10);
|
||||
if (randomize || !locked("religions")) religionsInput.value = religionsOutput.value = gauss(6, 3, 2, 10);
|
||||
if (randomize || !locked("power")) powerInput.value = powerOutput.value = gauss(4, 2, 0, 10, 2);
|
||||
if (randomize || !locked("neutral")) neutralInput.value = neutralOutput.value = rn(1 + Math.random(), 1);
|
||||
if (randomize || !locked("cultures")) culturesInput.value = culturesOutput.value = gauss(12, 3, 5, 30);
|
||||
|
|
@ -602,17 +616,17 @@ function randomizeCultureSet() {
|
|||
function setRendering(value) {
|
||||
viewbox.attr("shape-rendering", value);
|
||||
|
||||
if (value === "optimizeSpeed") {
|
||||
// block some styles
|
||||
coastline.select("#sea_island").style("filter", "none");
|
||||
statesHalo.style("display", "none");
|
||||
emblems.style("opacity", 1);
|
||||
} else {
|
||||
// remove style block
|
||||
coastline.select("#sea_island").style("filter", null);
|
||||
statesHalo.style("display", null);
|
||||
emblems.style("opacity", null);
|
||||
}
|
||||
// if (value === "optimizeSpeed") {
|
||||
// // block some styles
|
||||
// coastline.select("#sea_island").style("filter", "none");
|
||||
// statesHalo.style("display", "none");
|
||||
// emblems.style("opacity", 1);
|
||||
// } else {
|
||||
// // remove style block
|
||||
// coastline.select("#sea_island").style("filter", null);
|
||||
// statesHalo.style("display", null);
|
||||
// emblems.style("opacity", null);
|
||||
// }
|
||||
}
|
||||
|
||||
// generate current year and era name
|
||||
|
|
|
|||
|
|
@ -74,12 +74,13 @@ function createRiver() {
|
|||
|
||||
function addRiver() {
|
||||
const {rivers, cells} = pack;
|
||||
const {addMeandering, getApproximateLength, getWidth, getOffset, getName, getRiverPath, getBasin} = Rivers;
|
||||
const {addMeandering, getApproximateLength, getWidth, getOffset, getName, getRiverPath, getBasin, getNextId} =
|
||||
Rivers;
|
||||
|
||||
const riverCells = createRiver.cells;
|
||||
if (riverCells.length < 2) return tip("Add at least 2 cells", false, "error");
|
||||
|
||||
const riverId = rivers.length ? last(rivers).i + 1 : 1;
|
||||
const riverId = getNextId(rivers);
|
||||
const parent = cells.r[last(riverCells)] || riverId;
|
||||
|
||||
riverCells.forEach(cell => {
|
||||
|
|
@ -100,12 +101,30 @@ function createRiver() {
|
|||
const name = getName(mouth);
|
||||
const basin = getBasin(parent);
|
||||
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"});
|
||||
rivers.push({
|
||||
i: riverId,
|
||||
source,
|
||||
mouth,
|
||||
discharge,
|
||||
length,
|
||||
width,
|
||||
widthFactor,
|
||||
sourceWidth,
|
||||
parent,
|
||||
cells: riverCells,
|
||||
basin,
|
||||
name,
|
||||
type: "River"
|
||||
});
|
||||
const id = "river" + riverId;
|
||||
|
||||
// render river
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
viewbox.select("#rivers").append("path").attr("id", id).attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||
viewbox
|
||||
.select("#rivers")
|
||||
.append("path")
|
||||
.attr("id", id)
|
||||
.attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||
|
||||
editRiver(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
const CONTROL_POINST_DISTANCE = 10;
|
||||
|
||||
function editRoute(onClick) {
|
||||
if (customization) return;
|
||||
if (!onClick && elSelected && d3.event.target.id === elSelected.attr("id")) return;
|
||||
|
|
@ -47,13 +50,13 @@ function editRoute(onClick) {
|
|||
}
|
||||
|
||||
function drawControlPoints(node) {
|
||||
const l = node.getTotalLength();
|
||||
const increment = l / Math.ceil(l / 4);
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
const totalLength = node.getTotalLength();
|
||||
const increment = totalLength / Math.ceil(totalLength / CONTROL_POINST_DISTANCE);
|
||||
for (let i = 0; i <= totalLength; i += increment) {
|
||||
const point = node.getPointAtLength(i);
|
||||
addControlPoint([point.x, point.y]);
|
||||
}
|
||||
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
routeLength.innerHTML = rn(totalLength * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
}
|
||||
|
||||
function addControlPoint(point, before = null) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,27 @@
|
|||
// UI module to control the style presets
|
||||
"use strict";
|
||||
|
||||
const systemPresets = ["default", "ancient", "gloom", "light", "watercolor", "clean", "atlas", "cyberpunk", "monochrome"];
|
||||
const systemPresets = [
|
||||
"default",
|
||||
"ancient",
|
||||
"gloom",
|
||||
"pale",
|
||||
"light",
|
||||
"watercolor",
|
||||
"clean",
|
||||
"atlas",
|
||||
"cyberpunk",
|
||||
"monochrome"
|
||||
];
|
||||
const customPresetPrefix = "fmgStyle_";
|
||||
|
||||
// add style presets to list
|
||||
{
|
||||
const systemOptions = systemPresets.map(styleName => `<option value="${styleName}">${styleName}</option>`);
|
||||
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith(customPresetPrefix));
|
||||
const customOptions = storedStyles.map(styleName => `<option value="${styleName}">${styleName.replace(customPresetPrefix, "")} [custom]</option>`);
|
||||
const customOptions = storedStyles.map(
|
||||
styleName => `<option value="${styleName}">${styleName.replace(customPresetPrefix, "")} [custom]</option>`
|
||||
);
|
||||
const options = systemOptions.join("") + customOptions.join("");
|
||||
document.getElementById("stylePreset").innerHTML = options;
|
||||
}
|
||||
|
|
@ -37,7 +50,8 @@ async function getStylePreset(desiredPreset) {
|
|||
const isValid = JSON.isValid(storedStyleJSON);
|
||||
if (isValid) return [desiredPreset, JSON.parse(storedStyleJSON)];
|
||||
|
||||
ERROR && console.error(`Custom style ${desiredPreset} stored in localStorage is not valid. Applying default style`);
|
||||
ERROR &&
|
||||
console.error(`Custom style ${desiredPreset} stored in localStorage is not valid. Applying default style`);
|
||||
presetToLoad = "default";
|
||||
}
|
||||
}
|
||||
|
|
@ -145,8 +159,31 @@ function addStylePreset() {
|
|||
"#stateBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||
"#provinceBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||
"#cells": ["opacity", "stroke", "stroke-width", "filter", "mask"],
|
||||
"#gridOverlay": ["opacity", "scale", "dx", "dy", "type", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "transform", "filter", "mask"],
|
||||
"#coordinates": ["opacity", "data-size", "font-size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#gridOverlay": [
|
||||
"opacity",
|
||||
"scale",
|
||||
"dx",
|
||||
"dy",
|
||||
"type",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"stroke-dasharray",
|
||||
"stroke-linecap",
|
||||
"transform",
|
||||
"filter",
|
||||
"mask"
|
||||
],
|
||||
"#coordinates": [
|
||||
"opacity",
|
||||
"data-size",
|
||||
"font-size",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"stroke-dasharray",
|
||||
"stroke-linecap",
|
||||
"filter",
|
||||
"mask"
|
||||
],
|
||||
"#compass": ["opacity", "transform", "filter", "mask", "shape-rendering"],
|
||||
"#rose": ["transform"],
|
||||
"#relig": ["opacity", "stroke", "stroke-width", "filter"],
|
||||
|
|
@ -174,7 +211,17 @@ function addStylePreset() {
|
|||
"#statesBody": ["opacity", "filter"],
|
||||
"#statesHalo": ["opacity", "data-width", "stroke-width", "filter"],
|
||||
"#provs": ["opacity", "fill", "font-size", "font-family", "filter"],
|
||||
"#temperature": ["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||
"#temperature": [
|
||||
"opacity",
|
||||
"font-size",
|
||||
"fill",
|
||||
"fill-opacity",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"stroke-dasharray",
|
||||
"stroke-linecap",
|
||||
"filter"
|
||||
],
|
||||
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#emblems": ["opacity", "stroke-width", "filter"],
|
||||
"#texture": ["opacity", "filter", "mask"],
|
||||
|
|
@ -184,16 +231,65 @@ function addStylePreset() {
|
|||
"#oceanBase": ["fill"],
|
||||
"#oceanicPattern": ["href", "opacity"],
|
||||
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
|
||||
"#legend": ["data-size", "font-size", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"],
|
||||
"#legend": [
|
||||
"data-size",
|
||||
"font-size",
|
||||
"font-family",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"stroke-dasharray",
|
||||
"stroke-linecap",
|
||||
"data-x",
|
||||
"data-y",
|
||||
"data-columns"
|
||||
],
|
||||
"#legendBox": ["fill", "fill-opacity"],
|
||||
"#burgLabels > #cities": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
|
||||
"#burgIcons > #cities": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
|
||||
"#burgIcons > #cities": [
|
||||
"opacity",
|
||||
"fill",
|
||||
"fill-opacity",
|
||||
"size",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"stroke-dasharray",
|
||||
"stroke-linecap"
|
||||
],
|
||||
"#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"],
|
||||
"#burgLabels > #towns": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
|
||||
"#burgIcons > #towns": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
|
||||
"#burgIcons > #towns": [
|
||||
"opacity",
|
||||
"fill",
|
||||
"fill-opacity",
|
||||
"size",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"stroke-dasharray",
|
||||
"stroke-linecap"
|
||||
],
|
||||
"#anchors > #towns": ["opacity", "fill", "size", "stroke", "stroke-width"],
|
||||
"#labels > #states": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
|
||||
"#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
|
||||
"#labels > #states": [
|
||||
"opacity",
|
||||
"fill",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"text-shadow",
|
||||
"data-size",
|
||||
"font-size",
|
||||
"font-family",
|
||||
"filter"
|
||||
],
|
||||
"#labels > #addedLabels": [
|
||||
"opacity",
|
||||
"fill",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"text-shadow",
|
||||
"data-size",
|
||||
"font-size",
|
||||
"font-family",
|
||||
"filter"
|
||||
],
|
||||
"#fogging": ["opacity", "fill", "filter"]
|
||||
};
|
||||
|
||||
|
|
@ -238,7 +334,8 @@ function addStylePreset() {
|
|||
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
|
||||
if (!JSON.isValid(styleJSON)) return tip("JSON string is not valid, please check the format", false, "error");
|
||||
if (!desiredName) return tip("Please provide a preset name", false, "error");
|
||||
if (styleSaverTip.innerHTML === "default") return tip("You cannot overwrite default preset, please change the name", false, "error");
|
||||
if (styleSaverTip.innerHTML === "default")
|
||||
return tip("You cannot overwrite default preset, please change the name", false, "error");
|
||||
|
||||
const presetName = customPresetPrefix + desiredName;
|
||||
applyOption(stylePreset, presetName, desiredName + " [custom]");
|
||||
|
|
|
|||
|
|
@ -74,10 +74,8 @@ toolsContent.addEventListener("click", function (event) {
|
|||
});
|
||||
|
||||
function processFeatureRegeneration(event, button) {
|
||||
if (button === "regenerateStateLabels") {
|
||||
BurgsAndStates.drawStateLabels();
|
||||
if (!layerIsOn("toggleLabels")) toggleLabels();
|
||||
} else if (button === "regenerateReliefIcons") {
|
||||
if (button === "regenerateStateLabels") BurgsAndStates.drawStateLabels();
|
||||
else if (button === "regenerateReliefIcons") {
|
||||
ReliefIcons();
|
||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||
} else if (button === "regenerateRoutes") {
|
||||
|
|
@ -628,10 +626,11 @@ function addRiverOnClick() {
|
|||
getType,
|
||||
getWidth,
|
||||
getOffset,
|
||||
getApproximateLength
|
||||
getApproximateLength,
|
||||
getNextId
|
||||
} = Rivers;
|
||||
const riverCells = [];
|
||||
let riverId = rivers.length ? last(rivers).i + 1 : 1;
|
||||
let riverId = getNextId(rivers);
|
||||
let parent = riverId;
|
||||
|
||||
const initialFlux = grid.cells.prec[cells.g[i]];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue