Merge branch 'master' into master

This commit is contained in:
dranorter 2021-10-11 05:19:24 -04:00 committed by GitHub
commit d0d8015c96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 980 additions and 351 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before After
Before After

View file

@ -31,7 +31,6 @@
#loading-text span:nth-child(2), #mapOverlay > span:nth-child(2) {animation-delay: 1s;} #loading-text span:nth-child(2), #mapOverlay > span:nth-child(2) {animation-delay: 1s;}
#loading-text span:nth-child(3), #mapOverlay > span:nth-child(3) {animation-delay: 2s;} #loading-text span:nth-child(3), #mapOverlay > span:nth-child(3) {animation-delay: 2s;}
@keyframes blink {0% {opacity: 0;} 20% {opacity: 1;} 100% {opacity: .1;}} @keyframes blink {0% {opacity: 0;} 20% {opacity: 1;} 100% {opacity: .1;}}
</style> </style>
<link rel="stylesheet" href="index.css"> <link rel="stylesheet" href="index.css">
<link rel="stylesheet" href="icons.css"> <link rel="stylesheet" href="icons.css">
@ -227,7 +226,7 @@
<div id="loading"> <div id="loading">
<div id="titleName"><t data-t="titleName">Azgaar's</t></div> <div id="titleName"><t data-t="titleName">Azgaar's</t></div>
<div id="title"><t data-t="title">Fantasy Map Generator</t></div> <div id="title"><t data-t="title">Fantasy Map Generator</t></div>
<div id="version"><t data-t="version">v. </t>1.7</div> <div id="version"><t data-t="version">v. </t>1.71</div>
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p> <p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
</div> </div>
@ -431,18 +430,24 @@
<td> <td>
<select id="styleTextureInput"> <select id="styleTextureInput">
<option value="none">None</option> <option value="none">None</option>
<option value="default" selected>Default</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2021/10/folded-paper-big.jpg">Folded paper big</option>
<option value="https://i.imgur.com/EWvXSou.jpg">Folded paper</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2021/10/folded-paper-small.jpg">Folded paper small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/marble-big.jpg">Marble big</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2021/10/gray-paper.jpg">Gray paper</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2021/10/soiled-paper.jpg">Soiled paper horizontal</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2021/10/soiled-paper-e1633784189147.jpg">Soided paper vertical</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2021/10/plaster.jpg">Plaster</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2021/10/ocean.jpg">Ocean</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/antique-small.jpg">Antique small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/antique-big.jpg">Antique big</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/pergamena-small.jpg">Pergamena small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2021/10/marble-big.jpg" selected>Marble big</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/marble-small.jpg">Marble small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/marble-blue-small.jpg">Marble Blue</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/marble-blue-small.jpg">Marble Blue</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/marble-blue-big.jpg">Marble Blue big</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/marble-blue-big.jpg">Marble Blue big</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/stone-small.jpg">Stone small</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/stone-small.jpg">Stone small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/stone-big.jpg">Stone big</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/stone-big.jpg">Stone big</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/timbercut-small.jpg">Timber Cut small</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/timbercut-small.jpg">Timber Cut small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/timbercut-big.jpg">Timber Cut big</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/timbercut-big.jpg">Timber Cut big</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/antique-small.jpg">Antique small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/antique-big.jpg">Antique big</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/pergamena-small.jpg">Pergamena small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/mars-small.jpg">Mars small</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/mars-small.jpg">Mars small</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/mars-big.jpg">Mars big</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/mars-big.jpg">Mars big</option>
<option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/mercury-small.jpg">Mercury small</option> <option value="https://i2.wp.com/azgaar.files.wordpress.com/2019/07/mercury-small.jpg">Mercury small</option>
@ -1430,7 +1435,7 @@
<p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share maps.</p> <p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share maps.</p>
<p>The project is under active development. Creator and main maintainer: Azgaar. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:azgaar.fmg@yandex.by" target="_blank">email</a>.</p> <p>The project is under active development. Creator and main maintainer: Azgaar. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:azgaar.fmg@yandex.by" target="_blank">email</a>.</p>
<div style="background-color: #e85b46; padding: .4em; width: max-content; margin: .6em auto 0 auto; border: 1px solid #943838"> <div style="background-color: #e85b46; padding: .4em; width: max-content; margin: .6em auto 0 auto; border: 1px solid #943838">
<a href="https://www.patreon.com/azgaar" target="_blank" style="color: white; text-decoration: none; font-family: sans-serif"> <a href="https://www.patreon.com/azgaar" onclick="track('click', 'patreon from about')" target="_blank" style="color: white; text-decoration: none; font-family: sans-serif">
<div> <div>
<div style="width: .8em; display: inline-block; padding: 0 .2em; fill: white"> <div style="width: .8em; display: inline-block; padding: 0 .2em; fill: white">
<svg viewBox="0 0 569 546"> <svg viewBox="0 0 569 546">
@ -3353,6 +3358,10 @@
<tr> <tr>
<th data-tip="Unit icon">Icon</th> <th data-tip="Unit icon">Icon</th>
<th data-tip="Unit name. If name is changed for existing unit, old unit will be replaced">Unit name</th> <th data-tip="Unit name. If name is changed for existing unit, old unit will be replaced">Unit name</th>
<th style="width:5em" data-tip="Select allowed biomes">Biomes</th>
<th style="width:5em" data-tip="Select allowed states">States</th>
<th style="width:5em" data-tip="Select allowed cultures">Cultures</th>
<th style="width:5em" data-tip="Select allowed religions">Religions</th>
<th data-tip="Conscription percentage for rural population">Rural</th> <th data-tip="Conscription percentage for rural population">Rural</th>
<th data-tip="Conscription percentage for urban population">Urban</th> <th data-tip="Conscription percentage for urban population">Urban</th>
<th data-tip="Average number of people in crew (used for total personnel calculation)">Crew</th> <th data-tip="Average number of people in crew (used for total personnel calculation)">Crew</th>
@ -4332,18 +4341,18 @@
<script src="modules/ui/measurers.js"></script> <script src="modules/ui/measurers.js"></script>
<script src="libs/umami.js"></script> <script src="libs/umami.js"></script>
<script async defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script> <script defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
<script async defer src="modules/ui/general.js"></script> <script defer src="modules/ui/general.js"></script>
<script async defer src="modules/ui/options.js"></script> <script defer src="modules/ui/options.js"></script>
<script async defer src="modules/ui/style.js"></script> <script defer src="modules/ui/style.js"></script>
<script async defer src="modules/load.js"></script> <script defer src="modules/load.js"></script>
<script async defer src="modules/cloud.js"></script> <script defer src="modules/cloud.js"></script>
<script async defer src="main.js"></script> <script defer src="main.js"></script>
<script async defer src="modules/save.js"></script> <script defer src="modules/save.js"></script>
<script async defer src="modules/export.js"></script> <script defer src="modules/export.js"></script>
<script async defer src="modules/relief-icons.js"></script> <script defer src="modules/relief-icons.js"></script>
<script async defer src="modules/ui/tools.js"></script> <script defer src="modules/ui/tools.js"></script>
<script async defer src="modules/ui/world-configurator.js"></script> <script defer src="modules/ui/world-configurator.js"></script>
<script async defer src="modules/ui/heightmap-editor.js"></script> <script async defer src="modules/ui/heightmap-editor.js"></script>
<script async defer src="modules/ui/states-editor.js"></script> <script async defer src="modules/ui/states-editor.js"></script>
<script async defer src="modules/ui/provinces-editor.js"></script> <script async defer src="modules/ui/provinces-editor.js"></script>

View file

@ -1,5 +1,5 @@
(window => { (window => {
const noTrack = window.localStorage.getItem("noTrack"); const noTrack = !location.hostname || window.localStorage.getItem("noTrack");
const { const {
screen: {width, height}, screen: {width, height},

42
main.js
View file

@ -2,7 +2,7 @@
// https://github.com/Azgaar/Fantasy-Map-Generator // https://github.com/Azgaar/Fantasy-Map-Generator
"use strict"; "use strict";
const version = "1.7"; // generator version const version = "1.71"; // generator version
document.title += " v" + version; document.title += " v" + version;
// Switches to disable/enable logging features // Switches to disable/enable logging features
@ -111,14 +111,14 @@ legend.on("mousemove", () => tip("Drag to change the position. Click to hide the
// main data variables // main data variables
let grid = {}; // initial grapg based on jittered square grid and data let grid = {}; // initial grapg based on jittered square grid and data
let pack = {}; // packed graph and data let pack = {}; // packed graph and data
let seed, let seed;
mapId, let mapId;
mapHistory = [], let mapHistory = [];
elSelected, let elSelected;
modules = {}, let modules = {};
notes = []; let notes = [];
let rulers = new Rulers(); let rulers = new Rulers();
let customization = 0; // 0 - no; 1 = heightmap draw; 2 - states draw; 3 - add state/burg; 4 - cultures draw let customization = 0;
let biomesData = applyDefaultBiomesSystem(); let biomesData = applyDefaultBiomesSystem();
let nameBases = Names.getNameBases(); // cultures-related data let nameBases = Names.getNameBases(); // cultures-related data
@ -156,20 +156,24 @@ let urbanization = +document.getElementById("urbanizationInput").value;
let urbanDensity = +document.getElementById("urbanDensityInput").value; let urbanDensity = +document.getElementById("urbanDensityInput").value;
applyStoredOptions(); applyStoredOptions();
let graphWidth = +mapWidthInput.value,
graphHeight = +mapHeightInput.value; // voronoi graph extention, cannot be changed arter generation // voronoi graph extention, cannot be changed arter generation
let svgWidth = graphWidth, let graphWidth = +mapWidthInput.value;
svgHeight = graphHeight; // svg canvas resolution, can be changed let graphHeight = +mapHeightInput.value;
// svg canvas resolution, can be changed
let svgWidth = graphWidth;
let svgHeight = graphHeight;
landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
void (function removeLoading() { // remove loading screen
d3.select("#loading").transition().duration(4000).style("opacity", 0).remove(); d3.select("#loading").transition().duration(4000).style("opacity", 0).remove();
d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove(); d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove();
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1); d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
d3.select("#tooltip").transition().duration(4000).style("opacity", 1); d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
})();
// decide which map should be loaded or generated on page load // decide which map should be loaded or generated on page load
void (function checkLoadParameters() { void (function checkLoadParameters() {
@ -405,6 +409,7 @@ function showWelcomeMessage() {
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>. alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated. This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
<ul>Main changes: <ul>Main changes:
<li>Ability to limit military units by biome, state, culture and religion</li>
<li>New marker types</li> <li>New marker types</li>
<li>New markers editor</li> <li>New markers editor</li>
<li>Markers overview screen</li> <li>Markers overview screen</li>
@ -416,7 +421,7 @@ function showWelcomeMessage() {
</ul> </ul>
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p> <p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
<span>Thanks for all supporters on ${patreon}!</i></span>`; <span>Thanks for all supporters on <a href="https://www.patreon.com/azgaar" onclick="track('click', 'patreon from update')" target="_blank">Patreon</a>!</i></span>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
@ -951,7 +956,7 @@ function calculateTemperatures() {
const lat = Math.abs(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT); // [0; 90] const lat = Math.abs(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT); // [0; 90]
const initTemp = tEq - int(lat / 90) * tDelta; const initTemp = tEq - int(lat / 90) * tDelta;
for (let i = r; i < r + grid.cellsX; i++) { for (let i = r; i < r + grid.cellsX; i++) {
cells.temp[i] = Math.max(Math.min(initTemp - convertToFriendly(cells.h[i]), 127), -128); cells.temp[i] = minmax(initTemp - convertToFriendly(cells.h[i]), -128, 127);
} }
}); });
@ -1066,7 +1071,7 @@ function generatePrecipitation() {
const precipitation = isPassable ? getPrecipitation(humidity, current, next) : humidity; const precipitation = isPassable ? getPrecipitation(humidity, current, next) : humidity;
cells.prec[current] += precipitation; cells.prec[current] += precipitation;
const evaporation = precipitation > 1.5 ? 1 : 0; // some humidity evaporates back to the atmosphere const evaporation = precipitation > 1.5 ? 1 : 0; // some humidity evaporates back to the atmosphere
humidity = isPassable ? Math.min(Math.max(humidity - precipitation + evaporation, 0), maxPrec) : 0; humidity = isPassable ? minmax(humidity - precipitation + evaporation, 0, maxPrec) : 0;
} }
} }
} }
@ -1075,7 +1080,7 @@ function generatePrecipitation() {
const normalLoss = Math.max(humidity / (10 * modifier), 1); // precipitation in normal conditions const normalLoss = Math.max(humidity / (10 * modifier), 1); // precipitation in normal conditions
const diff = Math.max(cells.h[i + n] - cells.h[i], 0); // difference in height const diff = Math.max(cells.h[i + n] - cells.h[i], 0); // difference in height
const mod = (cells.h[i + n] / 70) ** 2; // 50 stands for hills, 70 for mountains const mod = (cells.h[i + n] / 70) ** 2; // 50 stands for hills, 70 for mountains
return Math.min(Math.max(normalLoss + diff * mod, 1), humidity); return minmax(normalLoss + diff * mod, 1, humidity);
} }
void (function drawWindDirection() { void (function drawWindDirection() {
@ -1854,7 +1859,6 @@ function showStatistics() {
mapId = Date.now(); // unique map id is it's creation date number mapId = Date.now(); // unique map id is it's creation date number
mapHistory.push({seed, width: graphWidth, height: graphHeight, template, created: mapId}); mapHistory.push({seed, width: graphWidth, height: graphHeight, template, created: mapId});
INFO && console.log(stats); INFO && console.log(stats);
track("generate", `Template: ${template} ${templateRandom}. Points: ${pointsInput.dataset.cells}`);
} }
const regenerateMap = debounce(function (source) { const regenerateMap = debounce(function (source) {

View file

@ -416,7 +416,7 @@ window.BurgsAndStates = (function () {
function getRiverCost(r, i, type) { function getRiverCost(r, i, type) {
if (type === "River") return r ? 0 : 100; // penalty for river cultures if (type === "River") return r ? 0 : 100; // penalty for river cultures
if (!r) return 0; // no penalty for others if there is no river if (!r) return 0; // no penalty for others if there is no river
return Math.min(Math.max(cells.fl[i] / 10, 20), 100); // river penalty from 20 to 100 based on flux return minmax(cells.fl[i] / 10, 20, 100); // river penalty from 20 to 100 based on flux
} }
function getTypeCost(t, type) { function getTypeCost(t, type) {
@ -606,15 +606,15 @@ window.BurgsAndStates = (function () {
if (pathLength < s.name.length) { if (pathLength < s.name.length) {
// only short name will fit // only short name will fit
lines = splitInTwo(s.name); lines = splitInTwo(s.name);
ratio = Math.max(Math.min(rn((pathLength / lines[0].length) * 60), 150), 50); ratio = minmax(rn((pathLength / lines[0].length) * 60), 50, 150);
} else if (pathLength > s.fullName.length * 2.5) { } else if (pathLength > s.fullName.length * 2.5) {
// full name will fit in one line // full name will fit in one line
lines = [s.fullName]; lines = [s.fullName];
ratio = Math.max(Math.min(rn((pathLength / lines[0].length) * 70), 170), 70); ratio = minmax(rn((pathLength / lines[0].length) * 70), 70, 170);
} else { } else {
// try miltilined label // try miltilined label
lines = splitInTwo(s.fullName); lines = splitInTwo(s.fullName);
ratio = Math.max(Math.min(rn((pathLength / lines[0].length) * 60), 150), 70); ratio = minmax(rn((pathLength / lines[0].length) * 60), 70, 150);
} }
// prolongate path if it's too short // prolongate path if it's too short
@ -665,7 +665,7 @@ window.BurgsAndStates = (function () {
example.text(name); example.text(name);
const left = example.node().getBBox().width / -2; // x offset const left = example.node().getBBox().width / -2; // x offset
el.innerHTML = `<tspan x="${left}px">${name}</tspan>`; el.innerHTML = `<tspan x="${left}px">${name}</tspan>`;
ratio = Math.max(Math.min(rn((pathLength / name.length) * 60), 130), 40); ratio = minmax(rn((pathLength / name.length) * 60), 40, 130);
el.setAttribute("font-size", ratio + "%"); el.setAttribute("font-size", ratio + "%");
}); });
@ -738,21 +738,24 @@ window.BurgsAndStates = (function () {
TIME && console.timeEnd("assignColors"); TIME && console.timeEnd("assignColors");
}; };
// generate historical conflicts of each state
const generateCampaigns = function () {
const wars = {War: 6, Conflict: 2, Campaign: 4, Invasion: 2, Rebellion: 2, Conquest: 2, Intervention: 1, Expedition: 1, Crusade: 1}; const wars = {War: 6, Conflict: 2, Campaign: 4, Invasion: 2, Rebellion: 2, Conquest: 2, Intervention: 1, Expedition: 1, Crusade: 1};
const generateCampaign = state => {
pack.states.forEach(s => { const neighbors = state.neighbors.length ? state.neighbors : [0];
if (!s.i || s.removed) return; return neighbors
const n = s.neighbors.length ? s.neighbors : [0];
s.campaigns = n
.map(i => { .map(i => {
const name = i && P(0.8) ? pack.states[i].name : Names.getCultureShort(s.culture); const name = i && P(0.8) ? pack.states[i].name : Names.getCultureShort(state.culture);
const start = gauss(options.year - 100, 150, 1, options.year - 6); const start = gauss(options.year - 100, 150, 1, options.year - 6);
const end = start + gauss(4, 5, 1, options.year - start - 1); const end = start + gauss(4, 5, 1, options.year - start - 1);
return {name: getAdjective(name) + " " + rw(wars), start, end}; return {name: getAdjective(name) + " " + rw(wars), start, end};
}) })
.sort((a, b) => a.start - b.start); .sort((a, b) => a.start - b.start);
};
// generate historical conflicts of each state
const generateCampaigns = function () {
pack.states.forEach(s => {
if (!s.i || s.removed) return;
s.campaigns = generateCampaign(s);
}); });
}; };
@ -947,7 +950,17 @@ window.BurgsAndStates = (function () {
}); });
const monarchy = ["Duchy", "Grand Duchy", "Principality", "Kingdom", "Empire"]; // per expansionism tier const monarchy = ["Duchy", "Grand Duchy", "Principality", "Kingdom", "Empire"]; // per expansionism tier
const republic = {Republic: 75, Federation: 4, Oligarchy: 2, "Most Serene Republic": 2, Tetrarchy: 1, Triumvirate: 1, Diarchy: 1, "Trade Company": 4, Junta: 1}; // weighted random const republic = {
Republic: 75,
Federation: 4,
Oligarchy: 2,
"Most Serene Republic": 2,
Tetrarchy: 1,
Triumvirate: 1,
Diarchy: 1,
"Trade Company": 4,
Junta: 1
}; // weighted random
const union = {Union: 3, League: 4, Confederation: 1, "United Kingdom": 1, "United Republic": 1, "United Provinces": 2, Commonwealth: 1, Heptarchy: 1}; // weighted random const union = {Union: 3, League: 4, Confederation: 1, "United Kingdom": 1, "United Republic": 1, "United Provinces": 2, Commonwealth: 1, Heptarchy: 1}; // weighted random
const theocracy = {Theocracy: 20, Brotherhood: 1, Thearchy: 2, See: 1, "Holy State": 1}; const theocracy = {Theocracy: 20, Brotherhood: 1, Thearchy: 2, See: 1, "Holy State": 1};
const anarchy = {"Free Territory": 2, Council: 3, Commune: 1, Community: 1}; const anarchy = {"Free Territory": 2, Council: 3, Commune: 1, Community: 1};
@ -957,7 +970,8 @@ window.BurgsAndStates = (function () {
const tier = expTiers[s.i]; const tier = expTiers[s.i];
const religion = pack.cells.religion[s.center]; const religion = pack.cells.religion[s.center];
const isTheocracy = (religion && pack.religions[religion].expansion === "state") || (P(0.1) && ["Organized", "Cult"].includes(pack.religions[religion].type)); const isTheocracy =
(religion && pack.religions[religion].expansion === "state") || (P(0.1) && ["Organized", "Cult"].includes(pack.religions[religion].type));
const isAnarchy = P(0.01 - tier / 500); const isAnarchy = P(0.01 - tier / 500);
if (isTheocracy) s.form = "Theocracy"; if (isTheocracy) s.form = "Theocracy";
@ -1025,7 +1039,25 @@ window.BurgsAndStates = (function () {
}; };
// state forms requiring Adjective + Name, all other forms use scheme Form + Of + Name // state forms requiring Adjective + Name, all other forms use scheme Form + Of + Name
const adjForms = ["Empire", "Sultanate", "Khaganate", "Shogunate", "Caliphate", "Despotate", "Theocracy", "Oligarchy", "Union", "Confederation", "Trade Company", "League", "Tetrarchy", "Triumvirate", "Diarchy", "Horde", "Marches"]; const adjForms = [
"Empire",
"Sultanate",
"Khaganate",
"Shogunate",
"Caliphate",
"Despotate",
"Theocracy",
"Oligarchy",
"Union",
"Confederation",
"Trade Company",
"League",
"Tetrarchy",
"Triumvirate",
"Diarchy",
"Horde",
"Marches"
];
const getFullName = function (s) { const getFullName = function (s) {
if (!s.formName) return s.name; if (!s.formName) return s.name;
@ -1223,5 +1255,23 @@ window.BurgsAndStates = (function () {
TIME && console.timeEnd("generateProvinces"); TIME && console.timeEnd("generateProvinces");
}; };
return {generate, expandStates, normalizeStates, assignColors, drawBurgs, specifyBurgs, defineBurgFeatures, getType, drawStateLabels, collectStatistics, generateCampaigns, generateDiplomacy, defineStateForms, getFullName, generateProvinces, updateCultures}; return {
generate,
expandStates,
normalizeStates,
assignColors,
drawBurgs,
specifyBurgs,
defineBurgFeatures,
getType,
drawStateLabels,
collectStatistics,
generateCampaign,
generateCampaigns,
generateDiplomacy,
defineStateForms,
getFullName,
generateProvinces,
updateCultures
};
})(); })();

View file

@ -117,7 +117,8 @@ window.Cultures = (function () {
if (cells.h[i] > 50) return "Highland"; // no penalty for hills and moutains, high for other elevations if (cells.h[i] > 50) return "Highland"; // no penalty for hills and moutains, high for other elevations
const f = pack.features[cells.f[cells.haven[i]]]; // opposite feature const f = pack.features[cells.f[cells.haven[i]]]; // opposite feature
if (f.type === "lake" && f.cells > 5) return "Lake"; // low water cross penalty and high for growth not along coastline if (f.type === "lake" && f.cells > 5) return "Lake"; // low water cross penalty and high for growth not along coastline
if ((cells.harbor[i] && f.type !== "lake" && P(0.1)) || (cells.harbor[i] === 1 && P(0.6)) || (pack.features[cells.f[i]].group === "isle" && P(0.4))) return "Naval"; // low water cross penalty and high for non-along-coastline growth if ((cells.harbor[i] && f.type !== "lake" && P(0.1)) || (cells.harbor[i] === 1 && P(0.6)) || (pack.features[cells.f[i]].group === "isle" && P(0.4)))
return "Naval"; // low water cross penalty and high for non-along-coastline growth
if (cells.r[i] && cells.fl[i] > 100) return "River"; // no River cross penalty, penalty for non-River growth if (cells.r[i] && cells.fl[i] > 100) return "River"; // no River cross penalty, penalty for non-River growth
if (cells.t[i] > 2 && [3, 7, 8, 9, 10, 12].includes(cells.biome[i])) return "Hunting"; // high penalty in non-native biomes if (cells.t[i] > 2 && [3, 7, 8, 9, 10, 12].includes(cells.biome[i])) return "Hunting"; // high penalty in non-native biomes
return "Generic"; return "Generic";
@ -435,7 +436,7 @@ window.Cultures = (function () {
function getRiverCost(r, i, type) { function getRiverCost(r, i, type) {
if (type === "River") return r ? 0 : 100; // penalty for river cultures if (type === "River") return r ? 0 : 100; // penalty for river cultures
if (!r) return 0; // no penalty for others if there is no river if (!r) return 0; // no penalty for others if there is no river
return Math.min(Math.max(cells.fl[i] / 10, 20), 100); // river penalty from 20 to 100 based on flux return minmax(cells.fl[i] / 10, 20, 100); // river penalty from 20 to 100 based on flux
} }
function getTypeCost(t, type) { function getTypeCost(t, type) {

View file

@ -225,8 +225,8 @@ function parseLoadedData(data) {
if (settings[11]) barPosY.value = settings[11]; if (settings[11]) barPosY.value = settings[11];
if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12]; if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12];
if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13]; if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13];
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(settings[14], 100), 1); if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100);
if (settings[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(settings[15], 100), 0); if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100);
if (settings[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = settings[16]; if (settings[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = settings[16];
if (settings[17]) temperaturePoleInput.value = temperaturePoleOutput.value = settings[17]; if (settings[17]) temperaturePoleInput.value = temperaturePoleOutput.value = settings[17];
if (settings[18]) precInput.value = precOutput.value = settings[18]; if (settings[18]) precInput.value = precOutput.value = settings[18];
@ -234,7 +234,7 @@ function parseLoadedData(data) {
if (settings[20]) mapName.value = settings[20]; if (settings[20]) mapName.value = settings[20];
if (settings[21]) hideLabels.checked = +settings[21]; if (settings[21]) hideLabels.checked = +settings[21];
if (settings[22]) stylePreset.value = settings[22]; if (settings[22]) stylePreset.value = settings[22];
if (settings[23]) rescaleLabels.checked = settings[23]; if (settings[23]) rescaleLabels.checked = +settings[23];
})(); })();
void (function parseConfiguration() { void (function parseConfiguration() {
@ -875,16 +875,23 @@ function parseLoadedData(data) {
const defs = document.getElementById("defs-markers"); const defs = document.getElementById("defs-markers");
const markersGroup = document.getElementById("markers"); const markersGroup = document.getElementById("markers");
const markerElements = markersGroup.querySelectorAll("use"); const markerElements = markersGroup.querySelectorAll("use");
const rescale = +markersGroup.getAttribute("rescale");
pack.markers = Array.from(markerElements).map((el, i) => { pack.markers = Array.from(markerElements).map((el, i) => {
const id = el.getAttribute("id"); const id = el.getAttribute("id");
const note = notes.find(note => note.id === id); const note = notes.find(note => note.id === id);
if (note) note.id = `marker${i}`; if (note) note.id = `marker${i}`;
const x = rn(+el.dataset.x, 1); let x = +el.dataset.x;
const y = rn(+el.dataset.y, 1); let y = +el.dataset.y;
const transform = el.getAttribute("transform");
if (transform) {
const [dx, dy] = parseTransform(transform);
if (dx) x += +dx;
if (dy) y += +dy;
}
const cell = findCell(x, y); const cell = findCell(x, y);
const size = rn(el.dataset.size * 30, 1); const size = rn(rescale ? el.dataset.size * 30 : el.getAttribute("width"), 1);
const href = el.href.baseVal; const href = el.href.baseVal;
const type = href.replace("#marker_", ""); const type = href.replace("#marker_", "");
@ -906,6 +913,7 @@ function parseLoadedData(data) {
if (!isNaN(dy) && dy !== 50) marker.dy = dy; if (!isNaN(dy) && dy !== 50) marker.dy = dy;
if (fill && fill !== "#ffffff") marker.fill = fill; if (fill && fill !== "#ffffff") marker.fill = fill;
if (stroke && stroke !== "#000000") marker.stroke = stroke; if (stroke && stroke !== "#000000") marker.stroke = stroke;
if (circle.getAttribute("opacity") === "0") marker.pin = "no";
return marker; return marker;
}); });

View file

@ -46,9 +46,11 @@ window.Markers = (function () {
}; };
const regenerate = () => { const regenerate = () => {
pack.markers = pack.markers.filter(({i, lock}) => { pack.markers = pack.markers.filter(({i, lock, cell}) => {
if (lock) return true; if (lock) {
occupied[cell] = true;
return true;
}
const id = `marker${i}`; const id = `marker${i}`;
document.getElementById(id)?.remove(); document.getElementById(id)?.remove();
const index = notes.findIndex(note => note.id === id); const index = notes.findIndex(note => note.id === id);
@ -110,12 +112,11 @@ window.Markers = (function () {
const {cells} = pack; const {cells} = pack;
let mountains = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 70).sort((a, b) => cells.h[b] - cells.h[a])); let mountains = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 70).sort((a, b) => cells.h[b] - cells.h[a]));
let quantity = getQuantity(mountains, 10, 300, multiplier); let quantity = getQuantity(mountains, 10, 500, multiplier);
if (!quantity) return; if (!quantity) return;
const highestMountains = mountains.slice(0, 20);
while (quantity) { while (quantity) {
const [cell] = extractAnyElement(highestMountains); const [cell] = extractAnyElement(mountains);
const id = addMarker({cell, icon, type, dx: 52, px: 13}); const id = addMarker({cell, icon, type, dx: 52, px: 13});
const proper = Names.getCulture(cells.culture[cell]); const proper = Names.getCulture(cells.culture[cell]);
const name = P(0.3) ? "Mount " + proper : Math.random() > 0.3 ? proper + " Volcano" : proper; const name = P(0.3) ? "Mount " + proper : Math.random() > 0.3 ? proper + " Volcano" : proper;
@ -128,12 +129,11 @@ window.Markers = (function () {
const {cells} = pack; const {cells} = pack;
let springs = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] > 50).sort((a, b) => cells.h[b] - cells.h[a])); let springs = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] > 50).sort((a, b) => cells.h[b] - cells.h[a]));
let quantity = getQuantity(springs, 30, 800, multiplier); let quantity = getQuantity(springs, 30, 1200, multiplier);
if (!quantity) return; if (!quantity) return;
const highestSprings = springs.slice(0, 40);
while (quantity) { while (quantity) {
const [cell] = extractAnyElement(highestSprings); const [cell] = extractAnyElement(springs);
const id = addMarker({cell, icon, type, dy: 52}); const id = addMarker({cell, icon, type, dy: 52});
const proper = Names.getCulture(cells.culture[cell]); const proper = Names.getCulture(cells.culture[cell]);
const temp = convertTemperature(gauss(35, 15, 20, 100)); const temp = convertTemperature(gauss(35, 15, 20, 100));
@ -181,7 +181,16 @@ window.Markers = (function () {
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]); const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
const riverName = river ? `${river.name} ${river.type}` : "river"; const riverName = river ? `${river.name} ${river.type}` : "river";
const name = river && P(0.2) ? river.name : burg.name; const name = river && P(0.2) ? river.name : burg.name;
notes.push({id, name: `${name} Bridge`, legend: `A stone bridge over the ${riverName} near ${burg.name}`}); const weightedAdjectives = {
stone: 10,
wooden: 1,
lengthy: 2,
formidable: 2,
rickety: 1,
beaten: 1,
weathered: 1
};
notes.push({id, name: `${name} Bridge`, legend: `A ${rw(weightedAdjectives)} bridge spans over the ${riverName} near ${burg.name}`});
quantity--; quantity--;
} }
} }
@ -448,7 +457,7 @@ window.Markers = (function () {
const [cell] = extractAnyElement(lighthouses); const [cell] = extractAnyElement(lighthouses);
const id = addMarker({cell, icon, type, px: 14}); const id = addMarker({cell, icon, type, px: 14});
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to keep the navigation safe`}); notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to serve as a beacon for ships in the open sea`});
quantity--; quantity--;
} }
} }
@ -460,11 +469,19 @@ window.Markers = (function () {
const quantity = getQuantity(waterfalls, 1, 5, multiplier); const quantity = getQuantity(waterfalls, 1, 5, multiplier);
if (!quantity) return; if (!quantity) return;
const descriptions = [
"A gorgeous waterfall flows here",
"The rapids of an exceptionally beautiful waterfall",
"An impressive waterfall has cut through the land",
"The cascades of a stunning waterfall",
"A river drops down from a great height forming a wonderous waterfall",
"A breathtaking waterfall cuts through the landscape"
];
for (let i = 0; i < waterfalls.length && i < quantity; i++) { for (let i = 0; i < waterfalls.length && i < quantity; i++) {
const cell = waterfalls[i]; const cell = waterfalls[i];
const id = addMarker({cell, icon, type, dy: 54, px: 16}); const id = addMarker({cell, icon, type, dy: 54, px: 16});
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend: `An extremely beautiful waterfall`}); notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend: `${ra(descriptions)}`});
} }
} }
@ -478,7 +495,9 @@ window.Markers = (function () {
while (quantity && battlefields.length) { while (quantity && battlefields.length) {
const [cell] = extractAnyElement(battlefields); const [cell] = extractAnyElement(battlefields);
const id = addMarker({cell, icon, type, dy: 52}); const id = addMarker({cell, icon, type, dy: 52});
const campaign = ra(states[cells.state[cell]].campaigns); const state = states[cells.state[cell]];
if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
const campaign = ra(state.campaigns);
const date = generateDate(campaign.start, campaign.end); const date = generateDate(campaign.start, campaign.end);
const name = Names.getCulture(cells.culture[cell]) + " Battlefield"; const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`; const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
@ -519,7 +538,7 @@ window.Markers = (function () {
const id = addMarker({cell, icon, type, dy: 48}); const id = addMarker({cell, icon, type, dy: 48});
const name = `${lake.name} Monster`; const name = `${lake.name} Monster`;
const length = gauss(10, 5, 5, 100); const length = gauss(10, 5, 5, 100);
const legend = `Rumors said a relic monster of ${length} ${heightUnit.value} long inhabits ${lake.name} Lake. Truth or lie, but folks are afraid to fish in the lake`; const legend = `Rumors say a relic monster of ${length} ${heightUnit.value} long inhabits ${lake.name} Lake. Truth or lie, folks are afraid to fish in the lake`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--; quantity--;
} }
@ -550,15 +569,54 @@ window.Markers = (function () {
let quantity = getQuantity(hills, 30, 600, multiplier); let quantity = getQuantity(hills, 30, 600, multiplier);
if (!quantity) return; if (!quantity) return;
const subjects = ["Locals", "Old folks", "Old books", "Tipplers"]; const adjectives = [
const species = ["Ogre", "Troll", "Cyclops", "Giant", "Monster", "Beast", "Dragon", "Undead", "Ghoul", "Vampire"]; "great",
"big",
"huge",
"prime",
"golden",
"proud",
"lucky",
"fat",
"giant",
"hungry",
"magical",
"superior",
"terrifying",
"horrifying",
"feared"
];
const subjects = ["Locals", "Elders", "Inscriptions", "Tipplers", "Legends", "Whispers", "Rumors", "Journeying folk", "Tales"];
const species = [
"Ogre",
"Troll",
"Cyclops",
"Giant",
"Monster",
"Beast",
"Dragon",
"Undead",
"Ghoul",
"Vampire",
"Hag",
"Banshee",
"Bearded Devil",
"Roc",
"Hydra",
"Warg"
];
const modusOperandi = [ const modusOperandi = [
"steals their cattle", "steals cattle at night",
"doesn't mind eating children", "prefers eating children",
"doesn't mind of human flesh", "doesn't mind of human flesh",
"keeps the region at bay", "keeps the region at bay",
"eats their kids", "eats kids whole",
"abducts young women" "abducts young women",
"terrorizes the region",
"harasses travelers in the area",
"snatches people from homes",
"attacks anyone who dares to approach its lair",
"attacks unsuspecting victims"
]; ];
while (quantity) { while (quantity) {
@ -567,7 +625,7 @@ window.Markers = (function () {
const monster = ra(species); const monster = ra(species);
const toponym = Names.getCulture(cells.culture[cell]); const toponym = Names.getCulture(cells.culture[cell]);
const name = `${toponym} ${monster}`; const name = `${toponym} ${monster}`;
const legend = `${ra(subjects)} tell tales of an old ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`; const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--; quantity--;
} }
@ -737,7 +795,7 @@ window.Markers = (function () {
let quantity = getQuantity(statues, 80, 1200, multiplier); let quantity = getQuantity(statues, 80, 1200, multiplier);
if (!quantity) return; if (!quantity) return;
const variants = ["Statue", "Obelisk", "Monument", "Column", "Monolith", "Pillar", "Megalith", "Stele", "Runestone"]; const variants = ["Statue", "Obelisk", "Monument", "Column", "Monolith", "Pillar", "Megalith", "Stele", "Runestone", "Sculpture", "Effigy", "Idol"];
const scripts = { const scripts = {
cypriot: "𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ", cypriot: "𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ",
geez: "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጰጸፀፈፐ ", geez: "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጰጸፀፈፐ ",
@ -771,7 +829,21 @@ window.Markers = (function () {
let quantity = getQuantity(ruins, 80, 1200, multiplier); let quantity = getQuantity(ruins, 80, 1200, multiplier);
if (!quantity) return; if (!quantity) return;
const types = ["City", "Town", "Pyramid", "Fort"]; const types = [
"City",
"Town",
"Settlement",
"Pyramid",
"Fort",
"Stronghold",
"Temple",
"Sacred site",
"Mausoleum",
"Outpost",
"Fortification",
"Fortress",
"Castle"
];
while (quantity) { while (quantity) {
const [cell] = extractAnyElement(ruins); const [cell] = extractAnyElement(ruins);
@ -779,7 +851,7 @@ window.Markers = (function () {
const ruinType = ra(types); const ruinType = ra(types);
const name = `Ruined ${ruinType}`; const name = `Ruined ${ruinType}`;
const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. A good place for a treasures hunt`; const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--; quantity--;
} }

View file

@ -3,9 +3,8 @@
window.Military = (function () { window.Military = (function () {
const generate = function () { const generate = function () {
TIME && console.time("generateMilitaryForces"); TIME && console.time("generateMilitaryForces");
const cells = pack.cells, const {cells, states} = pack;
p = cells.p, const {p} = cells;
states = pack.states;
const valid = states.filter(s => s.i && !s.removed); // valid states const valid = states.filter(s => s.i && !s.removed); // valid states
if (!options.military) options.military = getDefaultOptions(); if (!options.military) options.military = getDefaultOptions();
@ -19,7 +18,6 @@ window.Military = (function () {
mounted: {Nomadic: 2.3, Highland: 0.6, Lake: 0.7, Naval: 0.3, Hunting: 0.7, River: 0.8}, mounted: {Nomadic: 2.3, Highland: 0.6, Lake: 0.7, Naval: 0.3, Hunting: 0.7, River: 0.8},
machinery: {Nomadic: 0.8, Highland: 1.4, Lake: 1.1, Naval: 1.4, Hunting: 0.4, River: 1.1}, machinery: {Nomadic: 0.8, Highland: 1.4, Lake: 1.1, Naval: 1.4, Hunting: 0.4, River: 1.1},
naval: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.8, Hunting: 0.7, River: 1.2}, naval: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.8, Hunting: 0.7, River: 1.2},
// non-default generic:
armored: {Nomadic: 1, Highland: 0.5, Lake: 1, Naval: 1, Hunting: 0.7, River: 1.1}, armored: {Nomadic: 1, Highland: 0.5, Lake: 1, Naval: 1, Hunting: 0.7, River: 1.1},
aviation: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.2, Hunting: 0.6, River: 1.2}, aviation: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.2, Hunting: 0.6, River: 1.2},
magical: {Nomadic: 1, Highland: 2, Lake: 1, Naval: 1, Hunting: 1, River: 1} magical: {Nomadic: 1, Highland: 2, Lake: 1, Naval: 1, Hunting: 1, River: 1}
@ -38,27 +36,24 @@ window.Military = (function () {
}; };
valid.forEach(s => { valid.forEach(s => {
const temp = (s.temp = {}), s.temp = {};
d = s.diplomacy; const d = s.diplomacy;
const expansionRate = Math.min(Math.max(s.expansionism / expn / (s.area / area), 0.25), 4); // how much state expansionism is realized
const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized
const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? 0.8 : d.some(d => d === "Suspicion") ? 0.5 : 0.1; // peacefulness const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? 0.8 : d.some(d => d === "Suspicion") ? 0.5 : 0.1; // peacefulness
const neighborsRate = Math.min( const neighborsRateRaw = s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5);
Math.max( const neighborsRate = minmax(neighborsRateRaw, 0.3, 3); // neighbors rate
s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5), s.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier)
0.3 s.temp.platoons = [];
),
3
); // neighbors rate
s.alert = Math.min(Math.max(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1), 5); // war alert rate (army modifier)
temp.platoons = [];
// apply overall state modifiers for unit types based on state features // apply overall state modifiers for unit types based on state features
for (const unit of options.military) { for (const unit of options.military) {
if (!stateModifier[unit.type]) continue; if (!stateModifier[unit.type]) continue;
let modifier = stateModifier[unit.type][s.type] || 1; let modifier = stateModifier[unit.type][s.type] || 1;
if (unit.type === "mounted" && s.formName.includes("Horde")) modifier *= 2; if (unit.type === "mounted" && s.formName.includes("Horde")) modifier *= 2;
else if (unit.type === "naval" && s.form === "Republic") modifier *= 1.2; else if (unit.type === "naval" && s.form === "Republic") modifier *= 1.2;
temp[unit.name] = modifier * s.alert; s.temp[unit.name] = modifier * s.alert;
} }
}); });
@ -69,66 +64,91 @@ window.Military = (function () {
return "generic"; return "generic";
}; };
function passUnitLimits(unit, biome, state, culture, religion) {
if (unit.biomes && !unit.biomes.includes(biome)) return false;
if (unit.states && !unit.states.includes(state)) return false;
if (unit.cultures && !unit.cultures.includes(culture)) return false;
if (unit.religions && !unit.religions.includes(religion)) return false;
return true;
}
for (const i of cells.i) { for (const i of cells.i) {
if (!cells.pop[i]) continue; if (!cells.pop[i]) continue;
const s = states[cells.state[i]]; // cell state
if (!s.i || s.removed) continue;
let m = cells.pop[i] / 100; // basic rural army in percentages const biome = cells.biome[i];
if (cells.culture[i] !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture const state = cells.state[i];
if (cells.religion[i] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion const culture = cells.culture[i];
if (cells.f[i] !== cells.f[s.center]) m = s.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass const religion = cells.religion[i];
const stateObj = states[state];
if (!state || stateObj.removed) continue;
let modifier = cells.pop[i] / 100; // basic rural army in percentages
if (culture !== stateObj.culture) modifier = stateObj.form === "Union" ? modifier / 1.2 : modifier / 2; // non-dominant culture
if (religion !== cells.religion[stateObj.center]) modifier = stateObj.form === "Theocracy" ? modifier / 2.2 : modifier / 1.4; // non-dominant religion
if (cells.f[i] !== cells.f[stateObj.center]) modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass
const type = getType(i); const type = getType(i);
for (const u of options.military) { for (const unit of options.military) {
const perc = +u.rural; const perc = +unit.rural;
if (isNaN(perc) || perc <= 0 || !s.temp[u.name]) continue; if (isNaN(perc) || perc <= 0 || !stateObj.temp[unit.name]) continue;
if (!passUnitLimits(unit, biome, state, culture, religion)) continue;
const mod = type === "generic" ? 1 : cellTypeModifier[type][u.type]; // cell specific modifier const cellTypeMod = type === "generic" ? 1 : cellTypeModifier[type][unit.type]; // cell specific modifier
const army = m * perc * mod; // rural cell army const army = modifier * perc * cellTypeMod; // rural cell army
const t = rn(army * s.temp[u.name] * populationRate); // total troops const total = rn(army * stateObj.temp[unit.name] * populationRate); // total troops
if (!t) continue; if (!total) continue;
let x = p[i][0],
y = p[i][1], let [x, y] = p[i];
n = 0; let n = 0;
if (u.type === "naval") {
if (unit.type === "naval") {
let haven = cells.haven[i]; let haven = cells.haven[i];
(x = p[haven][0]), (y = p[haven][1]); [x, y] = p[haven];
n = 1; n = 1;
} // place naval to sea } // place naval to sea
s.temp.platoons.push({cell: i, a: t, t, x, y, u: u.name, n, s: u.separate, type: u.type});
stateObj.temp.platoons.push({cell: i, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type});
} }
} }
for (const b of pack.burgs) { for (const b of pack.burgs) {
if (!b.i || b.removed || !b.state || !b.population) continue; if (!b.i || b.removed || !b.state || !b.population) continue;
const s = states[b.state]; // burg state
const biome = cells.biome[b.cell];
const state = b.state;
const culture = b.culture;
const religion = cells.religion[b.cell];
const stateObj = states[state];
let m = (b.population * urbanization) / 100; // basic urban army in percentages let m = (b.population * urbanization) / 100; // basic urban army in percentages
if (b.capital) m *= 1.2; // capital has household troops if (b.capital) m *= 1.2; // capital has household troops
if (b.culture !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture if (culture !== stateObj.culture) m = stateObj.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture
if (cells.religion[b.cell] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion if (religion !== cells.religion[stateObj.center]) m = stateObj.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion
if (cells.f[b.cell] !== cells.f[s.center]) m = s.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass if (cells.f[b.cell] !== cells.f[stateObj.center]) m = stateObj.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass
const type = getType(b.cell); const type = getType(b.cell);
for (const u of options.military) { for (const unit of options.military) {
if (u.type === "naval" && !b.port) continue; // only ports produce naval units if (unit.type === "naval" && !b.port) continue; // only ports produce naval units
const perc = +u.urban; const perc = +unit.urban;
if (isNaN(perc) || perc <= 0 || !s.temp[u.name]) continue; if (isNaN(perc) || perc <= 0 || !stateObj.temp[unit.name]) continue;
if (!passUnitLimits(unit, biome, state, culture, religion)) continue;
const mod = type === "generic" ? 1 : burgTypeModifier[type][u.type]; // cell specific modifier const mod = type === "generic" ? 1 : burgTypeModifier[type][unit.type]; // cell specific modifier
const army = m * perc * mod; // urban cell army const army = m * perc * mod; // urban cell army
const t = rn(army * s.temp[u.name] * populationRate); // total troops const total = rn(army * stateObj.temp[unit.name] * populationRate); // total troops
if (!t) continue; if (!total) continue;
let x = p[b.cell][0],
y = p[b.cell][1], let [x, y] = p[b.cell];
n = 0; let n = 0;
if (u.type === "naval") {
if (unit.type === "naval") {
let haven = cells.haven[b.cell]; let haven = cells.haven[b.cell];
(x = p[haven][0]), (y = p[haven][1]); [x, y] = p[haven];
n = 1; n = 1;
} // place naval in sea cell } // place naval to sea
s.temp.platoons.push({cell: b.cell, a: t, t, x, y, u: u.name, n, s: u.separate, type: u.type});
stateObj.temp.platoons.push({cell: b.cell, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type});
} }
} }
@ -334,7 +354,13 @@ window.Military = (function () {
const getName = function (r, regiments) { const getName = function (r, regiments) {
const cells = pack.cells; const cells = pack.cells;
const proper = r.n ? null : cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].name : cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : null; const proper = r.n
? null
: cells.province[r.cell] && pack.provinces[cells.province[r.cell]]
? pack.provinces[cells.province[r.cell]].name
: cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]]
? pack.burgs[cells.burg[r.cell]].name
: null;
const number = nth(regiments.filter(reg => reg.n === r.n && reg.i < r.i).length + 1); const number = nth(regiments.filter(reg => reg.n === r.n && reg.i < r.i).length + 1);
const form = r.n ? "Fleet" : "Regiment"; const form = r.n ? "Fleet" : "Regiment";
return `${number}${proper ? ` (${proper}) ` : ` `}${form}`; return `${number}${proper ? ` (${proper}) ` : ` `}${form}`;
@ -351,7 +377,12 @@ window.Military = (function () {
const generateNote = function (r, s) { const generateNote = function (r, s) {
const cells = pack.cells; const cells = pack.cells;
const base = cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].fullName : null; const base =
cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]]
? pack.burgs[cells.burg[r.cell]].name
: cells.province[r.cell] && pack.provinces[cells.province[r.cell]]
? pack.provinces[cells.province[r.cell]].fullName
: null;
const station = base ? `${r.name} is ${r.n ? "based" : "stationed"} in ${base}. ` : ""; const station = base ? `${r.name} is ${r.n ? "based" : "stationed"} in ${base}. ` : "";
const composition = r.a const composition = r.a

View file

@ -52,7 +52,7 @@ window.ReliefIcons = (function () {
function getReliefIcon(i, h) { function getReliefIcon(i, h) {
const temp = grid.cells.temp[pack.cells.g[i]]; const temp = grid.cells.temp[pack.cells.g[i]];
const type = h > 70 && temp < 0 ? "mountSnow" : h > 70 ? "mount" : "hill"; const type = h > 70 && temp < 0 ? "mountSnow" : h > 70 ? "mount" : "hill";
const size = h > 70 ? (h - 45) * mod : Math.min(Math.max((h - 40) * mod, 3), 6); const size = h > 70 ? (h - 45) * mod : minmax((h - 40) * mod, 3, 6);
return [getIcon(type), size]; return [getIcon(type), size];
} }
} }

View file

@ -2,7 +2,22 @@
window.Religions = (function () { window.Religions = (function () {
// name generation approach and relative chance to be selected // name generation approach and relative chance to be selected
const approach = {Number: 1, Being: 3, Adjective: 5, "Color + Animal": 5, "Adjective + Animal": 5, "Adjective + Being": 5, "Adjective + Genitive": 1, "Color + Being": 3, "Color + Genitive": 3, "Being + of + Genitive": 2, "Being + of the + Genitive": 1, "Animal + of + Genitive": 1, "Adjective + Being + of + Genitive": 2, "Adjective + Animal + of + Genitive": 2}; const approach = {
Number: 1,
Being: 3,
Adjective: 5,
"Color + Animal": 5,
"Adjective + Animal": 5,
"Adjective + Being": 5,
"Adjective + Genitive": 1,
"Color + Being": 3,
"Color + Genitive": 3,
"Being + of + Genitive": 2,
"Being + of the + Genitive": 1,
"Animal + of + Genitive": 1,
"Adjective + Being + of + Genitive": 2,
"Adjective + Animal + of + Genitive": 2
};
// turn weighted array into simple array // turn weighted array into simple array
const approaches = []; const approaches = [];
@ -14,11 +29,254 @@ window.Religions = (function () {
const base = { const base = {
number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"], number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"],
being: ["God", "Goddess", "Lord", "Lady", "Deity", "Creator", "Maker", "Overlord", "Ruler", "Chief", "Master", "Spirit", "Ancestor", "Father", "Forebear", "Forefather", "Mother", "Brother", "Sister", "Elder", "Numen", "Ancient", "Virgin", "Giver", "Council", "Guardian", "Reaper"], being: [
animal: ["Dragon", "Wyvern", "Phoenix", "Unicorn", "Sphinx", "Centaur", "Pegasus", "Kraken", "Basilisk", "Chimera", "Cyclope", "Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "Cobra", "Crane", "Crocodile", "Crow", "Deer", "Dog", "Eagle", "Elk", "Fox", "Goat", "Goose", "Hare", "Hawk", "Heron", "Horse", "Hyena", "Ibis", "Jackal", "Jaguar", "Lark", "Leopard", "Lion", "Mantis", "Marten", "Moose", "Mule", "Narwhal", "Owl", "Panther", "Rat", "Raven", "Rook", "Scorpion", "Shark", "Sheep", "Snake", "Spider", "Swan", "Tiger", "Turtle", "Viper", "Vulture", "Walrus", "Wolf", "Wolverine", "Worm", "Camel", "Falcon", "Hound", "Ox", "Serpent"], "God",
adjective: ["New", "Good", "High", "Old", "Great", "Big", "Young", "Major", "Strong", "Happy", "Last", "Main", "Huge", "Far", "Beautiful", "Wild", "Fair", "Prime", "Crazy", "Ancient", "Proud", "Secret", "Lucky", "Sad", "Silent", "Latter", "Severe", "Fat", "Holy", "Pure", "Aggressive", "Honest", "Giant", "Mad", "Pregnant", "Distant", "Lost", "Broken", "Blind", "Friendly", "Unknown", "Sleeping", "Slumbering", "Loud", "Hungry", "Wise", "Worried", "Sacred", "Magical", "Superior", "Patient", "Dead", "Deadly", "Peaceful", "Grateful", "Frozen", "Evil", "Scary", "Burning", "Divine", "Bloody", "Dying", "Waking", "Brutal", "Unhappy", "Calm", "Cruel", "Favorable", "Blond", "Explicit", "Disturbing", "Devastating", "Brave", "Sunny", "Troubled", "Flying", "Sustainable", "Marine", "Fatal", "Inherent", "Selected", "Naval", "Cheerful", "Almighty", "Benevolent", "Eternal", "Immutable", "Infallible"], "Goddess",
genitive: ["Day", "Life", "Death", "Night", "Home", "Fog", "Snow", "Winter", "Summer", "Cold", "Springs", "Gates", "Nature", "Thunder", "Lightning", "War", "Ice", "Frost", "Fire", "Doom", "Fate", "Pain", "Heaven", "Justice", "Light", "Love", "Time", "Victory"], "Lord",
theGenitive: ["World", "Word", "South", "West", "North", "East", "Sun", "Moon", "Peak", "Fall", "Dawn", "Eclipse", "Abyss", "Blood", "Tree", "Earth", "Harvest", "Rainbow", "Sea", "Sky", "Stars", "Storm", "Underworld", "Wild"], "Lady",
"Deity",
"Creator",
"Maker",
"Overlord",
"Ruler",
"Chief",
"Master",
"Spirit",
"Ancestor",
"Father",
"Forebear",
"Forefather",
"Mother",
"Brother",
"Sister",
"Elder",
"Numen",
"Ancient",
"Virgin",
"Giver",
"Council",
"Guardian",
"Reaper"
],
animal: [
"Dragon",
"Wyvern",
"Phoenix",
"Unicorn",
"Sphinx",
"Centaur",
"Pegasus",
"Kraken",
"Basilisk",
"Chimera",
"Cyclope",
"Antelope",
"Ape",
"Badger",
"Bear",
"Beaver",
"Bison",
"Boar",
"Buffalo",
"Cat",
"Cobra",
"Crane",
"Crocodile",
"Crow",
"Deer",
"Dog",
"Eagle",
"Elk",
"Fox",
"Goat",
"Goose",
"Hare",
"Hawk",
"Heron",
"Horse",
"Hyena",
"Ibis",
"Jackal",
"Jaguar",
"Lark",
"Leopard",
"Lion",
"Mantis",
"Marten",
"Moose",
"Mule",
"Narwhal",
"Owl",
"Panther",
"Rat",
"Raven",
"Rook",
"Scorpion",
"Shark",
"Sheep",
"Snake",
"Spider",
"Swan",
"Tiger",
"Turtle",
"Viper",
"Vulture",
"Walrus",
"Wolf",
"Wolverine",
"Worm",
"Camel",
"Falcon",
"Hound",
"Ox",
"Serpent"
],
adjective: [
"New",
"Good",
"High",
"Old",
"Great",
"Big",
"Young",
"Major",
"Strong",
"Happy",
"Last",
"Main",
"Huge",
"Far",
"Beautiful",
"Wild",
"Fair",
"Prime",
"Crazy",
"Ancient",
"Proud",
"Secret",
"Lucky",
"Sad",
"Silent",
"Latter",
"Severe",
"Fat",
"Holy",
"Pure",
"Aggressive",
"Honest",
"Giant",
"Mad",
"Pregnant",
"Distant",
"Lost",
"Broken",
"Blind",
"Friendly",
"Unknown",
"Sleeping",
"Slumbering",
"Loud",
"Hungry",
"Wise",
"Worried",
"Sacred",
"Magical",
"Superior",
"Patient",
"Dead",
"Deadly",
"Peaceful",
"Grateful",
"Frozen",
"Evil",
"Scary",
"Burning",
"Divine",
"Bloody",
"Dying",
"Waking",
"Brutal",
"Unhappy",
"Calm",
"Cruel",
"Favorable",
"Blond",
"Explicit",
"Disturbing",
"Devastating",
"Brave",
"Sunny",
"Troubled",
"Flying",
"Sustainable",
"Marine",
"Fatal",
"Inherent",
"Selected",
"Naval",
"Cheerful",
"Almighty",
"Benevolent",
"Eternal",
"Immutable",
"Infallible"
],
genitive: [
"Day",
"Life",
"Death",
"Night",
"Home",
"Fog",
"Snow",
"Winter",
"Summer",
"Cold",
"Springs",
"Gates",
"Nature",
"Thunder",
"Lightning",
"War",
"Ice",
"Frost",
"Fire",
"Doom",
"Fate",
"Pain",
"Heaven",
"Justice",
"Light",
"Love",
"Time",
"Victory"
],
theGenitive: [
"World",
"Word",
"South",
"West",
"North",
"East",
"Sun",
"Moon",
"Peak",
"Fall",
"Dawn",
"Eclipse",
"Abyss",
"Blood",
"Tree",
"Earth",
"Harvest",
"Rainbow",
"Sea",
"Sky",
"Stars",
"Storm",
"Underworld",
"Wild"
],
color: ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"] color: ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]
}; };
@ -29,7 +287,16 @@ window.Religions = (function () {
Heresy: {Heresy: 1} Heresy: {Heresy: 1}
}; };
const methods = {"Random + type": 3, "Random + ism": 1, "Supreme + ism": 5, "Faith of + Supreme": 5, "Place + ism": 1, "Culture + ism": 2, "Place + ian + type": 6, "Culture + type": 4}; const methods = {
"Random + type": 3,
"Random + ism": 1,
"Supreme + ism": 5,
"Faith of + Supreme": 5,
"Place + ism": 1,
"Culture + ism": 2,
"Place + ian + type": 6,
"Culture + type": 4
};
const types = { const types = {
Shamanism: {Beliefs: 3, Shamanism: 2, Spirits: 1}, Shamanism: {Beliefs: 3, Shamanism: 2, Spirits: 1},
@ -78,7 +345,10 @@ window.Religions = (function () {
} }
const burgs = pack.burgs.filter(b => b.i && !b.removed); 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 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 religionsTree = d3.quadtree();
const spacing = (graphWidth + graphHeight) / 6 / religionsInput.value; // base min distance between towns const spacing = (graphWidth + graphHeight) / 6 / religionsInput.value; // base min distance between towns
const cultsCount = Math.floor((rand(10, 40) / 100) * religionsInput.value); const cultsCount = Math.floor((rand(10, 40) / 100) * religionsInput.value);
@ -160,9 +430,20 @@ window.Religions = (function () {
const name = getCultName("Heresy", center); const name = getCultName("Heresy", center);
const expansionism = gauss(1.2, 0.5, 0, 5); const expansionism = gauss(1.2, 0.5, 0, 5);
const color = getMixedColor(r.color, 0.4, 0.2); // "url(#hatch6)"; 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, origin: r.i}); religions.push({
i: religions.length,
name,
color,
culture,
type: "Heresy",
form: r.form,
deity: r.deity,
expansion: "global",
expansionism,
center,
origin: r.i
});
religionsTree.add([x, y]); religionsTree.add([x, y]);
//debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).attr("fill", "green");
} }
}); });
@ -195,7 +476,24 @@ window.Religions = (function () {
name, name,
religions.map(r => r.code) religions.map(r => r.code)
); );
religions.push({i, name, color, culture, type, form: formName, deity, expansion, expansionism: 0, center, cells: 0, area: 0, rural: 0, urban: 0, origin: r, code}); religions.push({
i,
name,
color,
culture,
type,
form: formName,
deity,
expansion,
expansionism: 0,
center,
cells: 0,
area: 0,
rural: 0,
urban: 0,
origin: r,
code
});
cells.religion[center] = i; cells.religion[center] = i;
}; };
@ -292,20 +590,18 @@ window.Religions = (function () {
}; };
function checkCenters() { function checkCenters() {
const cells = pack.cells, const {cells, religions} = pack;
religions = pack.religions;
const codes = religions.map(r => r.code); const codes = religions.map(r => r.code);
religions religions.forEach(r => {
.filter(r => r.i) if (!r.i) return;
.forEach(r => {
r.code = abbreviate(r.name, codes); r.code = abbreviate(r.name, codes);
// move religion center if it's not within religion area after expansion // move religion center if it's not within religion area after expansion
if (cells.religion[r.center] === r.i) return; // in area if (cells.religion[r.center] === r.i) return; // in area
const religCells = cells.i.filter(i => cells.religion[i] === r.i); const religCells = cells.i.filter(i => cells.religion[i] === r.i);
if (!religCells.length) return; // extinct religion if (!religCells.length) return; // extinct religion
r.center = religCells.sort((a, b) => b.pop - a.pop)[0]; r.center = religCells.sort((a, b) => cells.pop[b] - cells.pop[a])[0];
}); });
} }

View file

@ -1,20 +1,43 @@
"use strict"; "use strict";
window.ThreeD = (function () { window.ThreeD = (function () {
// set default options const options = {
const options = {scale: 50, lightness: 0.7, shadow: 0.5, sun: {x: 100, y: 600, z: 1000}, rotateMesh: 0, rotateGlobe: 0.5, skyColor: "#9ecef5", waterColor: "#466eab", extendedWater: 0, labels3d: 0, resolution: 2}; scale: 50,
lightness: 0.7,
shadow: 0.5,
sun: {x: 100, y: 600, z: 1000},
rotateMesh: 0,
rotateGlobe: 0.5,
skyColor: "#9ecef5",
waterColor: "#466eab",
extendedWater: 0,
labels3d: 0,
resolution: 2
};
// set variables // set variables
let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, raycaster; let Renderer,
scene,
const drawCtx = document.createElement("canvas").getContext("2d"); camera,
const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); controls,
document.body.appendChild(drawSVG); animationFrame,
material,
texture,
geometry,
mesh,
ambientLight,
spotLight,
waterPlane,
waterMaterial,
waterMesh,
raycaster;
let labels = []; let labels = [];
let icons = []; let icons = [];
let lines = []; let lines = [];
const context2d = document.createElement("canvas").getContext("2d");
// initiate 3d scene // initiate 3d scene
const create = async function (canvas, type = "viewMesh") { const create = async function (canvas, type = "viewMesh") {
options.isOn = true; options.isOn = true;
@ -210,16 +233,16 @@ window.ThreeD = (function () {
} }
async function createTextLabel({text, font, size, color, quality}) { async function createTextLabel({text, font, size, color, quality}) {
drawCtx.font = `${size * quality}px ${font}`; context2d.font = `${size * quality}px ${font}`;
drawCtx.canvas.width = drawCtx.measureText(text).width; context2d.canvas.width = context2d.measureText(text).width;
drawCtx.canvas.height = size * quality * 1.25; // 25% margin as text can overflow the font size context2d.canvas.height = size * quality * 1.25; // 25% margin as text can overflow the font size
drawCtx.clearRect(0, 0, drawCtx.canvas.width, drawCtx.canvas.height); context2d.clearRect(0, 0, context2d.canvas.width, context2d.canvas.height);
drawCtx.font = `${size * quality}px ${font}`; context2d.font = `${size * quality}px ${font}`;
drawCtx.fillStyle = color; context2d.fillStyle = color;
drawCtx.fillText(text, 0, size * quality); context2d.fillText(text, 0, size * quality);
return textureToSprite(drawCtx.canvas.toDataURL(), drawCtx.canvas.width / quality, drawCtx.canvas.height / quality); return textureToSprite(context2d.canvas.toDataURL(), context2d.canvas.width / quality, context2d.canvas.height / quality);
} }
function get3dCoords(baseX, baseY) { function get3dCoords(baseX, baseY) {
@ -578,5 +601,21 @@ window.ThreeD = (function () {
}); });
} }
return {create, redraw, update, stop, options, setScale, setLightness, setSun, setRotation, toggleLabels, toggleSky, setResolution, setColors, saveScreenshot, saveOBJ}; return {
create,
redraw,
update,
stop,
options,
setScale,
setLightness,
setSun,
setRotation,
toggleLabels,
toggleSky,
setResolution,
setColors,
saveScreenshot,
saveOBJ
};
})(); })();

View file

@ -330,7 +330,7 @@ class Battle {
} }
getInitialMorale() { getInitialMorale() {
const powerFee = diff => Math.min(Math.max(100 - diff ** 1.5 * 10 + 10, 50), 100); const powerFee = diff => minmax(100 - diff ** 1.5 * 10 + 10, 50, 100);
const distanceFee = dist => Math.min(d3.mean(dist) / 50, 15); const distanceFee = dist => Math.min(d3.mean(dist) / 50, 15);
const powerDiff = this.defenders.power / this.attackers.power; const powerDiff = this.defenders.power / this.attackers.power;
this.attackers.morale = powerFee(powerDiff) - distanceFee(this.attackers.distances); this.attackers.morale = powerFee(powerDiff) - distanceFee(this.attackers.distances);

View file

@ -621,7 +621,7 @@ function editHeightmap() {
const interpolate = d3.interpolateRound(power, 1); const interpolate = d3.interpolateRound(power, 1);
const land = changeOnlyLand.checked; const land = changeOnlyLand.checked;
function lim(v) { function lim(v) {
return Math.max(Math.min(v, 100), land ? 20 : 0); return minmax(v, land ? 20 : 0, 100);
} }
const h = grid.cells.h; const h = grid.cells.h;

View file

@ -4,10 +4,10 @@ document.addEventListener("keydown", handleKeydown);
document.addEventListener("keyup", handleKeyup); document.addEventListener("keyup", handleKeyup);
function handleKeydown(event) { function handleKeydown(event) {
const {key, code, ctrlKey, altKey} = event; const {code, ctrlKey, altKey} = event;
if (altKey && !ctrlKey) event.preventDefault(); // disallow alt key combinations if (altKey && !ctrlKey) event.preventDefault(); // disallow alt key combinations
if (ctrlKey && ["KeyS", "KeyC"].includes(code)) event.preventDefault(); // disallow CTRL + S and CTRL + C if (ctrlKey && ["KeyS", "KeyC"].includes(code)) event.preventDefault(); // disallow CTRL + S and CTRL + C
if (["F1", "F2", "F6", "F9", "Tab"].includes(key)) event.preventDefault(); // disallow default Fn and Tab if (["F1", "F2", "F6", "F9", "Tab"].includes(code)) event.preventDefault(); // disallow default Fn and Tab
} }
function handleKeyup(event) { function handleKeyup(event) {
@ -19,83 +19,82 @@ function handleKeyup(event) {
if (document.getSelection().toString()) return; // don't trigger if user selects text if (document.getSelection().toString()) return; // don't trigger if user selects text
event.stopPropagation(); event.stopPropagation();
const {ctrlKey, metaKey, shiftKey, altKey} = event; const {code, key, ctrlKey, metaKey, shiftKey, altKey} = event;
const key = event.key.toUpperCase();
const ctrl = ctrlKey || metaKey || key === "Control"; const ctrl = ctrlKey || metaKey || key === "Control";
const shift = shiftKey || key === "Shift"; const shift = shiftKey || key === "Shift";
const alt = altKey || key === "Alt"; const alt = altKey || key === "Alt";
if (key === "F1") showInfo(); if (code === "F1") showInfo();
else if (key === "F2") regeneratePrompt("hotkey"); else if (code === "F2") regeneratePrompt("hotkey");
else if (key === "F6") quickSave(); else if (code === "F6") quickSave();
else if (key === "F9") quickLoad(); else if (code === "F9") quickLoad();
else if (key === "TAB") toggleOptions(event); else if (code === "Tab") toggleOptions(event);
else if (key === "ESCAPE") closeAllDialogs(); else if (code === "Escape") closeAllDialogs();
else if (key === "DELETE") removeElementOnKey(); else if (code === "Delete") removeElementOnKey();
else if (key === "O" && document.getElementById("canvas3d")) toggle3dOptions(); else if (code === "KeyO" && document.getElementById("canvas3d")) toggle3dOptions();
else if (ctrl && key === "Q") toggleSaveReminder(); else if (ctrl && code === "KeyQ") toggleSaveReminder();
else if (ctrl && key === "S") dowloadMap(); else if (ctrl && code === "KeyS") dowloadMap();
else if (ctrl && key === "C") saveToDropbox(); else if (ctrl && code === "KeyC") saveToDropbox();
else if (ctrl && key === "Z" && undo.offsetParent) undo.click(); else if (ctrl && code === "KeyZ" && undo.offsetParent) undo.click();
else if (ctrl && key === "Y" && redo.offsetParent) redo.click(); else if (ctrl && code === "KeyY" && redo.offsetParent) redo.click();
else if (shift && key === "H") editHeightmap(); else if (shift && code === "KeyH") editHeightmap();
else if (shift && key === "B") editBiomes(); else if (shift && code === "KeyB") editBiomes();
else if (shift && key === "S") editStates(); else if (shift && code === "KeyS") editStates();
else if (shift && key === "P") editProvinces(); else if (shift && code === "KeyP") editProvinces();
else if (shift && key === "D") editDiplomacy(); else if (shift && code === "KeyD") editDiplomacy();
else if (shift && key === "C") editCultures(); else if (shift && code === "KeyC") editCultures();
else if (shift && key === "N") editNamesbase(); else if (shift && code === "KeyN") editNamesbase();
else if (shift && key === "Z") editZones(); else if (shift && code === "KeyZ") editZones();
else if (shift && key === "R") editReligions(); else if (shift && code === "KeyR") editReligions();
else if (shift && key === "Y") openEmblemEditor(); else if (shift && code === "KeyY") openEmblemEditor();
else if (shift && key === "Q") editUnits(); else if (shift && code === "KeyQ") editUnits();
else if (shift && key === "O") editNotes(); else if (shift && code === "KeyO") editNotes();
else if (shift && key === "T") overviewBurgs(); else if (shift && code === "KeyT") overviewBurgs();
else if (shift && key === "V") overviewRivers(); else if (shift && code === "KeyV") overviewRivers();
else if (shift && key === "M") overviewMilitary(); else if (shift && code === "KeyM") overviewMilitary();
else if (shift && key === "K") overviewMarkers(); else if (shift && code === "KeyK") overviewMarkers();
else if (shift && key === "E") viewCellDetails(); else if (shift && code === "KeyE") viewCellDetails();
else if (shift && key === "1") toggleAddBurg(); else if (key === "!") toggleAddBurg();
else if (shift && key === "2") toggleAddLabel(); else if (key === "@") toggleAddLabel();
else if (shift && key === "3") toggleAddRiver(); else if (key === "#") toggleAddRiver();
else if (shift && key === "4") toggleAddRoute(); else if (key === "$") toggleAddRoute();
else if (shift && key === "5") toggleAddMarker(); else if (key === "%") toggleAddMarker();
else if (alt && key === "B") console.table(pack.burgs); else if (alt && code === "KeyB") console.table(pack.burgs);
else if (alt && key === "S") console.table(pack.states); else if (alt && code === "KeyS") console.table(pack.states);
else if (alt && key === "C") console.table(pack.cultures); else if (alt && code === "KeyC") console.table(pack.cultures);
else if (alt && key === "R") console.table(pack.religions); else if (alt && code === "KeyR") console.table(pack.religions);
else if (alt && key === "F") console.table(pack.features); else if (alt && code === "KeyF") console.table(pack.features);
else if (key === "X") toggleTexture(); else if (code === "KeyX") toggleTexture();
else if (key === "H") toggleHeight(); else if (code === "KeyH") toggleHeight();
else if (key === "B") toggleBiomes(); else if (code === "KeyB") toggleBiomes();
else if (key === "E") toggleCells(); else if (code === "KeyE") toggleCells();
else if (key === "G") toggleGrid(); else if (code === "KeyG") toggleGrid();
else if (key === "O") toggleCoordinates(); else if (code === "KeyO") toggleCoordinates();
else if (key === "W") toggleCompass(); else if (code === "KeyW") toggleCompass();
else if (key === "V") toggleRivers(); else if (code === "KeyV") toggleRivers();
else if (key === "F") toggleRelief(); else if (code === "KeyF") toggleRelief();
else if (key === "C") toggleCultures(); else if (code === "KeyC") toggleCultures();
else if (key === "S") toggleStates(); else if (code === "KeyS") toggleStates();
else if (key === "P") toggleProvinces(); else if (code === "KeyP") toggleProvinces();
else if (key === "Z") toggleZones(); else if (code === "KeyZ") toggleZones();
else if (key === "D") toggleBorders(); else if (code === "KeyD") toggleBorders();
else if (key === "R") toggleReligions(); else if (code === "KeyR") toggleReligions();
else if (key === "U") toggleRoutes(); else if (code === "KeyU") toggleRoutes();
else if (key === "T") toggleTemp(); else if (code === "KeyT") toggleTemp();
else if (key === "N") togglePopulation(); else if (code === "KeyN") togglePopulation();
else if (key === "J") toggleIce(); else if (code === "KeyJ") toggleIce();
else if (key === "A") togglePrec(); else if (code === "KeyA") togglePrec();
else if (key === "Y") toggleEmblems(); else if (code === "KeyY") toggleEmblems();
else if (key === "L") toggleLabels(); else if (code === "KeyL") toggleLabels();
else if (key === "I") toggleIcons(); else if (code === "KeyI") toggleIcons();
else if (key === "M") toggleMilitary(); else if (code === "KeyM") toggleMilitary();
else if (key === "K") toggleMarkers(); else if (code === "KeyK") toggleMarkers();
else if (key === "=") toggleRulers(); else if (code === "Equal") toggleRulers();
else if (key === "/") toggleScaleBar(); else if (code === "Slash") toggleScaleBar();
else if (key === "ARROWLEFT") zoom.translateBy(svg, 10, 0); else if (code === "ArrowLeft") zoom.translateBy(svg, 10, 0);
else if (key === "ARROWRIGHT") zoom.translateBy(svg, -10, 0); else if (code === "ArrowRight") zoom.translateBy(svg, -10, 0);
else if (key === "ARROWUP") zoom.translateBy(svg, 0, 10); else if (code === "ArrowUp") zoom.translateBy(svg, 0, 10);
else if (key === "ARROWDOWN") zoom.translateBy(svg, 0, -10); else if (code === "ArrowDown") zoom.translateBy(svg, 0, -10);
else if (key === "+" || key === "-") pressNumpadSign(key); else if (key === "+" || key === "-") pressNumpadSign(key);
else if (key === "0") resetZoom(1000); else if (key === "0") resetZoom(1000);
else if (key === "1") zoom.scaleTo(svg, 1); else if (key === "1") zoom.scaleTo(svg, 1);
@ -123,7 +122,7 @@ function pressNumpadSign(key) {
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById("religionsManuallyBrush"); else if (religionsManuallyBrush.offsetParent) brush = document.getElementById("religionsManuallyBrush");
if (brush) { if (brush) {
const value = Math.max(Math.min(+brush.value + change, +brush.max), +brush.min); const value = minmax(+brush.value + change, +brush.min, +brush.max);
brush.value = document.getElementById(brush.id + "Number").value = value; brush.value = document.getElementById(brush.id + "Number").value = value;
return; return;
} }

View file

@ -47,8 +47,7 @@ function changePreset(preset) {
.querySelectorAll("li") .querySelectorAll("li")
.forEach(function (e) { .forEach(function (e) {
if (layers.includes(e.id) && !layerIsOn(e.id)) e.click(); if (layers.includes(e.id) && !layerIsOn(e.id)) e.click();
// turn on else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click();
else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off
}); });
layersPreset.value = preset; layersPreset.value = preset;
localStorage.setItem("preset", preset); localStorage.setItem("preset", preset);
@ -121,6 +120,7 @@ function restoreLayers() {
if (layerIsOn("toggleReligions")) drawReligions(); if (layerIsOn("toggleReligions")) drawReligions();
if (layerIsOn("toggleIce")) drawIce(); if (layerIsOn("toggleIce")) drawIce();
if (layerIsOn("toggleEmblems")) drawEmblems(); if (layerIsOn("toggleEmblems")) drawEmblems();
if (layerIsOn("toggleMarkers")) drawMarkers();
// some layers are rendered each time, remove them if they are not on // some layers are rendered each time, remove them if they are not on
if (!layerIsOn("toggleBorders")) borders.selectAll("path").remove(); if (!layerIsOn("toggleBorders")) borders.selectAll("path").remove();
@ -1435,8 +1435,8 @@ function toggleTexture(event) {
turnButtonOn("toggleTexture"); turnButtonOn("toggleTexture");
// append default texture image selected by default. Don't append on load to not harm performance // append default texture image selected by default. Don't append on load to not harm performance
if (!texture.selectAll("*").size()) { if (!texture.selectAll("*").size()) {
const x = +styleTextureShiftX.value, const x = +styleTextureShiftX.value;
y = +styleTextureShiftY.value; const y = +styleTextureShiftY.value;
const image = texture const image = texture
.append("image") .append("image")
.attr("id", "textureImage") .attr("id", "textureImage")
@ -1444,18 +1444,14 @@ function toggleTexture(event) {
.attr("y", y) .attr("y", y)
.attr("width", graphWidth - x) .attr("width", graphWidth - x)
.attr("height", graphHeight - y) .attr("height", graphHeight - y)
.attr("xlink:href", getDefaultTexture())
.attr("preserveAspectRatio", "xMidYMid slice"); .attr("preserveAspectRatio", "xMidYMid slice");
if (styleTextureInput.value !== "default") getBase64(styleTextureInput.value, base64 => image.attr("xlink:href", base64)); getBase64(styleTextureInput.value, base64 => image.attr("xlink:href", base64));
} }
$("#texture").fadeIn(); $("#texture").fadeIn();
zoom.scaleBy(svg, 1.00001); // enforce browser re-draw zoom.scaleBy(svg, 1.00001); // enforce browser re-draw
if (event && isCtrlClick(event)) editStyle("texture"); if (event && isCtrlClick(event)) editStyle("texture");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("texture");
editStyle("texture");
return;
}
$("#texture").fadeOut(); $("#texture").fadeOut();
turnButtonOff("toggleTexture"); turnButtonOff("toggleTexture");
} }
@ -1563,7 +1559,7 @@ const getPin = (shape = "bubble", fill = "#fff", stroke = "#000") => {
function drawMarker(marker, rescale = 1) { function drawMarker(marker, rescale = 1) {
const {i, icon, x, y, dx = 50, dy = 50, px = 12, size = 30, pin, fill, stroke} = marker; const {i, icon, x, y, dx = 50, dy = 50, px = 12, size = 30, pin, fill, stroke} = marker;
const id = `marker${i}`; const id = `marker${i}`;
const zoomSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : 1; const zoomSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
const viewX = rn(x - zoomSize / 2, 1); const viewX = rn(x - zoomSize / 2, 1);
const viewY = rn(y - zoomSize, 1); const viewY = rn(y - zoomSize, 1);
const pinHTML = getPin(pin, fill, stroke); const pinHTML = getPin(pin, fill, stroke);
@ -1674,21 +1670,21 @@ function drawEmblems() {
const validBurgs = burgs.filter(b => b.i && !b.removed && b.coa && b.coaSize != 0); const validBurgs = burgs.filter(b => b.i && !b.removed && b.coa && b.coaSize != 0);
const getStateEmblemsSize = () => { const getStateEmblemsSize = () => {
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 40, 10), 100); const startSize = minmax((graphHeight + graphWidth) / 40, 10, 100);
const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier
const sizeMod = +document.getElementById("emblemsStateSizeInput").value || 1; const sizeMod = +document.getElementById("emblemsStateSizeInput").value || 1;
return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states
}; };
const getProvinceEmblemsSize = () => { const getProvinceEmblemsSize = () => {
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 100, 5), 70); const startSize = minmax((graphHeight + graphWidth) / 100, 5, 70);
const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier
const sizeMod = +document.getElementById("emblemsProvinceSizeInput").value || 1; const sizeMod = +document.getElementById("emblemsProvinceSizeInput").value || 1;
return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces
}; };
const getBurgEmblemSize = () => { const getBurgEmblemSize = () => {
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 185, 2), 50); const startSize = minmax((graphHeight + graphWidth) / 185, 2, 50);
const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier
const sizeMod = +document.getElementById("emblemsBurgSizeInput").value || 1; const sizeMod = +document.getElementById("emblemsBurgSizeInput").value || 1;
return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs

View file

@ -8,6 +8,8 @@ function editMarker(markerI) {
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true); elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
if (document.getElementById("notesEditor").offsetParent) editNotes(element.id, element.id);
// dom elements // dom elements
const markerType = document.getElementById("markerType"); const markerType = document.getElementById("markerType");
const markerIcon = document.getElementById("markerIcon"); const markerIcon = document.getElementById("markerIcon");

View file

@ -75,14 +75,21 @@ function overviewMilitary() {
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" "); const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}"> lines += `<div class="states" data-id=${s.i} data-state="${
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg> s.name
}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
s.color
}" class="fillRect"></svg>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly> <input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
${lineData} ${lineData}
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div> <div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>
<div data-type="population" data-tip="State population">${si(population)}</div> <div data-type="population" data-tip="State population">${si(population)}</div>
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div> <div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div>
<input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn(s.alert, 2)}"> <input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn(
s.alert,
2
)}">
<span data-tip="Show regiments list" class="icon-list-bullet pointer"></span> <span data-tip="Show regiments list" class="icon-list-bullet pointer"></span>
</div>`; </div>`;
} }
@ -145,7 +152,15 @@ function overviewMilitary() {
if (!layerIsOn("toggleStates")) return; if (!layerIsOn("toggleStates")) return;
const d = regions.select("#state" + state).attr("d"); const d = regions.select("#state" + state).attr("d");
const path = debug.append("path").attr("class", "highlight").attr("d", d).attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1).attr("filter", "url(#blur1)"); const path = debug
.append("path")
.attr("class", "highlight")
.attr("d", d)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1)
.attr("opacity", 1)
.attr("filter", "url(#blur1)");
const l = path.node().getTotalLength(), const l = path.node().getTotalLength(),
dur = (l + 5000) / 2; dur = (l + 5000) / 2;
@ -199,9 +214,9 @@ function overviewMilitary() {
function militaryCustomize() { function militaryCustomize() {
const types = ["melee", "ranged", "mounted", "machinery", "naval", "armored", "aviation", "magical"]; const types = ["melee", "ranged", "mounted", "machinery", "naval", "armored", "aviation", "magical"];
const table = document.getElementById("militaryOptions").querySelector("tbody"); const tableBody = document.getElementById("militaryOptions").querySelector("tbody");
removeUnitLines(); removeUnitLines();
options.military.map(u => addUnitLine(u)); options.military.map(unit => addUnitLine(unit));
$("#militaryOptions").dialog({ $("#militaryOptions").dialog({
title: "Edit Military Units", title: "Edit Military Units",
@ -218,43 +233,132 @@ function overviewMilitary() {
}, },
open: function () { open: function () {
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button"); const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
buttons[0].addEventListener("mousemove", () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>")); buttons[0].addEventListener("mousemove", () =>
tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>")
);
buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table")); buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table"));
buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings")); buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings"));
buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes")); buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes"));
} }
}); });
if (modules.overviewMilitaryCustomize) return;
modules.overviewMilitaryCustomize = true;
tableBody.addEventListener("click", event => {
const el = event.target;
if (el.tagName !== "BUTTON") return;
const type = el.dataset.type;
if (type === "icon") return selectIcon(el.innerHTML, v => (el.innerHTML = v));
if (type === "biomes") {
const {i, name, color} = biomesData;
const biomesArray = Array(i.length).fill(null);
const biomes = biomesArray.map((_, i) => ({i, name: name[i], color: color[i]}));
return selectLimitation(el, biomes);
}
if (type === "states") return selectLimitation(el, pack.states);
if (type === "cultures") return selectLimitation(el, pack.cultures);
if (type === "religions") return selectLimitation(el, pack.religions);
});
function removeUnitLines() { function removeUnitLines() {
table.querySelectorAll("tr").forEach(el => el.remove()); tableBody.querySelectorAll("tr").forEach(el => el.remove());
} }
function addUnitLine(u) { function getLimitValue(attr) {
return attr?.join(",") || "";
}
function getLimitText(attr) {
return attr?.length ? "some" : "all";
}
function getLimitTip(attr, data) {
if (!attr || !attr.length) return "";
return attr.map(i => data?.[i]?.name || "").join(", ");
}
function addUnitLine(unit) {
const row = document.createElement("tr"); const row = document.createElement("tr");
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon || " "}</button></td> const typeOptions = types.map(t => `<option ${unit.type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ");
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td> const getLimitButton = attr =>
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td> `<button
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td> data-tip="Select allowed ${attr}"
<td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td> data-type="${attr}"
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td> title="${getLimitTip(unit[attr], pack[attr])}"
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types.map(t => `<option ${u.type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ")}</select></td> data-value="${getLimitValue(unit[attr])}">
${getLimitText(unit[attr])}
</button>`;
row.innerHTML = `<td><button data-type="icon" data-tip="Click to select unit icon">${unit.icon || " "}</button></td>
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${unit.name}"></td>
<td>${getLimitButton("biomes")}</td>
<td>${getLimitButton("states")}</td>
<td>${getLimitButton("cultures")}</td>
<td>${getLimitButton("religions")}</td>
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${unit.rural}"></td>
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${unit.urban}"></td>
<td><input data-tip="Enter average number of people in crew (for total personnel calculation)" type="number" min=1 step=1 value="${unit.crew}"></td>
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${unit.power}"></td>
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${typeOptions}</select></td>
<td data-tip="Check if unit is separate and can be stacked only with units of the same type"> <td data-tip="Check if unit is separate and can be stacked only with units of the same type">
<input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? "checked" : ""}> <input id="${unit.name}Separate" type="checkbox" class="checkbox" ${unit.separate ? "checked" : ""}>
<label for="${u.name}Separate" class="checkbox-label"></label></td> <label for="${unit.name}Separate" class="checkbox-label"></label></td>
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`; <td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
row.querySelector("button").addEventListener("click", function (e) { tableBody.appendChild(row);
selectIcon(this.innerHTML, v => (this.innerHTML = v));
});
table.appendChild(row);
} }
function restoreDefaultUnits() { function restoreDefaultUnits() {
removeUnitLines(); removeUnitLines();
Military.getDefaultOptions().map(u => addUnitLine(u)); Military.getDefaultOptions().map(unit => addUnitLine(unit));
}
function selectLimitation(el, data) {
const type = el.dataset.type;
const value = el.dataset.value;
const initial = value ? value.split(",").map(v => +v) : [];
const lines = data.slice(1).map(
({i, name, fullName, color}) =>
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
<td><input id="el${i}" type="checkbox" class="checkbox" ${!initial.length || initial.includes(i) ? "checked" : ""} >
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
</td></tr>`
);
alertMessage.innerHTML = `<b>Limit unit by ${type}:</b><div style="margin-top:.3em" class="table"><table><tbody>${lines.join("")}</tbody></table></div>`;
$("#alert").dialog({
width: fitContent(),
title: `Limit unit`,
buttons: {
Invert: function () {
alertMessage.querySelectorAll("input").forEach(el => (el.checked = !el.checked));
},
Apply: function () {
const inputs = Array.from(alertMessage.querySelectorAll("input"));
const selected = inputs.reduce((acc, input, index) => {
if (input.checked) acc.push(index + 1);
return acc;
}, []);
if (!selected.length) return tip("Select at least one element", false, "error");
const allAreSelected = selected.length === inputs.length;
el.dataset.value = allAreSelected ? "" : selected.join(",");
el.innerHTML = allAreSelected ? "all" : "some";
el.setAttribute("title", getLimitTip(selected, data));
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
} }
function applyMilitaryOptions() { function applyMilitaryOptions() {
const unitLines = Array.from(table.querySelectorAll("tr")); const unitLines = Array.from(tableBody.querySelectorAll("tr"));
const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_")); const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_"));
if (new Set(names).size !== names.length) { if (new Set(names).size !== names.length) {
tip("All units should have unique names", false, "error"); tip("All units should have unique names", false, "error");
@ -263,14 +367,22 @@ function overviewMilitary() {
$("#militaryOptions").dialog("close"); $("#militaryOptions").dialog("close");
options.military = unitLines.map((r, i) => { options.military = unitLines.map((r, i) => {
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll("input, select, button")).map(d => { const elements = Array.from(r.querySelectorAll("input, button, select"));
let value = d.value; const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = elements.map(el => {
if (d.type === "number") value = +d.value || 0; const {type, value} = el.dataset || {};
if (d.type === "checkbox") value = +d.checked || 0; if (type === "icon") return el.innerHTML || "";
if (d.type === "button") value = d.innerHTML || ""; if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
return value; if (el.type === "number") return +el.value || 0;
if (el.type === "checkbox") return +el.checked || 0;
return el.value;
}); });
return {icon, name: names[i], rural, urban, crew, power, type, separate};
const unit = {icon, name: names[i], rural, urban, crew, power, type, separate};
if (biomes) unit.biomes = biomes;
if (states) unit.states = states;
if (cultures) unit.cultures = cultures;
if (religions) unit.religions = religions;
return unit;
}); });
localStorage.setItem("military", JSON.stringify(options.military)); localStorage.setItem("military", JSON.stringify(options.military));
Military.generate(); Military.generate();

View file

@ -1,4 +1,5 @@
"use strict"; "use strict";
function editNotes(id, name) { function editNotes(id, name) {
// update list of objects // update list of objects
const select = document.getElementById("notesSelect"); const select = document.getElementById("notesSelect");
@ -8,11 +9,12 @@ function editNotes(id, name) {
} }
// initiate pell (html editor) // initiate pell (html editor)
const notesText = document.getElementById("notesText");
notesText.innerHTML = "";
const editor = Pell.init({ const editor = Pell.init({
element: document.getElementById("notesText"), element: notesText,
onChange: html => { onChange: html => {
const id = document.getElementById("notesSelect").value; const note = notes.find(note => note.id === select.value);
const note = notes.find(note => note.id === id);
if (!note) return; if (!note) return;
note.legend = html; note.legend = html;
showNote(note); showNote(note);
@ -43,8 +45,7 @@ function editNotes(id, name) {
title: "Notes Editor", title: "Notes Editor",
minWidth: "40em", minWidth: "40em",
width: "50vw", width: "50vw",
position: {my: "center", at: "center", of: "svg"}, position: {my: "center", at: "center", of: "svg"}
close: () => (notesText.innerHTML = "")
}); });
if (modules.editNotes) return; if (modules.editNotes) return;

View file

@ -13,7 +13,6 @@ if (localStorage.getItem("disable_click_arrow_tooltip")) {
// Show options pane on trigger click // Show options pane on trigger click
function showOptions(event) { function showOptions(event) {
track("click", "show options");
if (!localStorage.getItem("disable_click_arrow_tooltip")) { if (!localStorage.getItem("disable_click_arrow_tooltip")) {
clearMainTip(); clearMainTip();
localStorage.setItem("disable_click_arrow_tooltip", true); localStorage.setItem("disable_click_arrow_tooltip", true);
@ -76,7 +75,6 @@ document
// show popup with a list of Patreon supportes (updated manually, to be replaced with API call) // show popup with a list of Patreon supportes (updated manually, to be replaced with API call)
function showSupporters() { function showSupporters() {
track("click", "show supporters");
const supporters = `Aaron Meyer,Ahmad Amerih,AstralJacks,aymeric,Billy Dean Goehring,Branndon Edwards,Chase Mayers,Curt Flood,cyninge,Dino Princip, const supporters = `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, 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, Nathan Mitchell,Paavi1,Pat,Ryan Westcott,Sasquatch,Shawn Spencer,Sizz_TV,Timothée CALLET,UTG community,Vlad Tomash,Wil Sisney,William Merriott,
@ -91,19 +89,18 @@ function showSupporters() {
Maxwell Hill,Drunken_Legends,rob bee,Jesse Holmes,YYako,Detocroix,Anoplexian,Hannah,Paul,Sandra Krohn,Lucid,Richard Keating,Allen Varney,Rick Falkvinge, 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, 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, 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,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,Thirty-OneR, Thirty-OneR,ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,
ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill, 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,
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, Alex Debus,Joshua Vaught,Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,
Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,Radovan Zapletal,Jmmat6, Radovan Zapletal,Jmmat6,Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,
Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,Guilherme Aguiar,Jarno Hallikainen, Guilherme Aguiar,Jarno Hallikainen,Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,
Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,Cooper Counts,Patrick Jones,Clonetone, Cooper Counts,Patrick Jones,Clonetone,PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,
PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram, Page One Project,Spencer Morris,Paul Ingram,Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee, Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth, PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 ,Chris Gray,Phoenix Boatwright,Mackenzie, Nobody679,良义 ,Chris Gray,Phoenix Boatwright,Mackenzie,Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,
Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne, Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,
George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,Mike Conley,Xavier privé,Hope You're Well, Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky,Dario Spadavecchia`;
Mark Sprietsma,Robert Landry,Nick Mowry"`;
const array = supporters const array = supporters
.replace(/(?:\r\n|\r|\n)/g, "") .replace(/(?:\r\n|\r|\n)/g, "")
@ -476,10 +473,10 @@ function changeDialogsTheme(themeColor, transparency) {
} }
function changeZoomExtent(value) { function changeZoomExtent(value) {
const min = Math.max(+zoomExtentMin.value, 0.01), const min = Math.max(+zoomExtentMin.value, 0.01);
max = Math.min(+zoomExtentMax.value, 200); const max = Math.min(+zoomExtentMax.value, 200);
zoom.scaleExtent([min, max]); zoom.scaleExtent([min, max]);
const scale = Math.max(Math.min(+value, 200), 0.01); const scale = minmax(+value, 0.01, 200);
zoom.scaleTo(svg, scale); zoom.scaleTo(svg, scale);
} }
@ -520,7 +517,7 @@ function applyStoredOptions() {
uiSizeInput.max = uiSizeOutput.max = getUImaxSize(); uiSizeInput.max = uiSizeOutput.max = getUImaxSize();
if (localStorage.getItem("uiSize")) changeUIsize(localStorage.getItem("uiSize")); if (localStorage.getItem("uiSize")) changeUIsize(localStorage.getItem("uiSize"));
else changeUIsize(Math.max(Math.min(rn(mapWidthInput.value / 1280, 1), 2.5), 1)); else changeUIsize(minmax(rn(mapWidthInput.value / 1280, 1), 1, 2.5));
// search params overwrite stored and default options // search params overwrite stored and default options
const params = new URL(window.location.href).searchParams; const params = new URL(window.location.href).searchParams;

View file

@ -335,7 +335,6 @@ styleFilterInput.addEventListener("change", function () {
styleTextureInput.addEventListener("change", function () { styleTextureInput.addEventListener("change", function () {
if (this.value === "none") texture.select("image").attr("xlink:href", ""); if (this.value === "none") texture.select("image").attr("xlink:href", "");
if (this.value === "default") texture.select("image").attr("xlink:href", getDefaultTexture());
else getBase64(this.value, base64 => texture.select("image").attr("xlink:href", base64)); else getBase64(this.value, base64 => texture.select("image").attr("xlink:href", base64));
}); });

View file

@ -469,7 +469,10 @@ function addLabelOnClick() {
const name = Names.getCulture(culture); const name = Names.getCulture(culture);
const id = getNextId("label"); const id = getNextId("label");
let group = labels.select("#addedLabels"); // use most recently selected label group
let selected = labelGroupSelect.value;
const symbol = selected ? "#" + selected : "#addedLabels";
let group = labels.select(symbol);
if (!group.size()) if (!group.size())
group = labels group = labels
.append("g") .append("g")

File diff suppressed because one or more lines are too long