Merge synchronizations with the master branch

This commit is contained in:
Peter 2022-11-27 16:18:19 -05:00
parent f3396941e4
commit 9d18e962ac
6 changed files with 875 additions and 568 deletions

View file

@ -24,12 +24,8 @@
font-size: 10px; font-size: 10px;
overflow: hidden; overflow: hidden;
} }
#loading > * { #map {
pointer-events: none; position: absolute;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
} }
#loading > * { #loading > * {
pointer-events: none; pointer-events: none;
@ -121,7 +117,6 @@
id="map" id="map"
width="100%" width="100%"
height="100%" height="100%"
style="position: absolute"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink"
@ -340,11 +335,20 @@
</svg> </svg>
<div id="loading"> <div id="loading">
<svg width="100%" height="100%">
<rect x="-1%" y="-1%" width="102%" height="102%" fill="#466eab" />
<rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)" />
</svg>
<svg id="loading-rose" width="100%" height="100%" viewBox="0 0 700 700">
<use href="#rose" x="50%" y="50%" />
</svg>
<div id="loading-typography">
<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.66</div> <div id="versionText"><t data-t="version">v</t><span id="version"></span></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>
</div>
<div id="optionsContainer" style="opacity: 0"> <div id="optionsContainer" style="opacity: 0">
<div id="collapsible"> <div id="collapsible">
@ -2014,22 +2018,64 @@
<p>Click to regenerate:</p> <p>Click to regenerate:</p>
<div id="regenerateFeature"> <div id="regenerateFeature">
<button id="regenerateStateLabels" data-tip="Click to update state labels placement based on current borders">Labels</button> <button
<button id="regenerateReliefIcons" data-tip="Click to regenerate all relief icons based on current cell biome and elevation">Relief</button> id="regenerateStateLabels"
data-tip="Click to update state labels placement based on current borders"
>
Labels
</button>
<button
id="regenerateReliefIcons"
data-tip="Click to regenerate all relief icons based on current cell biome and elevation"
>
Relief
</button>
<button id="regenerateRoutes" data-tip="Click to regenerate all routes">Routes</button> <button id="regenerateRoutes" data-tip="Click to regenerate all routes">Routes</button>
<button id="regenerateRivers" data-tip="Click to regenerate all rivers (restore default state)">Rivers</button> <button id="regenerateRivers" data-tip="Click to regenerate all rivers (restore default state)">
<button id="regeneratePopulation" data-tip="Click to recalculate rural and urban population">Population</button> Rivers
<button id="regenerateStates" data-tip="Click to select new capitals and regenerate states. Emblems and military forces will be regenerated as well, burgs will remain as they are">States</button> </button>
<button id="regenerateProvinces" data-tip="Click to regenerate provinces. States will remain as they are">Provinces</button> <button id="regeneratePopulation" data-tip="Click to recalculate rural and urban population">
<button id="regenerateBurgs" data-tip="Click to regenerate all unlocked burgs and routes. States will remain as they are">Burgs</button> Population
<button id="regenerateResources" data-tip="Click to regenerate resources">Resources</button> </button>
<button
id="regenerateStates"
data-tip="Click to select new capitals and regenerate states. Emblems and military forces will be regenerated as well, burgs will remain as they are"
>
States
</button>
<button id="regenerateProvinces" data-tip="Click to regenerate provinces. States will remain as they are">
Provinces
</button>
<button
id="regenerateBurgs"
data-tip="Click to regenerate all unlocked burgs and routes. States will remain as they are. Note: burgs are only generated in populated areas with culture assigned"
>
Burgs
</button>
<button
id="regenerateResources"
data-tip="Click to regenerate resources">
Resources
</button>
<button id="regenerateEmblems" data-tip="Click to regenerate all emblems">Emblems</button> <button id="regenerateEmblems" data-tip="Click to regenerate all emblems">Emblems</button>
<button id="regenerateReligions" data-tip="Click to regenerate religions">Religions</button> <button id="regenerateReligions" data-tip="Click to regenerate religions">Religions</button>
<button id="regenerateCultures" data-tip="Click to regenerate cultures">Cultures</button> <button id="regenerateCultures" data-tip="Click to regenerate cultures">Cultures</button>
<button id="regenerateMilitary" data-tip="Click to recalculate military forces based on current military options">Military</button> <button
id="regenerateMilitary"
data-tip="Click to recalculate military forces based on current military options"
>
Military
</button>
<button id="regenerateIce" data-tip="Click to icebergs and glaciers">Ice</button> <button id="regenerateIce" data-tip="Click to icebergs and glaciers">Ice</button>
<button id="regenerateMarkers" data-tip="Click to regenerate markers. Hold Ctrl and click to set markers number multiplier">Markers</button> <button id="regenerateMarkers" data-tip="Click to regenerate markers">
<button id="regenerateZones" data-tip="Click to regenerate zones. Hold Ctrl and click to set zones number multiplier">Zones</button> Markers <i id="configRegenerateMarkers" class="icon-cog" data-tip="Click to set number multiplier"></i>
</button>
<button
id="regenerateZones"
data-tip="Click to regenerate zones. Hold Ctrl and click to set zones number multiplier"
>
Zones
</button>
</div> </div>
<p>Click to add:</p> <p>Click to add:</p>
@ -5864,27 +5910,6 @@
</div> </div>
</div> </div>
</div> </div>
<div data-tip="Move all existing towns to the 'largetown' burg group">
<input id="submapPromoteTowns" class="checkbox" type="checkbox" />
<label for="submapPromoteTowns" class="checkbox-label">Promote towns to largetowns</label>
</div>
<div data-tip="Add lakes in depressions (can be very slow on big landmasses)">
<input id="submapAddLakeInDepression" class="checkbox" type="checkbox" />
<label for="submapAddLakeInDepression" class="checkbox-label">Add lakes in depressions (slow)</label>
</div>
</div>
<div id="alert" style="display: none" class="dialog">
<p id="alertMessage">Warning!</p>
</div>
<div id="prompt" style="display: none" class="dialog">
<form id="promptForm">
<div id="promptText"></div>
<input id="promptInput" type="number" step=".01" placeholder="type value" autocomplete="off" />
<button type="submit">Confirm</button>
<button type="button" id="promptCancel" formnovalidate>Cancel</button>
</form>
</div> </div>
<div id="saveTilesScreen" style="display: none" class="dialog"> <div id="saveTilesScreen" style="display: none" class="dialog">
@ -8092,6 +8117,8 @@
<script src="modules/heightmap-generator.js"></script> <script src="modules/heightmap-generator.js"></script>
<script src="modules/ocean-layers.js?v=1.87.15"></script> <script src="modules/ocean-layers.js?v=1.87.15"></script>
<script src="modules/resources-generator.js"></script> <script src="modules/resources-generator.js"></script>
<script src="modules/production-generator.js"></script>
<script src="modules/trade-generator.js"></script>
<script src="modules/river-generator.js"></script> <script src="modules/river-generator.js"></script>
<script src="modules/lakes.js"></script> <script src="modules/lakes.js"></script>
<script src="modules/names-generator.js?v=1.87.14"></script> <script src="modules/names-generator.js?v=1.87.14"></script>

63
main.js
View file

@ -517,13 +517,13 @@ function handleZoom(isScaleChanged, isPositionChanged) {
// zoom image converter overlay // zoom image converter overlay
if (customization === 1) { if (customization === 1) {
const canvas = document.getElementById('canvas'); const canvas = document.getElementById("canvas");
if (!canvas || canvas.style.opacity === '0') return; if (!canvas || canvas.style.opacity === "0") return;
const img = document.getElementById('imageToConvert'); const img = document.getElementById("imageToConvert");
if (!img) return; if (!img) return;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(scale, 0, 0, scale, viewX, viewY); ctx.setTransform(scale, 0, 0, scale, viewX, viewY);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
@ -565,11 +565,11 @@ function invokeActiveZooming() {
if (this.id === "burgLabels") return; if (this.id === "burgLabels") return;
const desired = +this.dataset.size; const desired = +this.dataset.size;
const relative = Math.max(rn((desired + desired / scale) / 2, 2), 1); const relative = Math.max(rn((desired + desired / scale) / 2, 2), 1);
if (rescaleLabels.checked) this.setAttribute('font-size', relative); if (rescaleLabels.checked) this.setAttribute("font-size", relative);
const hidden = hideLabels.checked && (relative * scale < 6 || relative * scale > 60); const hidden = hideLabels.checked && (relative * scale < 6 || relative * scale > 60);
if (hidden) this.classList.add('hidden'); if (hidden) this.classList.add("hidden");
else this.classList.remove('hidden'); else this.classList.remove("hidden");
}); });
} }
@ -595,7 +595,7 @@ function invokeActiveZooming() {
if (!customization && !isOptimized) { if (!customization && !isOptimized) {
const desired = +statesHalo.attr("data-width"); const desired = +statesHalo.attr("data-width");
const haloSize = rn(desired / scale ** 0.8, 2); const haloSize = rn(desired / scale ** 0.8, 2);
statesHalo.attr('stroke-width', haloSize).style('display', haloSize > 0.1 ? 'block' : 'none'); statesHalo.attr("stroke-width", haloSize).style("display", haloSize > 0.1 ? "block" : "none");
} }
// rescale map markers // rescale map markers
@ -721,13 +721,17 @@ async function generate(options) {
rankCells(); rankCells();
Cultures.generate(); Cultures.generate();
Cultures.expand(); Cultures.expand();
BurgsAndStates.generate(); BurgsAndStates.generate();
Religions.generate(); Religions.generate();
BurgsAndStates.defineStateForms(); BurgsAndStates.defineStateForms();
BurgsAndStates.generateProvinces();
BurgsAndStates.defineBurgFeatures();
BurgsAndStates.defineTaxes(); BurgsAndStates.defineTaxes();
drawStates();
drawBorders();
BurgsAndStates.drawStateLabels();
Production.collectResources(); Production.collectResources();
Production.defineExport(); Production.defineExport();
@ -740,13 +744,6 @@ async function generate(options) {
// pack.cells.road = new Uint16Array(pack.cells.i.length); // pack.cells.road = new Uint16Array(pack.cells.i.length);
// pack.cells.crossroad = new Uint16Array(pack.cells.i.length); // pack.cells.crossroad = new Uint16Array(pack.cells.i.length);
BurgsAndStates.generateProvinces();
BurgsAndStates.defineBurgFeatures();
drawStates();
drawBorders();
BurgsAndStates.drawStateLabels();
Rivers.specify(); Rivers.specify();
Lakes.generateName(); Lakes.generateName();
@ -1435,9 +1432,9 @@ function reMarkFeatures() {
cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell); cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell);
cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells); cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells);
const defineHaven = (i) => { const defineHaven = i => {
const water = cells.c[i].filter((c) => cells.h[c] < 20); const water = cells.c[i].filter(c => cells.h[c] < 20);
const dist2 = water.map((c) => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2); const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2);
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))]; const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))];
cells.haven[i] = closest; cells.haven[i] = closest;
@ -1506,12 +1503,6 @@ function isWetLand(moisture, temperature, height) {
return false; return false;
} }
function isWetLand(moisture, temperature, height) {
if (moisture > 40 && temperature > -2 && height < 25) return true; //near coast
if (moisture > 24 && temperature > -2 && height > 24 && height < 60) return true; //off coast
return false;
}
// assign biome id for each cell // assign biome id for each cell
function defineBiomes() { function defineBiomes() {
TIME && console.time("defineBiomes"); TIME && console.time("defineBiomes");
@ -1551,12 +1542,12 @@ function getBiomeId(moisture, temperature, height) {
return biomesData.biomesMartix[moistureBand][temperatureBand]; return biomesData.biomesMartix[moistureBand][temperatureBand];
} }
// assess cells suitability to calculate population and rang cells for culture center and burgs placement // assess cells suitability to calculate population and rand cells for culture center and burgs placement
function rankCells() { function rankCells() {
TIME && console.time("rankCells"); TIME && console.time("rankCells");
const {cells, features} = pack; const {cells, features} = pack;
cells.s = new Int16Array(cells.i.length); // cell suitability score cells.s = new Int16Array(cells.i.length); // cell suitability array
cells.pop = new Float32Array(cells.i.length); // cell population cells.pop = new Float32Array(cells.i.length); // cell population array
const flMean = d3.median(cells.fl.filter(f => f)) || 0, const flMean = d3.median(cells.fl.filter(f => f)) || 0,
flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux
@ -1567,11 +1558,10 @@ function rankCells() {
for (const i of cells.i) { for (const i of cells.i) {
if (cells.b[i]) continue; // avoid adding burgs on map border if (cells.b[i]) continue; // avoid adding burgs on map border
if (cells.h[i] < 20) continue; // no population in water if (cells.h[i] < 20) continue; // no population in water
let s = biomesData.habitability[cells.biome[i]] / 10; // base suitability derived from biome habitability let s = +biomesData.habitability[cells.biome[i]]; // base suitability derived from biome habitability
if (!s) continue; // uninhabitable biomes has 0 suitability if (!s) continue; // uninhabitable biomes has 0 suitability
if (flMean) s += normalize(cells.fl[i] + cells.conf[i], flMean, flMax) * 250; // big rivers and confluences are valued
if (flMean) s += normalize(cells.fl[i] + cells.conf[i], flMean, flMax) * 50; // big rivers and confluences are valued s -= (cells.h[i] - 50) / 5; // low elevation is valued, high is not;
s -= (cells.h[i] - 50) / 25; // low elevation is valued, high is not;
if (cells.t[i] === 1) { if (cells.t[i] === 1) {
if (cells.r[i]) s += 15; // estuary is valued if (cells.r[i]) s += 15; // estuary is valued
@ -1584,8 +1574,8 @@ function rankCells() {
else if (feature.group == "sinkhole") s -= 5; else if (feature.group == "sinkhole") s -= 5;
else if (feature.group == "lava") s -= 30; else if (feature.group == "lava") s -= 30;
} else { } else {
s += 1; // ocean coast is valued s += 5; // ocean coast is valued
if (cells.harbor[i] === 1) s += 4; // safe sea harbor is valued if (cells.harbor[i] === 1) s += 20; // safe sea harbor is valued
} }
} }
@ -1595,8 +1585,7 @@ function rankCells() {
const resBonus = (cellRes ? cellRes + 10 : 0) + neibRes; const resBonus = (cellRes ? cellRes + 10 : 0) + neibRes;
// cell rural population is suitability adjusted by cell area // cell rural population is suitability adjusted by cell area
cells.pop[i] = s > 0 ? (s * POP_BALANCER * cells.area[i]) / areaMean : 0; cells.pop[i] = cells.s[i] > 0 ? (cells.s[i] * cells.area[i]) / areaMean : 0;
cells.s[i] = s + resBonus;
} }
TIME && console.timeEnd("rankCells"); TIME && console.timeEnd("rankCells");

View file

@ -5,22 +5,28 @@ window.BurgsAndStates = (function () {
const {cells, cultures} = pack; const {cells, cultures} = pack;
const n = cells.i.length; const n = cells.i.length;
cells.burg = new Uint16Array(n); cells.burg = new Uint16Array(n); // cell burg
pack.burgs = placeCapitals(); cells.road = new Uint16Array(n); // cell road power
const {burgs} = pack; cells.crossroad = new Uint16Array(n); // cell crossroad power
const burgs = (pack.burgs = placeCapitals());
pack.states = createStates(); pack.states = createStates();
const capitalRoutes = Routes.getRoads();
placeTowns(); placeTowns();
expandStates(); expandStates();
normalizeStates(); normalizeStates();
const townRoutes = Routes.getTrails();
specifyBurgs(); specifyBurgs();
const oceanRoutes = Routes.getSearoutes();
collectStatistics(); collectStatistics();
assignColors(); assignColors();
generateCampaigns(); generateCampaigns();
generateDiplomacy(); generateDiplomacy();
Routes.draw(capitalRoutes, townRoutes, oceanRoutes);
drawBurgs(); drawBurgs();
function placeCapitals() { function placeCapitals() {
@ -55,7 +61,7 @@ window.BurgsAndStates = (function () {
} }
if (i === sorted.length - 1) { if (i === sorted.length - 1) {
WARN && console.warn('Cannot place capitals with current spacing. Trying again with reduced spacing'); WARN && console.warn("Cannot place capitals with current spacing. Trying again with reduced spacing");
burgsTree = d3.quadtree(); burgsTree = d3.quadtree();
i = -1; i = -1;
burgs = [0]; burgs = [0];
@ -68,13 +74,12 @@ window.BurgsAndStates = (function () {
return burgs; return burgs;
} }
// for each capital create a state // For each capital create a state
function createStates() { function createStates() {
TIME && console.time("createStates"); TIME && console.time("createStates");
const states = [{i: 0, name: "Neutrals"}]; const states = [{i: 0, name: "Neutrals"}];
const colors = getColors(burgs.length - 1); const colors = getColors(burgs.length - 1);
const each5th = each(5); const each5th = each(5);
const maxExpansionism = +powerInput.value;
burgs.forEach(function (b, i) { burgs.forEach(function (b, i) {
if (!i) return; // skip first element if (!i) return; // skip first element
@ -87,7 +92,7 @@ window.BurgsAndStates = (function () {
b.capital = 1; b.capital = 1;
// states data // states data
const expansionism = rn(Math.random() * maxExpansionism + 1, 1); const expansionism = rn(Math.random() * powerInput.value + 1, 1);
const basename = b.name.length < 9 && each5th(b.cell) ? b.name : Names.getCultureShort(b.culture); const basename = b.name.length < 9 && each5th(b.cell) ? b.name : Names.getCultureShort(b.culture);
const name = Names.getState(basename, b.culture); const name = Names.getState(basename, b.culture);
const type = cultures[b.culture].type; const type = cultures[b.culture].type;
@ -133,10 +138,9 @@ window.BurgsAndStates = (function () {
while (burgsAdded < burgsNumber && spacing > 1) { while (burgsAdded < burgsNumber && spacing > 1) {
for (let i = 0; burgsAdded < burgsNumber && i < sorted.length; i++) { for (let i = 0; burgsAdded < burgsNumber && i < sorted.length; i++) {
if (cells.burg[sorted[i]]) continue; if (cells.burg[sorted[i]]) continue;
const cell = sorted[i],
const cell = sorted[i]; x = cells.p[cell][0],
const [x, y] = cells.p[cell]; y = cells.p[cell][1];
const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make placement not uniform const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make placement not uniform
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
const burg = burgs.length; const burg = burgs.length;
@ -180,10 +184,8 @@ window.BurgsAndStates = (function () {
b.port = port ? f : 0; // port is defined by water body id it lays on b.port = port ? f : 0; // port is defined by water body id it lays on
} else b.port = 0; } else b.port = 0;
// define burg population // define burg population (keep urbanization at about 10% rate)
const primaryPopulation = cells.s[i]; b.population = rn(Math.max((cells.s[i] + cells.road[i] / 2) / 8 + b.i / 1000 + (i % 100) / 1000, 0.1), 3);
const secondaryPopulation = b.i / 1000 + (i % 100) / 1000;
b.population = rn(Math.max(primaryPopulation + secondaryPopulation, 0.1), 3);
if (b.capital) b.population = rn(b.population * 1.3, 3); // increase capital population if (b.capital) b.population = rn(b.population * 1.3, 3); // increase capital population
if (b.port) { if (b.port) {
@ -219,10 +221,10 @@ window.BurgsAndStates = (function () {
} }
// de-assign port status if it's the only one on feature // de-assign port status if it's the only one on feature
const ports = pack.burgs.filter((b) => !b.removed && b.port > 0); const ports = pack.burgs.filter(b => !b.removed && b.port > 0);
for (const f of features) { for (const f of features) {
if (!f.i || f.land || f.border) continue; if (!f.i || f.land || f.border) continue;
const featurePorts = ports.filter((b) => b.port === f.i); const featurePorts = ports.filter(b => b.port === f.i);
if (featurePorts.length === 1) featurePorts[0].port = 0; if (featurePorts.length === 1) featurePorts[0].port = 0;
} }
@ -231,14 +233,14 @@ window.BurgsAndStates = (function () {
const getType = function (i, port) { const getType = function (i, port) {
const cells = pack.cells; const cells = pack.cells;
if (port) return 'Naval'; if (port) return "Naval";
if (cells.haven[i] && pack.features[cells.f[cells.haven[i]]].type === 'lake') return 'Lake'; if (cells.haven[i] && pack.features[cells.f[cells.haven[i]]].type === "lake") return "Lake";
if (cells.h[i] > 60) return 'Highland'; if (cells.h[i] > 60) return "Highland";
if (cells.r[i] && cells.r[i].length > 100 && cells.r[i].length >= pack.rivers[0].length) return 'River'; if (cells.r[i] && cells.r[i].length > 100 && cells.r[i].length >= pack.rivers[0].length) return "River";
if (!cells.burg[i] || pack.burgs[cells.burg[i]].population < 6) { if (!cells.burg[i] || pack.burgs[cells.burg[i]].population < 6) {
if (population < 5 && [1, 2, 3, 4].includes(cells.biome[i])) return 'Nomadic'; if (population < 5 && [1, 2, 3, 4].includes(cells.biome[i])) return "Nomadic";
if (cells.biome[i] > 4 && cells.biome[i] < 10) return 'Hunting'; if (cells.biome[i] > 4 && cells.biome[i] < 10) return "Hunting";
} }
return "Generic"; return "Generic";
@ -264,9 +266,9 @@ window.BurgsAndStates = (function () {
TIME && console.time("drawBurgs"); TIME && console.time("drawBurgs");
// remove old data // remove old data
burgIcons.selectAll('circle').remove(); burgIcons.selectAll("circle").remove();
burgLabels.selectAll('text').remove(); burgLabels.selectAll("text").remove();
icons.selectAll('use').remove(); icons.selectAll("use").remove();
// capitals // capitals
const capitals = pack.burgs.filter(b => b.capital && !b.removed); const capitals = pack.burgs.filter(b => b.capital && !b.removed);
@ -383,7 +385,7 @@ window.BurgsAndStates = (function () {
const {e, p, s, b} = next; const {e, p, s, b} = next;
const {type, culture} = states[s]; const {type, culture} = states[s];
cells.c[e].forEach((e) => { cells.c[e].forEach(e => {
if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells
const cultureCost = culture === cells.culture[e] ? -9 : 100; const cultureCost = culture === cells.culture[e] ? -9 : 100;
@ -409,33 +411,33 @@ window.BurgsAndStates = (function () {
function getBiomeCost(b, biome, type) { function getBiomeCost(b, biome, type) {
if (b === biome) return 10; // tiny penalty for native biome if (b === biome) return 10; // tiny penalty for native biome
if (type === 'Hunting') return biomesData.cost[biome] * 2; // non-native biome penalty for hunters if (type === "Hunting") return biomesData.cost[biome] * 2; // non-native biome penalty for hunters
if (type === 'Nomadic' && biome > 4 && biome < 10) return biomesData.cost[biome] * 3; // forest biome penalty for nomads if (type === "Nomadic" && biome > 4 && biome < 10) return biomesData.cost[biome] * 3; // forest biome penalty for nomads
return biomesData.cost[biome]; // general non-native biome penalty return biomesData.cost[biome]; // general non-native biome penalty
} }
function getHeightCost(f, h, type) { function getHeightCost(f, h, type) {
if (type === 'Lake' && f.type === 'lake') return 10; // low lake crossing penalty for Lake cultures if (type === "Lake" && f.type === "lake") return 10; // low lake crossing penalty for Lake cultures
if (type === 'Naval' && h < 20) return 300; // low sea crossing penalty for Navals if (type === "Naval" && h < 20) return 300; // low sea crossing penalty for Navals
if (type === 'Nomadic' && h < 20) return 10000; // giant sea crossing penalty for Nomads if (type === "Nomadic" && h < 20) return 10000; // giant sea crossing penalty for Nomads
if (h < 20) return 1000; // general sea crossing penalty if (h < 20) return 1000; // general sea crossing penalty
if (type === 'Highland' && h < 62) return 1100; // penalty for highlanders on lowlands if (type === "Highland" && h < 62) return 1100; // penalty for highlanders on lowlands
if (type === 'Highland') return 0; // no penalty for highlanders on highlands if (type === "Highland") return 0; // no penalty for highlanders on highlands
if (h >= 67) return 2200; // general mountains crossing penalty if (h >= 67) return 2200; // general mountains crossing penalty
if (h >= 44) return 300; // general hills crossing penalty if (h >= 44) return 300; // general hills crossing penalty
return 0; return 0;
} }
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 minmax(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) {
if (t === 1) return type === 'Naval' || type === 'Lake' ? 0 : type === 'Nomadic' ? 60 : 20; // penalty for coastline if (t === 1) return type === "Naval" || type === "Lake" ? 0 : type === "Nomadic" ? 60 : 20; // penalty for coastline
if (t === 2) return type === 'Naval' || type === 'Nomadic' ? 30 : 0; // low penalty for land level 2 for Navals and nomads if (t === 2) return type === "Naval" || type === "Nomadic" ? 30 : 0; // low penalty for land level 2 for Navals and nomads
if (t !== -1) return type === 'Naval' || type === 'Lake' ? 100 : 0; // penalty for mainland for navals if (t !== -1) return type === "Naval" || type === "Lake" ? 100 : 0; // penalty for mainland for navals
return 0; return 0;
} }
@ -449,11 +451,11 @@ window.BurgsAndStates = (function () {
for (const i of cells.i) { for (const i of cells.i) {
if (cells.h[i] < 20 || cells.burg[i]) continue; // do not overwrite burgs if (cells.h[i] < 20 || cells.burg[i]) continue; // do not overwrite burgs
if (cells.c[i].some((c) => burgs[cells.burg[c]].capital)) continue; // do not overwrite near capital if (cells.c[i].some(c => burgs[cells.burg[c]].capital)) continue; // do not overwrite near capital
const neibs = cells.c[i].filter((c) => cells.h[c] >= 20); const neibs = cells.c[i].filter(c => cells.h[c] >= 20);
const adversaries = neibs.filter((c) => cells.state[c] !== cells.state[i]); const adversaries = neibs.filter(c => cells.state[c] !== cells.state[i]);
if (adversaries.length < 2) continue; if (adversaries.length < 2) continue;
const buddies = neibs.filter((c) => cells.state[c] === cells.state[i]); const buddies = neibs.filter(c => cells.state[c] === cells.state[i]);
if (buddies.length > 2) continue; if (buddies.length > 2) continue;
if (adversaries.length <= buddies.length) continue; if (adversaries.length <= buddies.length) continue;
cells.state[i] = cells.state[adversaries[0]]; cells.state[i] = cells.state[adversaries[0]];
@ -501,7 +503,7 @@ window.BurgsAndStates = (function () {
const visualCenter = findCell(s.pole[0], s.pole[1]); const visualCenter = findCell(s.pole[0], s.pole[1]);
const start = cells.state[visualCenter] === s.i ? visualCenter : s.center; const start = cells.state[visualCenter] === s.i ? visualCenter : s.center;
const hull = getHull(start, s.i, s.cells / 10); const hull = getHull(start, s.i, s.cells / 10);
const points = [...hull].map((v) => pack.vertices.p[v]); const points = [...hull].map(v => pack.vertices.p[v]);
const delaunay = Delaunator.from(points); const delaunay = Delaunator.from(points);
const voronoi = new Voronoi(delaunay, points, points.length); const voronoi = new Voronoi(delaunay, points, points.length);
const chain = connectCenters(voronoi.vertices, s.pole[1]); const chain = connectCenters(voronoi.vertices, s.pole[1]);
@ -539,7 +541,7 @@ window.BurgsAndStates = (function () {
return used[findCell(p[0], p[1])]; return used[findCell(p[0], p[1])];
}); });
const pointsInside = d3.range(c.p.length).filter((i) => inside[i]); const pointsInside = d3.range(c.p.length).filter(i => inside[i]);
if (!pointsInside.length) return [0]; if (!pointsInside.length) return [0];
const h = c.p.length < 200 ? 0 : c.p.length < 600 ? 0.5 : 1; // power of horyzontality shift const h = c.p.length < 200 ? 0 : c.p.length < 600 ? 0.5 : 1; // power of horyzontality shift
const end = const end =
@ -598,14 +600,14 @@ window.BurgsAndStates = (function () {
if (!list) { if (!list) {
// remove all labels and textpaths // remove all labels and textpaths
g.selectAll('text').remove(); g.selectAll("text").remove();
t.selectAll("path[id*='stateLabel']").remove(); t.selectAll("path[id*='stateLabel']").remove();
} }
const example = g.append('text').attr('x', 0).attr('x', 0).text('Average'); const example = g.append("text").attr("x", 0).attr("x", 0).text("Average");
const letterLength = example.node().getComputedTextLength() / 7; // average length of 1 letter const letterLength = example.node().getComputedTextLength() / 7; // average length of 1 letter
paths.forEach((p) => { paths.forEach(p => {
const id = p[0]; const id = p[0];
const state = states[p[0]]; const state = states[p[0]];
const {name, fullName} = state; const {name, fullName} = state;
@ -736,7 +738,7 @@ window.BurgsAndStates = (function () {
} }
// convert neighbors Set object into array // convert neighbors Set object into array
states.forEach((s) => { states.forEach(s => {
if (!s.neighbors) return; if (!s.neighbors) return;
s.neighbors = Array.from(s.neighbors); s.neighbors = Array.from(s.neighbors);
}); });
@ -752,14 +754,14 @@ window.BurgsAndStates = (function () {
pack.states.forEach(s => { pack.states.forEach(s => {
if (!s.i || s.removed) return; if (!s.i || s.removed) return;
const neibs = s.neighbors; const neibs = s.neighbors;
s.color = colors.find((c) => neibs.every((n) => pack.states[n].color !== c)); s.color = colors.find(c => neibs.every(n => pack.states[n].color !== c));
if (!s.color) s.color = getRandomColor(); if (!s.color) s.color = getRandomColor();
colors.push(colors.shift()); colors.push(colors.shift());
}); });
// randomize each already used color a bit // randomize each already used color a bit
colors.forEach((c) => { colors.forEach(c => {
const sameColored = pack.states.filter((s) => s.color === c); const sameColored = pack.states.filter(s => s.color === c);
sameColored.forEach((s, d) => { sameColored.forEach((s, d) => {
if (!d) return; if (!d) return;
s.color = getMixedColor(s.color); s.color = getMixedColor(s.color);
@ -821,9 +823,9 @@ window.BurgsAndStates = (function () {
for (let f = 1; f < states.length; f++) { for (let f = 1; f < states.length; f++) {
if (states[f].removed) continue; if (states[f].removed) continue;
if (states[f].diplomacy.includes('Vassal')) { if (states[f].diplomacy.includes("Vassal")) {
// Vassals copy relations from their Suzerains // Vassals copy relations from their Suzerains
const suzerain = states[f].diplomacy.indexOf('Vassal'); const suzerain = states[f].diplomacy.indexOf("Vassal");
for (let i = 1; i < states.length; i++) { for (let i = 1; i < states.length; i++) {
if (i === f || i === suzerain) continue; if (i === f || i === suzerain) continue;
@ -831,7 +833,7 @@ window.BurgsAndStates = (function () {
if (states[suzerain].diplomacy[i] === "Suzerain") states[f].diplomacy[i] = "Ally"; if (states[suzerain].diplomacy[i] === "Suzerain") states[f].diplomacy[i] = "Ally";
for (let e = 1; e < states.length; e++) { for (let e = 1; e < states.length; e++) {
if (e === f || e === suzerain) continue; if (e === f || e === suzerain) continue;
if (states[e].diplomacy[suzerain] === 'Suzerain' || states[e].diplomacy[suzerain] === 'Vassal') continue; if (states[e].diplomacy[suzerain] === "Suzerain" || states[e].diplomacy[suzerain] === "Vassal") continue;
states[e].diplomacy[f] = states[e].diplomacy[suzerain]; states[e].diplomacy[f] = states[e].diplomacy[suzerain];
} }
} }
@ -841,8 +843,8 @@ window.BurgsAndStates = (function () {
for (let t = f + 1; t < states.length; t++) { for (let t = f + 1; t < states.length; t++) {
if (states[t].removed) continue; if (states[t].removed) continue;
if (states[t].diplomacy.includes('Vassal')) { if (states[t].diplomacy.includes("Vassal")) {
const suzerain = states[t].diplomacy.indexOf('Vassal'); const suzerain = states[t].diplomacy.indexOf("Vassal");
states[f].diplomacy[t] = states[f].diplomacy[suzerain]; states[f].diplomacy[t] = states[f].diplomacy[suzerain];
continue; continue;
} }
@ -880,9 +882,9 @@ window.BurgsAndStates = (function () {
for (let attacker = 1; attacker < states.length; attacker++) { for (let attacker = 1; attacker < states.length; attacker++) {
const ad = states[attacker].diplomacy; // attacker relations; const ad = states[attacker].diplomacy; // attacker relations;
if (states[attacker].removed) continue; if (states[attacker].removed) continue;
if (!ad.includes('Rival')) continue; // no rivals to attack if (!ad.includes("Rival")) continue; // no rivals to attack
if (ad.includes('Vassal')) continue; // not independent if (ad.includes("Vassal")) continue; // not independent
if (ad.includes('Enemy')) continue; // already at war if (ad.includes("Enemy")) continue; // already at war
// random independent rival // random independent rival
const defender = ra( const defender = ra(
@ -920,8 +922,8 @@ window.BurgsAndStates = (function () {
} }
}); });
ap = d3.sum(attackers.map((a) => states[a].area * states[a].expansionism)); // attackers joined power ap = d3.sum(attackers.map(a => states[a].area * states[a].expansionism)); // attackers joined power
dp = d3.sum(defenders.map((d) => states[d].area * states[d].expansionism)); // defender joined power dp = d3.sum(defenders.map(d => states[d].area * states[d].expansionism)); // defender joined power
// defender allies join // defender allies join
dd.forEach((r, d) => { dd.forEach((r, d) => {
@ -929,7 +931,7 @@ window.BurgsAndStates = (function () {
if (states[d].diplomacy[attacker] !== "Rival" && ap / dp > 2 * gauss(1.6, 0.8, 0, 10, 2)) { if (states[d].diplomacy[attacker] !== "Rival" && ap / dp > 2 * gauss(1.6, 0.8, 0, 10, 2)) {
const reason = states[d].diplomacy.includes("Enemy") ? "Being already at war," : `Frightened by ${an},`; const reason = states[d].diplomacy.includes("Enemy") ? "Being already at war," : `Frightened by ${an},`;
war.push(`${reason} ${states[d].name} severed the defense pact with ${dn}`); war.push(`${reason} ${states[d].name} severed the defense pact with ${dn}`);
dd[d] = states[d].diplomacy[defender] = 'Suspicion'; dd[d] = states[d].diplomacy[defender] = "Suspicion";
return; return;
} }
defenders.push(d); defenders.push(d);
@ -949,7 +951,7 @@ window.BurgsAndStates = (function () {
// attacker allies join if the defender is their rival or joined power > defenders power and defender is not an ally // attacker allies join if the defender is their rival or joined power > defenders power and defender is not an ally
ad.forEach((r, d) => { ad.forEach((r, d) => {
if (r !== 'Ally' || states[d].diplomacy.includes('Vassal') || defenders.includes(d)) return; if (r !== "Ally" || states[d].diplomacy.includes("Vassal") || defenders.includes(d)) return;
const name = states[d].name; const name = states[d].name;
if (states[d].diplomacy[defender] !== "Rival" && (P(0.2) || ap <= dp * 1.2)) { if (states[d].diplomacy[defender] !== "Rival" && (P(0.2) || ap <= dp * 1.2)) {
war.push(`${an}'s ally ${name} avoided entering the war`); war.push(`${an}'s ally ${name} avoided entering the war`);
@ -981,7 +983,7 @@ window.BurgsAndStates = (function () {
chronicle.push(war); // add a record to diplomatical history chronicle.push(war); // add a record to diplomatical history
} }
TIME && console.timeEnd('generateDiplomacy'); TIME && console.timeEnd("generateDiplomacy");
//console.table(states.map(s => s.diplomacy)); //console.table(states.map(s => s.diplomacy));
}; };
@ -1062,33 +1064,33 @@ window.BurgsAndStates = (function () {
if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals
} }
if (base === 16 && (form === 'Empire' || form === 'Kingdom')) return 'Sultanate'; // Turkic if (base === 16 && (form === "Empire" || form === "Kingdom")) return "Sultanate"; // Turkic
if (base === 5 && (form === 'Empire' || form === 'Kingdom')) return 'Tsardom'; // Ruthenian if (base === 5 && (form === "Empire" || form === "Kingdom")) return "Tsardom"; // Ruthenian
if ([16, 31].includes(base) && (form === 'Empire' || form === 'Kingdom')) return 'Khaganate'; // Turkic, Mongolian if ([16, 31].includes(base) && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic, Mongolian
if (base === 12 && (form === 'Kingdom' || form === 'Grand Duchy')) return 'Shogunate'; // Japanese if (base === 12 && (form === "Kingdom" || form === "Grand Duchy")) return "Shogunate"; // Japanese
if ([18, 17].includes(base) && form === 'Empire') return 'Caliphate'; // Arabic, Berber if ([18, 17].includes(base) && form === "Empire") return "Caliphate"; // Arabic, Berber
if (base === 18 && (form === 'Grand Duchy' || form === 'Duchy')) return 'Emirate'; // Arabic if (base === 18 && (form === "Grand Duchy" || form === "Duchy")) return "Emirate"; // Arabic
if (base === 7 && (form === 'Grand Duchy' || form === 'Duchy')) return 'Despotate'; // Greek if (base === 7 && (form === "Grand Duchy" || form === "Duchy")) return "Despotate"; // Greek
if (base === 31 && (form === 'Grand Duchy' || form === 'Duchy')) return 'Ulus'; // Mongolian if (base === 31 && (form === "Grand Duchy" || form === "Duchy")) return "Ulus"; // Mongolian
if (base === 16 && (form === 'Grand Duchy' || form === 'Duchy')) return 'Horde'; // Turkic if (base === 16 && (form === "Grand Duchy" || form === "Duchy")) return "Horde"; // Turkic
if (base === 24 && (form === 'Grand Duchy' || form === 'Duchy')) return 'Satrapy'; // Iranian if (base === 24 && (form === "Grand Duchy" || form === "Duchy")) return "Satrapy"; // Iranian
return form; return form;
} }
if (s.form === 'Republic') { if (s.form === "Republic") {
// Default name is from weighted array, special case for small states with only 1 burg // Default name is from weighted array, special case for small states with only 1 burg
if (tier < 2 && s.burgs === 1) { if (tier < 2 && s.burgs === 1) {
if (trimVowels(s.name) === trimVowels(pack.burgs[s.capital].name)) { if (trimVowels(s.name) === trimVowels(pack.burgs[s.capital].name)) {
s.name = pack.burgs[s.capital].name; s.name = pack.burgs[s.capital].name;
return 'Free City'; return "Free City";
} }
if (P(0.3)) return "City-state"; if (P(0.3)) return "City-state";
} }
return rw(republic); return rw(republic);
} }
if (s.form === 'Union') return rw(union); if (s.form === "Union") return rw(union);
if (s.form === 'Anarchy') return rw(anarchy); if (s.form === "Anarchy") return rw(anarchy);
if (s.form === "Theocracy") { if (s.form === "Theocracy") {
// European // European
@ -1211,7 +1213,7 @@ window.BurgsAndStates = (function () {
const name = nameByBurg ? stateBurgs[i].name : Names.getState(Names.getCultureShort(c), c); const name = nameByBurg ? stateBurgs[i].name : Names.getState(Names.getCultureShort(c), c);
const formName = rw(form); const formName = rw(form);
form[formName] += 10; form[formName] += 10;
const fullName = name + ' ' + formName; const fullName = name + " " + formName;
const color = getMixedColor(s.color); const color = getMixedColor(s.color);
const kinship = nameByBurg ? 0.8 : 0.4; const kinship = nameByBurg ? 0.8 : 0.4;
const type = getType(center, burg.port); const type = getType(center, burg.port);
@ -1256,10 +1258,10 @@ window.BurgsAndStates = (function () {
// justify provinces shapes a bit // justify provinces shapes a bit
for (const i of cells.i) { for (const i of cells.i) {
if (cells.burg[i]) continue; // do not overwrite burgs if (cells.burg[i]) continue; // do not overwrite burgs
const neibs = cells.c[i].filter((c) => cells.state[c] === cells.state[i]).map((c) => cells.province[c]); const neibs = cells.c[i].filter(c => cells.state[c] === cells.state[i]).map(c => cells.province[c]);
const adversaries = neibs.filter((c) => c !== cells.province[i]); const adversaries = neibs.filter(c => c !== cells.province[i]);
if (adversaries.length < 2) continue; if (adversaries.length < 2) continue;
const buddies = neibs.filter((c) => c === cells.province[i]).length; const buddies = neibs.filter(c => c === cells.province[i]).length;
if (buddies.length > 2) continue; if (buddies.length > 2) continue;
const competitors = adversaries.map(p => adversaries.reduce((s, v) => (v === p ? s + 1 : s), 0)); const competitors = adversaries.map(p => adversaries.reduce((s, v) => (v === p ? s + 1 : s), 0));
const max = d3.max(competitors); const max = d3.max(competitors);
@ -1268,8 +1270,8 @@ window.BurgsAndStates = (function () {
} }
// add "wild" provinces if some cells don't have a province assigned // add "wild" provinces if some cells don't have a province assigned
const noProvince = Array.from(cells.i).filter((i) => cells.state[i] && !cells.province[i]); // cells without province assigned const noProvince = Array.from(cells.i).filter(i => cells.state[i] && !cells.province[i]); // cells without province assigned
states.forEach((s) => { states.forEach(s => {
if (!s.provinces.length) return; if (!s.provinces.length) return;
const coreProvinceNames = s.provinces.map(p => provinces[p]?.name); const coreProvinceNames = s.provinces.map(p => provinces[p]?.name);
@ -1286,7 +1288,7 @@ window.BurgsAndStates = (function () {
while (stateNoProvince.length) { while (stateNoProvince.length) {
// add new province // add new province
const province = provinces.length; const province = provinces.length;
const burgCell = stateNoProvince.find((i) => cells.burg[i]); const burgCell = stateNoProvince.find(i => cells.burg[i]);
const center = burgCell ? burgCell : stateNoProvince[0]; const center = burgCell ? burgCell : stateNoProvince[0];
const burg = burgCell ? cells.burg[burgCell] : 0; const burg = burgCell ? cells.burg[burgCell] : 0;
cells.province[center] = province; cells.province[center] = province;
@ -1360,7 +1362,7 @@ window.BurgsAndStates = (function () {
while (queue.length) { while (queue.length) {
const current = queue.pop(); const current = queue.pop();
if (current === to) return true; // way is found if (current === to) return true; // way is found
cells.c[current].forEach((c) => { cells.c[current].forEach(c => {
if (used[c] || cells.h[c] < 20 || cells.state[c] !== state) return; if (used[c] || cells.h[c] < 20 || cells.state[c] !== state) return;
queue.push(c); queue.push(c);
used[c] = 1; used[c] = 1;
@ -1370,7 +1372,7 @@ window.BurgsAndStates = (function () {
} }
// re-check // re-check
stateNoProvince = noProvince.filter((i) => cells.state[i] === s.i && !cells.province[i]); stateNoProvince = noProvince.filter(i => cells.state[i] === s.i && !cells.province[i]);
} }
}); });

View file

@ -149,6 +149,7 @@ function getPackCellsData() {
religion: Array.from(pack.cells.religion), religion: Array.from(pack.cells.religion),
province: Array.from(pack.cells.province) province: Array.from(pack.cells.province)
}; };
const cellObjArr = []; const cellObjArr = [];
{ {
cellConverted.i.forEach(value => { cellConverted.i.forEach(value => {
@ -222,14 +223,3 @@ function getPackVerticesData() {
} }
return verticesArray; return verticesArray;
} }
function getGridCellsDataJson() {
TIME && console.time("getGridCellsDataJson");
const info = getMapInfo();
const gridCells = getGridCellsData()
const exportData = {info,gridCells};
TIME && console.log("getGridCellsDataJson");
return JSON.stringify(exportData);
}

File diff suppressed because it is too large Load diff

View file

@ -41,6 +41,7 @@ window.Submap = (function () {
// create new grid // create new grid
applyMapSize(); applyMapSize();
grid = generateGrid(); grid = generateGrid();
drawScaleBar(scale); drawScaleBar(scale);
const resampler = (points, qtree, f) => { const resampler = (points, qtree, f) => {