mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 03:51:23 +01:00
commit
8fd9b82554
30 changed files with 1761 additions and 613 deletions
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
# Fantasy Map Generator
|
||||
|
||||
Azgaar's _Fantasy Map Generator_ is a free web application generating interactive and highly customizable svg maps based on voronoi diagram.
|
||||
Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps.
|
||||
|
||||
Project is under development, the current version is available on [Github Pages](https://azgaar.github.io/Fantasy-Map-Generator).
|
||||
Link: [azgaar.github.io/Fantasy-Map-Generator](https://azgaar.github.io/Fantasy-Map-Generator).
|
||||
|
||||
Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for guidance. The current progress is tracked in [Trello](https://trello.com/b/7x832DG4/fantasy-map-generator). Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
Azgaar's Fantasy Map Generator
|
||||
|
||||
Developed by Azgaar (azgaar.fmg@yandex.com) and contributors
|
||||
|
||||
Minsk, 2017-2021. MIT License
|
||||
|
||||
https://github.com/Azgaar/Fantasy-Map-Generator
|
||||
|
||||
To run the tool unzip ALL files and open index.html in browser
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
To get heightmap with correct height scale:
|
||||
1. Open tangrams.github.io
|
||||
1. Open https://tangrams.github.io/heightmapper
|
||||
2. Toggle off auto-exposure
|
||||
3. Set max elevation to 2000
|
||||
4. Set min elevation to -500
|
||||
|
|
|
|||
|
|
@ -1082,12 +1082,16 @@ tr.battleSurvivors {
|
|||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#battleBody div.battlePhases,
|
||||
#battleBottom div.battleTypes {
|
||||
position: fixed;
|
||||
background-color: #ffffff30;
|
||||
}
|
||||
|
||||
#battleBody div.battlePhases {
|
||||
position: absolute;
|
||||
background-color: #ffffff30;
|
||||
}
|
||||
|
||||
#battleBody div.battlePhases > button,
|
||||
#battleBottom div.battleTypes > button {
|
||||
width: 3.2em;
|
||||
|
|
@ -2045,6 +2049,7 @@ div.textual span,
|
|||
}
|
||||
|
||||
#notesLegend {
|
||||
width: auto;
|
||||
height: 87%;
|
||||
outline: 0;
|
||||
overflow-y: auto;
|
||||
|
|
@ -2239,7 +2244,6 @@ svg.button {
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
.dontAsk {
|
||||
margin: 0.9em 0 0 0.6em;
|
||||
display: inline-flex;
|
||||
|
|
|
|||
75
index.html
75
index.html
|
|
@ -3,14 +3,33 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>Azgaar's Fantasy Map Generator</title>
|
||||
<meta name="application-name" content="Azgaar's Fantasy Map Generator" />
|
||||
<meta name="author" content="Azgaar (Max Ganiev)" />
|
||||
<meta name="description" content="Azgaar's Fantasy Map Generator and Editor" />
|
||||
<meta name="author" content="Azgaar" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Free web app that helps fantasy writers, game masters, and cartographers create and edit fantasy maps"
|
||||
/>
|
||||
|
||||
<meta property="og:url" content="https://azgaar.github.io/Fantasy-Map-Generator" />
|
||||
<meta property="og:title" content="Azgaar's Fantasy Map Generator" />
|
||||
<meta property="og:description" content="Web application generating interactive and customizable maps" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Free web app that helps fantasy writers, game masters, and cartographers create and edit fantasy maps"
|
||||
/>
|
||||
<meta property="og:image" content="images/preview.png" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="azgaar.github.io" />
|
||||
<meta property="twitter:url" content="https://azgaar.github.io/Fantasy-Map-Generator/" />
|
||||
<meta name="twitter:title" content="Azgaar's Fantasy Map Generator" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Free web app that helps fantasy writers, game masters, and cartographers create and edit fantasy maps"
|
||||
/>
|
||||
<meta name="twitter:image" content="images/preview.png" />
|
||||
|
||||
<link rel="icon" type="image/png" href="images/icons/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="images/icons/favicon-16x16.png" sizes="16x16" />
|
||||
<link rel="apple-touch-icon" href="images/icons/maskable_icon_x192.png" />
|
||||
|
|
@ -108,7 +127,7 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<link rel="preload" href="index.css?v=1.89.00" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
<link rel="preload" href="index.css?v=1.89.13" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
<link rel="preload" href="icons.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
<link rel="preload" href="libs/jquery-ui.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
</head>
|
||||
|
|
@ -1237,10 +1256,6 @@
|
|||
<td><select id="styleStatesBodyFilter" /></td>
|
||||
</tr>
|
||||
|
||||
<tr style="margin-top: 1em">
|
||||
<td><em>Halo is disabled if "Rendering" option is set to "Best performance"</em></td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set states halo effect width">
|
||||
<td>Halo width</td>
|
||||
<td>
|
||||
|
|
@ -1446,7 +1461,7 @@
|
|||
</p>
|
||||
<table>
|
||||
<tr
|
||||
data-tip="Canvas width and height in pixels. Defines map size on generation, then map size cannot be changed and canvas size changes only visible area. Keep canvas size equal to screen size or less to improve performance. The best aspect ratio for maps is 2:1"
|
||||
data-tip="Canvas width and height in pixels. Defines map size on generation that cannot be changed later. Always keep canvas size equal to your screen size or less. The best option is to use the default value. For full-globe maps use aspect ratio 2:1"
|
||||
>
|
||||
<td></td>
|
||||
<td>Canvas size</td>
|
||||
|
|
@ -1458,7 +1473,7 @@
|
|||
</td>
|
||||
<td>
|
||||
<i
|
||||
data-tip="Toggle between screen size and initial canvas size"
|
||||
data-tip="Toggle between the current screen size and the initial canvas size"
|
||||
id="toggleFullscreen"
|
||||
class="icon-resize-full-alt"
|
||||
></i>
|
||||
|
|
@ -1883,8 +1898,8 @@
|
|||
<td>Rendering</td>
|
||||
<td>
|
||||
<select id="shapeRendering" data-stored="shapeRendering">
|
||||
<option value="geometricPrecision" selected>Best quality</option>
|
||||
<option value="optimizeSpeed">Best performace</option>
|
||||
<option value="geometricPrecision">Best quality</option>
|
||||
<option value="optimizeSpeed" selected>Best performace</option>
|
||||
</select>
|
||||
</td>
|
||||
<td></td>
|
||||
|
|
@ -2749,7 +2764,7 @@
|
|||
<div id="iceEditor" class="dialog" style="display: none">
|
||||
<button id="iceEditStyle" data-tip="Edit style in Style Editor" class="icon-brush"></button>
|
||||
<button id="iceRandomize" data-tip="Randomize Iceberg shape" class="icon-shuffle"></button>
|
||||
<input id="iceSize" data-tip="Change Iceberg size" type="range" min=".05" max="1" step=".01" />
|
||||
<input id="iceSize" data-tip="Change Iceberg size" type="range" min=".05" max="2" step=".01" />
|
||||
<button id="iceNew" data-tip="Add an Iceberg (click on map)" class="icon-plus"></button>
|
||||
<button
|
||||
id="iceRemove"
|
||||
|
|
@ -5602,7 +5617,7 @@
|
|||
</div>
|
||||
|
||||
<div id="iconSelector" style="display: none" class="dialog">
|
||||
<table id="iconTable" class="table pointer" style="font-size: 2em; text-align: center"></table>
|
||||
<table id="iconTable" class="table pointer" style="font-size: 2em; text-align: center; width: 100%"></table>
|
||||
<div style="font-style: italic; font-size: 1.2em; margin: 0.4em 0 0 0.4em">
|
||||
<span>Select from the list or paste a Unicode character here: </span>
|
||||
<input id="iconInput" style="width: 2em" />
|
||||
|
|
@ -7830,7 +7845,7 @@
|
|||
<script src="utils/colorUtils.js"></script>
|
||||
<script src="utils/graphUtils.js?v=1.88.02"></script>
|
||||
<script src="utils/nodeUtils.js"></script>
|
||||
<script src="utils/numberUtils.js"></script>
|
||||
<script src="utils/numberUtils.js?v=1.89.08"></script>
|
||||
<script src="utils/polyfills.js"></script>
|
||||
<script src="utils/probabilityUtils.js?v=1.88.00"></script>
|
||||
<script src="utils/stringUtils.js"></script>
|
||||
|
|
@ -7841,14 +7856,14 @@
|
|||
<script src="config/heightmap-templates.js"></script>
|
||||
<script src="config/precreated-heightmaps.js"></script>
|
||||
<script src="modules/heightmap-generator.js?v=1.88.00"></script>
|
||||
<script src="modules/ocean-layers.js?v=1.87.15"></script>
|
||||
<script src="modules/river-generator.js"></script>
|
||||
<script src="modules/ocean-layers.js?v=1.89.08"></script>
|
||||
<script src="modules/river-generator.js?v=1.89.13"></script>
|
||||
<script src="modules/lakes.js"></script>
|
||||
<script src="modules/names-generator.js?v=1.87.14"></script>
|
||||
<script src="modules/cultures-generator.js?v=1.89.00"></script>
|
||||
<script src="modules/burgs-and-states.js?v=1.89.00"></script>
|
||||
<script src="modules/cultures-generator.js?v=1.89.10"></script>
|
||||
<script src="modules/burgs-and-states.js?v=1.89.07"></script>
|
||||
<script src="modules/routes-generator.js"></script>
|
||||
<script src="modules/religions-generator.js?v=1.89.00"></script>
|
||||
<script src="modules/religions-generator.js?v=1.89.15"></script>
|
||||
<script src="modules/military-generator.js"></script>
|
||||
<script src="modules/markers-generator.js?v=1.87.13"></script>
|
||||
<script src="modules/coa-generator.js"></script>
|
||||
|
|
@ -7859,34 +7874,34 @@
|
|||
<script src="modules/fonts.js"></script>
|
||||
<script src="modules/ui/layers.js"></script>
|
||||
<script src="modules/ui/measurers.js?v=1.87.02"></script>
|
||||
<script src="modules/ui/stylePresets.js"></script>
|
||||
<script src="modules/ui/stylePresets.js?v=1.89.11"></script>
|
||||
|
||||
<script src="modules/ui/general.js?v=1.87.03"></script>
|
||||
<script src="modules/ui/options.js?v=1.88.02"></script>
|
||||
<script src="main.js?v=1.88.02"></script>
|
||||
<script src="modules/ui/options.js?v=1.88.14"></script>
|
||||
<script src="main.js?v=1.89.12"></script>
|
||||
|
||||
<script defer src="modules/relief-icons.js"></script>
|
||||
<script defer src="modules/ui/style.js"></script>
|
||||
<script defer src="modules/ui/editors.js?v=1.87.07"></script>
|
||||
<script defer src="modules/ui/tools.js?v=1.89.00"></script>
|
||||
<script defer src="modules/ui/editors.js?v=1.89.12"></script>
|
||||
<script defer src="modules/ui/tools.js?v=1.89.13"></script>
|
||||
<script defer src="modules/ui/world-configurator.js"></script>
|
||||
<script defer src="modules/ui/heightmap-editor.js?v=1.88.03"></script>
|
||||
<script defer src="modules/ui/heightmap-editor.js?v=1.89.06"></script>
|
||||
<script defer src="modules/ui/provinces-editor.js?v=1.89.00"></script>
|
||||
<script defer src="modules/ui/biomes-editor.js"></script>
|
||||
<script defer src="modules/ui/namesbase-editor.js?v=1.87.10"></script>
|
||||
<script defer src="modules/ui/elevation-profile.js"></script>
|
||||
<script defer src="modules/ui/temperature-graph.js"></script>
|
||||
<script defer src="modules/ui/routes-editor.js"></script>
|
||||
<script defer src="modules/ui/ice-editor.js"></script>
|
||||
<script defer src="modules/ui/routes-editor.js?v=1.89.04"></script>
|
||||
<script defer src="modules/ui/ice-editor.js?v=1.89.08"></script>
|
||||
<script defer src="modules/ui/lakes-editor.js?v=1.87.10"></script>
|
||||
<script defer src="modules/ui/coastline-editor.js"></script>
|
||||
<script defer src="modules/ui/labels-editor.js"></script>
|
||||
<script defer src="modules/ui/rivers-editor.js"></script>
|
||||
<script defer src="modules/ui/rivers-creator.js"></script>
|
||||
<script defer src="modules/ui/rivers-creator.js?v=1.89.13"></script>
|
||||
<script defer src="modules/ui/relief-editor.js"></script>
|
||||
<script defer src="modules/ui/burg-editor.js"></script>
|
||||
<script defer src="modules/ui/units-editor.js"></script>
|
||||
<script defer src="modules/ui/notes-editor.js"></script>
|
||||
<script defer src="modules/ui/notes-editor.js?v=1.89.03"></script>
|
||||
<script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script>
|
||||
<script defer src="modules/ui/zones-editor.js"></script>
|
||||
<script defer src="modules/ui/burgs-overview.js"></script>
|
||||
|
|
|
|||
2
main.js
2
main.js
|
|
@ -191,7 +191,6 @@ let populationRate = +document.getElementById("populationRateInput").value;
|
|||
let distanceScale = +document.getElementById("distanceScaleInput").value;
|
||||
let urbanization = +document.getElementById("urbanizationInput").value;
|
||||
let urbanDensity = +document.getElementById("urbanDensityInput").value;
|
||||
let statesNeutral = 1; // statesEditor growth parameter
|
||||
|
||||
applyStoredOptions();
|
||||
|
||||
|
|
@ -695,6 +694,7 @@ async function generate(options) {
|
|||
if (shouldRegenerateGrid(grid, precreatedSeed)) grid = precreatedGraph || generateGrid();
|
||||
else delete grid.cells.h;
|
||||
grid.cells.h = await HeightmapGenerator.generate(grid);
|
||||
pack = {}; // reset pack
|
||||
|
||||
markFeatures();
|
||||
markupGridOcean();
|
||||
|
|
|
|||
|
|
@ -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 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: s.center, p: 0, s: s.i, b});
|
||||
cost[s.center] = 1;
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
||||
for (let culture, rnd, i = 0; cultures.length < count && i < 200; i++) {
|
||||
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 < 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,7 +304,12 @@ window.Religions = (function () {
|
|||
Heresy: {Heresy: 1}
|
||||
};
|
||||
|
||||
const methods = {
|
||||
const namingMethods = {
|
||||
Folk: {
|
||||
"Culture + type": 1
|
||||
},
|
||||
|
||||
Organized: {
|
||||
"Random + type": 3,
|
||||
"Random + ism": 1,
|
||||
"Supreme + ism": 5,
|
||||
|
|
@ -313,6 +318,20 @@ window.Religions = (function () {
|
|||
"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");
|
||||
}
|
||||
|
||||
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}));
|
||||
}
|
||||
|
||||
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 candidateCells = getCandidateCells();
|
||||
const religionCores = placeReligions();
|
||||
|
||||
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;
|
||||
|
||||
const getType = index => {
|
||||
if (index < organizedCount) return "Organized";
|
||||
if (index < organizedCount + cultsCount) return "Cult";
|
||||
return "Heresy";
|
||||
};
|
||||
|
||||
return religionCores.map((cellId, index) => {
|
||||
const type = getType(index);
|
||||
const form = rw(forms[type]);
|
||||
const cultureId = cells.culture[cellId];
|
||||
|
||||
return {type, form, culture: cultureId, center: cellId};
|
||||
});
|
||||
|
||||
function placeReligions() {
|
||||
const religionCells = [];
|
||||
const religionsTree = d3.quadtree();
|
||||
|
||||
// pre-populate with locked centers
|
||||
lockedReligions.forEach(({center}) => religionsTree.add(cells.p[center]));
|
||||
|
||||
// min distance between religion inceptions
|
||||
const spacing = (graphWidth + graphHeight) / 2 / desiredReligionNumber;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return foundReligions.size ? [...foundReligions] : [fallbackOrigin];
|
||||
}
|
||||
|
||||
// growth algorithm to assign cells to religions
|
||||
function expandReligions() {
|
||||
function expandReligions(religions) {
|
||||
const cells = pack.cells;
|
||||
const religionIds = spreadFolkReligions(religions);
|
||||
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
|
||||
const maxExpansionCost = (cells.i.length / 20) * neutralInput.value; // limit cost for organized religions growth
|
||||
|
||||
const biomePassageCost = cellId => biomesData.cost[cells.biome[cellId]];
|
||||
|
||||
religions
|
||||
.filter(r => !r.lock && (r.type === "Organized" || r.type === "Cult"))
|
||||
.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], c: r.culture});
|
||||
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center]});
|
||||
cost[r.center] = 1;
|
||||
});
|
||||
|
||||
const neutral = (cells.i.length / 5000) * 200 * gauss(1, 0.3, 0.2, 2, 2) * neutralInput.value; // limit cost for organized religions growth
|
||||
const popCost = d3.max(cells.pop) / 3; // enougth population to spered religion without penalty
|
||||
const religionsMap = new Map(religions.map(r => [r.i, r]));
|
||||
|
||||
const isMainRoad = cellId => cells.road[cellId] - cells.crossroad[cellId] > 4;
|
||||
const isTrail = cellId => cells.h[cellId] > 19 && cells.road[cellId] - cells.crossroad[cellId] === 1;
|
||||
const isSeaRoute = cellId => cells.h[cellId] < 20 && cells.road[cellId];
|
||||
const isWater = cellId => cells.h[cellId] < 20;
|
||||
|
||||
while (queue.length) {
|
||||
const {e, p, r, c, s} = queue.dequeue();
|
||||
const expansion = religions[r].expansion;
|
||||
const {e: cellId, p, r, s: state} = queue.dequeue();
|
||||
const {culture, expansion, expansionism} = religionsMap.get(r);
|
||||
|
||||
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;
|
||||
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 = 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;
|
||||
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.h[nextCell] >= 20 && cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
|
||||
if (cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
|
||||
cost[nextCell] = totalCost;
|
||||
queue.queue({e: nextCell, p: totalCost, r, c, s});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// growth algorithm to assign cells to heresies
|
||||
function expandHeresies() {
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
// 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);
|
||||
|
||||
religions
|
||||
.filter(r => !r.lock && r.type === "Heresy")
|
||||
.forEach(r => {
|
||||
const b = religionIds[r.center]; // "base" religion id
|
||||
religionIds[r.center] = r.i; // heresy id
|
||||
queue.queue({e: r.center, p: 0, r: r.i, b});
|
||||
cost[r.center] = 1;
|
||||
});
|
||||
const folkReligions = religions.filter(religion => religion.type === "Folk" && !religion.removed);
|
||||
const cultureToReligionMap = new Map(folkReligions.map(({i, culture}) => [culture, i]));
|
||||
|
||||
const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth
|
||||
|
||||
while (queue.length) {
|
||||
const {e, p, r, b} = queue.dequeue();
|
||||
|
||||
cells.c[e].forEach(nextCell => {
|
||||
if (religions[religionIds[nextCell]]?.lock) return;
|
||||
const religionCost = religionIds[nextCell] === b ? 0 : 2000;
|
||||
const biomeCost = cells.road[nextCell] ? 0 : biomesData.cost[cells.biome[nextCell]];
|
||||
const heightCost = Math.max(cells.h[nextCell], 20) - 20;
|
||||
const waterCost = cells.h[nextCell] < 20 ? (cells.road[nextCell] ? 50 : 1000) : 0;
|
||||
const totalCost =
|
||||
p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1);
|
||||
|
||||
if (totalCost > neutral) return;
|
||||
|
||||
if (!cost[nextCell] || totalCost < cost[nextCell]) {
|
||||
if (cells.h[nextCell] >= 20 && cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
|
||||
cost[nextCell] = totalCost;
|
||||
queue.queue({e: nextCell, p: totalCost, r});
|
||||
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 codes = religions.map(r => r.code);
|
||||
religions.forEach(r => {
|
||||
const cells = pack.cells;
|
||||
pack.religions.forEach(r => {
|
||||
if (!r.i) return;
|
||||
r.code = abbreviate(r.name, codes);
|
||||
|
||||
// move religion center if it's not within religion area after expansion
|
||||
if (religionIds[r.center] === r.i) return; // in area
|
||||
const firstCell = cells.i.find(i => religionIds[i] === r.i);
|
||||
if (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]];
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@
|
|||
"opacity": 0.4,
|
||||
"data-width": 10,
|
||||
"stroke-width": 10,
|
||||
"filter": "blur(5px)"
|
||||
"filter": "blur(3.5px)"
|
||||
},
|
||||
"#provs": {
|
||||
"opacity": 0.7,
|
||||
|
|
|
|||
|
|
@ -192,18 +192,18 @@
|
|||
"filter": null
|
||||
},
|
||||
"#roads": {
|
||||
"opacity": 0.9,
|
||||
"stroke": "#3c1d0b",
|
||||
"stroke-width": 1.37,
|
||||
"opacity": 0.8,
|
||||
"stroke": "#95481a",
|
||||
"stroke-width": 0.8,
|
||||
"stroke-dasharray": 2,
|
||||
"stroke-linecap": "inherit",
|
||||
"filter": null,
|
||||
"mask": null
|
||||
},
|
||||
"#trails": {
|
||||
"opacity": 0.9,
|
||||
"opacity": 0.8,
|
||||
"stroke": "#95481a",
|
||||
"stroke-width": 0.88,
|
||||
"stroke-width": 0.5,
|
||||
"stroke-dasharray": ".8 1.6",
|
||||
"stroke-linecap": "butt",
|
||||
"filter": null,
|
||||
|
|
|
|||
389
styles/pale.json
Normal file
389
styles/pale.json
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
{
|
||||
"#map": {
|
||||
"background-color": "#000000",
|
||||
"filter": null,
|
||||
"data-filter": null
|
||||
},
|
||||
"#armies": {
|
||||
"font-size": 9,
|
||||
"box-size": 4.5,
|
||||
"stroke": "#000",
|
||||
"stroke-width": 0,
|
||||
"fill-opacity": 1,
|
||||
"filter": "url(#dropShadow05)"
|
||||
},
|
||||
"#biomes": {
|
||||
"opacity": 0.6,
|
||||
"filter": null,
|
||||
"mask": "url(#land)"
|
||||
},
|
||||
"#stateBorders": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#4c483e",
|
||||
"stroke-width": 0.8,
|
||||
"stroke-dasharray": "1 2.5",
|
||||
"stroke-linecap": "square",
|
||||
"filter": null
|
||||
},
|
||||
"#provinceBorders": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#56566d",
|
||||
"stroke-width": 0.2,
|
||||
"stroke-dasharray": 0.5,
|
||||
"stroke-linecap": "butt",
|
||||
"filter": null
|
||||
},
|
||||
"#cells": {
|
||||
"opacity": null,
|
||||
"stroke": "#808080",
|
||||
"stroke-width": 0.1,
|
||||
"filter": null,
|
||||
"mask": null
|
||||
},
|
||||
"#gridOverlay": {
|
||||
"opacity": 0.5,
|
||||
"scale": 1,
|
||||
"dx": 0,
|
||||
"dy": 0,
|
||||
"type": "pointyHex",
|
||||
"stroke": "#808080",
|
||||
"stroke-width": 1,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"transform": null,
|
||||
"filter": null,
|
||||
"mask": null
|
||||
},
|
||||
"#coordinates": {
|
||||
"opacity": 0.7,
|
||||
"data-size": 15,
|
||||
"font-size": 15,
|
||||
"stroke": "#734d37",
|
||||
"stroke-width": 1.5,
|
||||
"stroke-dasharray": 5,
|
||||
"stroke-linecap": "square",
|
||||
"filter": null,
|
||||
"mask": ""
|
||||
},
|
||||
"#compass": {
|
||||
"opacity": 0.6,
|
||||
"transform": null,
|
||||
"filter": null,
|
||||
"mask": "url(#water)",
|
||||
"shape-rendering": "optimizespeed"
|
||||
},
|
||||
"#rose": {
|
||||
"transform": null
|
||||
},
|
||||
"#relig": {
|
||||
"opacity": 0.5,
|
||||
"stroke": null,
|
||||
"stroke-width": 0,
|
||||
"filter": null
|
||||
},
|
||||
"#cults": {
|
||||
"opacity": 0.5,
|
||||
"stroke": "#777777",
|
||||
"stroke-width": 0,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
},
|
||||
"#landmass": {
|
||||
"opacity": 1,
|
||||
"fill": "#f4f2f0",
|
||||
"filter": null
|
||||
},
|
||||
"#markers": {
|
||||
"opacity": null,
|
||||
"rescale": 1,
|
||||
"filter": null
|
||||
},
|
||||
"#prec": {
|
||||
"opacity": null,
|
||||
"stroke": "#000000",
|
||||
"stroke-width": 0.1,
|
||||
"fill": "#2554ef",
|
||||
"filter": null
|
||||
},
|
||||
"#population": {
|
||||
"opacity": null,
|
||||
"stroke-width": 1.6,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": "butt",
|
||||
"filter": null
|
||||
},
|
||||
"#rural": {
|
||||
"stroke": "#0000ff"
|
||||
},
|
||||
"#urban": {
|
||||
"stroke": "#ff0000"
|
||||
},
|
||||
"#freshwater": {
|
||||
"opacity": 0.8,
|
||||
"fill": "#98b6cd",
|
||||
"stroke": "#718798",
|
||||
"stroke-width": 0.5,
|
||||
"filter": "url(#dropShadow05)"
|
||||
},
|
||||
"#salt": {
|
||||
"opacity": 0.5,
|
||||
"fill": "#409b8a",
|
||||
"stroke": "#388985",
|
||||
"stroke-width": 0.7,
|
||||
"filter": null
|
||||
},
|
||||
"#sinkhole": {
|
||||
"opacity": 1,
|
||||
"fill": "#5bc9fd",
|
||||
"stroke": "#53a3b0",
|
||||
"stroke-width": 0.7,
|
||||
"filter": null
|
||||
},
|
||||
"#frozen": {
|
||||
"opacity": 0.95,
|
||||
"fill": "#cdd4e7",
|
||||
"stroke": "#cfe0eb",
|
||||
"stroke-width": 0,
|
||||
"filter": null
|
||||
},
|
||||
"#lava": {
|
||||
"opacity": 0.7,
|
||||
"fill": "#90270d",
|
||||
"stroke": "#f93e0c",
|
||||
"stroke-width": 2,
|
||||
"filter": "url(#crumpled)"
|
||||
},
|
||||
"#dry": {
|
||||
"opacity": 1,
|
||||
"fill": "#c9bfa7",
|
||||
"stroke": "#8e816f",
|
||||
"stroke-width": 0.7,
|
||||
"filter": null
|
||||
},
|
||||
"#sea_island": {
|
||||
"opacity": 1,
|
||||
"stroke": "#242424",
|
||||
"stroke-width": 0.1,
|
||||
"filter": "url(#dropShadow)",
|
||||
"auto-filter": 1
|
||||
},
|
||||
"#lake_island": {
|
||||
"opacity": 1,
|
||||
"stroke": "#7c8eaf",
|
||||
"stroke-width": 0.1,
|
||||
"filter": null
|
||||
},
|
||||
"#terrain": {
|
||||
"opacity": 0.8,
|
||||
"set": "simple",
|
||||
"size": 0.7,
|
||||
"density": 0.3,
|
||||
"filter": null,
|
||||
"mask": ""
|
||||
},
|
||||
"#rivers": {
|
||||
"opacity": 1,
|
||||
"filter": null,
|
||||
"fill": "#6dabba"
|
||||
},
|
||||
"#ruler": {
|
||||
"opacity": null,
|
||||
"filter": null
|
||||
},
|
||||
"#roads": {
|
||||
"opacity": 0.9,
|
||||
"stroke": "#d06324",
|
||||
"stroke-width": 0.6,
|
||||
"stroke-dasharray": "1 2",
|
||||
"stroke-linecap": "round",
|
||||
"filter": null,
|
||||
"mask": null
|
||||
},
|
||||
"#trails": {
|
||||
"opacity": 0.9,
|
||||
"stroke": "#d06324",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-dasharray": ".5 2",
|
||||
"stroke-linecap": "round",
|
||||
"filter": null,
|
||||
"mask": null
|
||||
},
|
||||
"#searoutes": {
|
||||
"opacity": 1,
|
||||
"stroke": "#e5edff",
|
||||
"stroke-width": 0.5,
|
||||
"stroke-dasharray": "2 3",
|
||||
"stroke-linecap": "round",
|
||||
"filter": null,
|
||||
"mask": null
|
||||
},
|
||||
"#statesBody": {
|
||||
"opacity": 0.15,
|
||||
"filter": null
|
||||
},
|
||||
"#statesHalo": {
|
||||
"opacity": 0.3,
|
||||
"data-width": 10,
|
||||
"stroke-width": 10,
|
||||
"filter": "blur(3.5px)"
|
||||
},
|
||||
"#provs": {
|
||||
"opacity": 0.4,
|
||||
"fill": "#000000",
|
||||
"font-size": 8,
|
||||
"font-family": "Arima Madurai",
|
||||
"filter": null
|
||||
},
|
||||
"#temperature": {
|
||||
"opacity": null,
|
||||
"font-size": "8px",
|
||||
"fill": "#000000",
|
||||
"fill-opacity": 0.3,
|
||||
"stroke": null,
|
||||
"stroke-width": 1.8,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": null,
|
||||
"filter": null
|
||||
},
|
||||
"#ice": {
|
||||
"opacity": 0.9,
|
||||
"fill": "#e8f0f6",
|
||||
"stroke": "#e8f0f6",
|
||||
"stroke-width": 0.1,
|
||||
"filter": "url(#dropShadow05)"
|
||||
},
|
||||
"#emblems": {
|
||||
"opacity": 0.9,
|
||||
"stroke-width": 1,
|
||||
"filter": null
|
||||
},
|
||||
"#texture": {
|
||||
"opacity": 0.39,
|
||||
"filter": null,
|
||||
"mask": "url(#land)"
|
||||
},
|
||||
"#textureImage": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"#zones": {
|
||||
"opacity": 0.6,
|
||||
"stroke": "#333333",
|
||||
"stroke-width": 0,
|
||||
"stroke-dasharray": null,
|
||||
"stroke-linecap": "butt",
|
||||
"filter": null,
|
||||
"mask": null
|
||||
},
|
||||
"#oceanLayers": {
|
||||
"filter": "url(#dropShadow05)",
|
||||
"layers": "-6,-3,-1"
|
||||
},
|
||||
"#oceanBase": {
|
||||
"fill": "#7ca4b6"
|
||||
},
|
||||
"#oceanicPattern": {
|
||||
"href": "./images/kiwiroo.png",
|
||||
"opacity": 0.3
|
||||
},
|
||||
"#terrs": {
|
||||
"opacity": 0.7,
|
||||
"scheme": "bright",
|
||||
"terracing": 0,
|
||||
"skip": 2,
|
||||
"relax": 1,
|
||||
"curve": 0,
|
||||
"filter": "",
|
||||
"mask": "url(#land)"
|
||||
},
|
||||
"#legend": {
|
||||
"data-size": 13,
|
||||
"font-size": 13,
|
||||
"font-family": "Arima Madurai",
|
||||
"stroke": "#812929",
|
||||
"stroke-width": 2.5,
|
||||
"stroke-dasharray": "0 4 10 4",
|
||||
"stroke-linecap": "round",
|
||||
"data-x": 54.73,
|
||||
"data-y": 62.98,
|
||||
"data-columns": 8
|
||||
},
|
||||
"#burgLabels > #cities": {
|
||||
"opacity": 0.8,
|
||||
"fill": "#3a3a3a",
|
||||
"text-shadow": "white 0px 0px 4px",
|
||||
"data-size": 7,
|
||||
"font-size": 7,
|
||||
"font-family": "Arima Madurai"
|
||||
},
|
||||
"#burgIcons > #cities": {
|
||||
"opacity": 1,
|
||||
"fill": "#ffffff",
|
||||
"fill-opacity": 0.7,
|
||||
"size": 1.5,
|
||||
"stroke": "#4f4f4f",
|
||||
"stroke-width": 0.2,
|
||||
"stroke-dasharray": "",
|
||||
"stroke-linecap": "butt"
|
||||
},
|
||||
"#anchors > #cities": {
|
||||
"opacity": 1,
|
||||
"fill": "#ffffff",
|
||||
"size": 3,
|
||||
"stroke": "#3e3e4b",
|
||||
"stroke-width": 1.2
|
||||
},
|
||||
"#burgLabels > #towns": {
|
||||
"opacity": 0.8,
|
||||
"fill": "#3e3e4b",
|
||||
"text-shadow": "white 0px 0px 4px",
|
||||
"data-size": 4,
|
||||
"font-size": 4,
|
||||
"font-family": "Arima Madurai"
|
||||
},
|
||||
"#burgIcons > #towns": {
|
||||
"opacity": 1,
|
||||
"fill": "#ffffff",
|
||||
"fill-opacity": 0.7,
|
||||
"size": 0.6,
|
||||
"stroke": "#4f4f4f",
|
||||
"stroke-width": 0.12,
|
||||
"stroke-dasharray": "",
|
||||
"stroke-linecap": "butt"
|
||||
},
|
||||
"#anchors > #towns": {
|
||||
"opacity": 1,
|
||||
"fill": "#ffffff",
|
||||
"size": 1.2,
|
||||
"stroke": "#3e3e4b",
|
||||
"stroke-width": 1
|
||||
},
|
||||
"#labels > #states": {
|
||||
"opacity": 0.8,
|
||||
"fill": "#3e3e3e",
|
||||
"stroke": "#000000",
|
||||
"stroke-width": 0,
|
||||
"text-shadow": "white 0px 0px 6px",
|
||||
"data-size": 14,
|
||||
"font-size": 14,
|
||||
"font-family": "Arima Madurai",
|
||||
"filter": null
|
||||
},
|
||||
"#labels > #addedLabels": {
|
||||
"opacity": 1,
|
||||
"fill": "#f24706",
|
||||
"stroke": "#701b05",
|
||||
"stroke-width": 0.1,
|
||||
"text-shadow": "white 0px 0px 4px",
|
||||
"data-size": 6,
|
||||
"font-size": 6,
|
||||
"font-family": "Arima Madurai",
|
||||
"filter": null
|
||||
},
|
||||
"#fogging": {
|
||||
"opacity": 1,
|
||||
"fill": "#30426f",
|
||||
"filter": null
|
||||
}
|
||||
}
|
||||
8
sw.js
8
sw.js
|
|
@ -8,7 +8,10 @@ const {ExpirationPlugin} = workbox.expiration;
|
|||
const DAY = 24 * 60 * 60;
|
||||
|
||||
const getPolitics = ({entries, days}) => {
|
||||
return [new CacheableResponsePlugin({statuses: [0, 200]}), new ExpirationPlugin({maxEntries: entries, maxAgeSeconds: days * DAY})];
|
||||
return [
|
||||
new CacheableResponsePlugin({statuses: [0, 200]}),
|
||||
new ExpirationPlugin({maxEntries: entries, maxAgeSeconds: days * DAY})
|
||||
];
|
||||
};
|
||||
|
||||
registerRoute(
|
||||
|
|
@ -21,7 +24,8 @@ registerRoute(
|
|||
);
|
||||
|
||||
registerRoute(
|
||||
({request, url}) => request.destination === "script" && !url.pathname.endsWith("min.js") && !url.pathname.includes("versioning.js"),
|
||||
({request, url}) =>
|
||||
request.destination === "script" && !url.pathname.endsWith("min.js") && !url.pathname.includes("versioning.js"),
|
||||
new CacheFirst({
|
||||
cacheName: "fmg-scripts",
|
||||
plugins: getPolitics({entries: 100, days: 30})
|
||||
|
|
|
|||
|
|
@ -20,3 +20,7 @@ function lim(v) {
|
|||
function normalize(val, min, max) {
|
||||
return minmax((val - min) / (max - min), 0, 1);
|
||||
}
|
||||
|
||||
function lerp(a, b, t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
// version and caching control
|
||||
const version = "1.89.00"; // generator version, update each time
|
||||
const version = "1.89.15"; // generator version, update each time
|
||||
|
||||
{
|
||||
document.title += " v" + version;
|
||||
|
|
@ -28,6 +28,7 @@ const version = "1.89.00"; // generator version, update each time
|
|||
|
||||
<ul>
|
||||
<strong>Latest changes:</strong>
|
||||
<li>Religions can be edited and redrawn like cultures</li>
|
||||
<li>Lock states, provinces, cultures, and religions from regeneration</li>
|
||||
<li>Heightmap brushes: linear edit option</li>
|
||||
<li>Data Charts screen</li>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue