diff --git a/.gitignore b/.gitignore
index 8cd42a0d..07e6e472 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1 @@
-.bat
-.vscode
-.idea
-.idea/Fantasy-Map-Generator.iml
+/node_modules
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 6afdcc7d..00000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "version": "0.1.0",
- "configurations": [
- {
- "name": "Debug",
- "type": "chrome",
- "request": "launch",
- "file": "${workspaceFolder}/index.html"
- }
- ]
-}
\ No newline at end of file
diff --git a/Readme.txt b/Readme.txt
deleted file mode 100644
index 9d270a7c..00000000
--- a/Readme.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-Azgaar's Fantasy Map Generator
-
-Developed by Azgaar (azgaar.fmg@yandex.com) and contributors
-
-Minsk, 2017-2021. MIT License
-
-https://github.com/Azgaar/Fantasy-Map-Generator
-
-To run the tool unzip ALL files and open index.html in browser
\ No newline at end of file
diff --git a/index.html b/index.html
index 01717609..c7887d63 100644
--- a/index.html
+++ b/index.html
@@ -3718,8 +3718,8 @@
-
+
+
Show all labels
@@ -6384,32 +6384,26 @@
d="m 48.8842,16.8699 c -1.785997,0.666059 -3.779594,1.246295 -4.301192,3.452184 -0.540223,2.017352 -3.325715,0.423824 -4.4494,2.229627 -2.494158,-0.673487 -2.019728,1.842576 -2.548911,3.383955 -1.030703,1.62935 -1.137361,3.670141 -1.837647,5.502122 -1.455888,1.8507 -2.889787,3.789023 -3.24835,6.150212 -0.642322,1.376996 -2.934697,4.232379 -0.743197,5.002756 3.276226,0.386491 6.865778,0.297294 9.668135,-1.671956 1.992411,-0.789487 3.045587,-2.751047 4.759962,-3.9329 1.189858,-0.552573 2.437218,-0.990001 3.777113,-0.811 1.907845,-0.01586 3.785152,-0.37634 5.672187,0.08659 1.978298,0.05321 -0.985275,-1.72622 0.908237,-2.032705 1.474101,-0.686901 1.911031,0.604732 2.789914,1.139442 0.72917,-0.07521 2.250626,0.907421 2.007947,-0.440847 0.758787,-1.773464 1.770613,-4.072587 4.142983,-2.926051 2.333406,0.19823 4.47649,-1.394758 4.631923,-3.803654 0.362029,-1.471587 0.276981,-3.115583 2.276446,-2.98201 1.962019,-0.748148 2.294241,-3.385233 1.73135,-5.017763 -1.101666,-1.371396 0.2507,-2.912999 1.327975,-3.832219 C 76.753843,15.865967 76.05046,14.539717 75.8076,13.5526 75.093304,12.114215 75.790908,10.071743 73.619081,9.8482516 73.01701,8.9737297 73.441083,9.1741347 73.177475,8.0910547 73.369945,6.7516759 71.308021,6.5289859 70.544363,5.961525 69.388061,5.7732631 68.393705,5.6084929 67.935746,4.3663653 66.967743,3.8236661 65.71194,4.1429299 64.948956,3.4639047 63.291625,3.3657328 61.428814,3.5574961 60.282876,4.8581076 58.121173,5.7094079 58.85032,7.8874864 58.599915,9.5497793 57.986956,10.324235 56.222784,10.545705 57.2655,11.7578 c -1.231347,1.555102 -2.786541,2.706743 -4.5422,3.6878 -1.39291,0.193194 -2.512881,1.045804 -3.8391,1.4243 z"
/>
@@ -7132,32 +7126,26 @@
d="m 48.8842,16.8699 c -1.785997,0.666059 -3.779594,1.246295 -4.301192,3.452184 -0.540223,2.017352 -3.325715,0.423824 -4.4494,2.229627 -2.494158,-0.673487 -2.019728,1.842576 -2.548911,3.383955 -1.030703,1.62935 -1.137361,3.670141 -1.837647,5.502122 -1.455888,1.8507 -2.889787,3.789023 -3.24835,6.150212 -0.642322,1.376996 -2.934697,4.232379 -0.743197,5.002756 3.276226,0.386491 6.865778,0.297294 9.668135,-1.671956 1.992411,-0.789487 3.045587,-2.751047 4.759962,-3.9329 1.189858,-0.552573 2.437218,-0.990001 3.777113,-0.811 1.907845,-0.01586 3.785152,-0.37634 5.672187,0.08659 1.978298,0.05321 -0.985275,-1.72622 0.908237,-2.032705 1.474101,-0.686901 1.911031,0.604732 2.789914,1.139442 0.72917,-0.07521 2.250626,0.907421 2.007947,-0.440847 0.758787,-1.773464 1.770613,-4.072587 4.142983,-2.926051 2.333406,0.19823 4.47649,-1.394758 4.631923,-3.803654 0.362029,-1.471587 0.276981,-3.115583 2.276446,-2.98201 1.962019,-0.748148 2.294241,-3.385233 1.73135,-5.017763 -1.101666,-1.371396 0.2507,-2.912999 1.327975,-3.832219 C 76.753843,15.865967 76.05046,14.539717 75.8076,13.5526 75.093304,12.114215 75.790908,10.071743 73.619081,9.8482516 73.01701,8.9737297 73.441083,9.1741347 73.177475,8.0910547 73.369945,6.7516759 71.308021,6.5289859 70.544363,5.961525 69.388061,5.7732631 68.393705,5.6084929 67.935746,4.3663653 66.967743,3.8236661 65.71194,4.1429299 64.948956,3.4639047 63.291625,3.3657328 61.428814,3.5574961 60.282876,4.8581076 58.121173,5.7094079 58.85032,7.8874864 58.599915,9.5497793 57.986956,10.324235 56.222784,10.545705 57.2655,11.7578 c -1.231347,1.555102 -2.786541,2.706743 -4.5422,3.6878 -1.39291,0.193194 -2.512881,1.045804 -3.8391,1.4243 z"
/>
@@ -7783,7 +7771,6 @@
-
@@ -7795,33 +7782,39 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
-
-
-
+
+
+
+
+
+
-
+
@@ -7841,7 +7834,7 @@
-
+
diff --git a/modules/activeZooming.js b/modules/activeZooming.js
new file mode 100644
index 00000000..20097bbb
--- /dev/null
+++ b/modules/activeZooming.js
@@ -0,0 +1,92 @@
+window.handleZoom = function (isScaleChanged, isPositionChanged) {
+ viewbox.attr("transform", `translate(${viewX} ${viewY}) scale(${scale})`);
+
+ if (isPositionChanged) drawCoordinates();
+
+ if (isScaleChanged) {
+ invokeActiveZooming();
+ drawScaleBar(scale);
+ }
+
+ // zoom image converter overlay
+ if (customization === 1) {
+ const canvas = document.getElementById("canvas");
+ if (!canvas || canvas.style.opacity === "0") return;
+
+ const img = document.getElementById("imageToConvert");
+ if (!img) return;
+
+ const ctx = canvas.getContext("2d");
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.setTransform(scale, 0, 0, scale, viewX, viewY);
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
+ }
+};
+
+// active zooming feature
+export function invokeActiveZooming() {
+ if (coastline.select("#sea_island").size() && +coastline.select("#sea_island").attr("auto-filter")) {
+ // toggle shade/blur filter for coatline on zoom
+ const filter = scale > 1.5 && scale <= 2.6 ? null : scale > 2.6 ? "url(#blurFilter)" : "url(#dropShadow)";
+ coastline.select("#sea_island").attr("filter", filter);
+ }
+
+ // rescale labels on zoom
+ if (labels.style("display") !== "none") {
+ labels.selectAll("g").each(function () {
+ if (this.id === "burgLabels") return;
+ const desired = +this.dataset.size;
+ const relative = Math.max(rn((desired + desired / scale) / 2, 2), 1);
+ if (rescaleLabels.checked) this.setAttribute("font-size", relative);
+
+ const hidden = hideLabels.checked && (relative * scale < 6 || relative * scale > 60);
+ if (hidden) this.classList.add("hidden");
+ else this.classList.remove("hidden");
+ });
+ }
+
+ // rescale emblems on zoom
+ if (emblems.style("display") !== "none") {
+ emblems.selectAll("g").each(function () {
+ const size = this.getAttribute("font-size") * scale;
+ const hidden = hideEmblems.checked && (size < 25 || size > 300);
+ if (hidden) this.classList.add("hidden");
+ else this.classList.remove("hidden");
+ if (!hidden && window.COArenderer && this.children.length && !this.children[0].getAttribute("href"))
+ renderGroupCOAs(this);
+ });
+ }
+
+ // turn off ocean pattern if scale is big (improves performance)
+ oceanPattern
+ .select("rect")
+ .attr("fill", scale > 10 ? "#fff" : "url(#oceanic)")
+ .attr("opacity", scale > 10 ? 0.2 : null);
+
+ // change states halo width
+ if (!customization) {
+ const desired = +statesHalo.attr("data-width");
+ const haloSize = rn(desired / scale ** 0.8, 2);
+ statesHalo.attr("stroke-width", haloSize).style("display", haloSize > 0.1 ? "block" : "none");
+ }
+
+ // rescale map markers
+ +markers.attr("rescale") &&
+ pack.markers?.forEach(marker => {
+ const {i, x, y, size = 30, hidden} = marker;
+ const el = !hidden && document.getElementById(`marker${i}`);
+ if (!el) return;
+
+ const zoomedSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
+ el.setAttribute("width", zoomedSize);
+ el.setAttribute("height", zoomedSize);
+ el.setAttribute("x", rn(x - zoomedSize / 2, 1));
+ el.setAttribute("y", rn(y - zoomedSize, 1));
+ });
+
+ // rescale rulers to have always the same size
+ if (ruler.style("display") !== "none") {
+ const size = rn((10 / scale ** 0.3) * 2, 2);
+ ruler.selectAll("text").attr("font-size", size);
+ }
+}
diff --git a/modules/biomes.js b/modules/biomes.js
new file mode 100644
index 00000000..045e0310
--- /dev/null
+++ b/modules/biomes.js
@@ -0,0 +1,76 @@
+window.Biomes = (function () {
+ const getDefault = () => {
+ const name = [
+ "Marine",
+ "Hot desert",
+ "Cold desert",
+ "Savanna",
+ "Grassland",
+ "Tropical seasonal forest",
+ "Temperate deciduous forest",
+ "Tropical rainforest",
+ "Temperate rainforest",
+ "Taiga",
+ "Tundra",
+ "Glacier",
+ "Wetland"
+ ];
+
+ const color = [
+ "#466eab",
+ "#fbe79f",
+ "#b5b887",
+ "#d2d082",
+ "#c8d68f",
+ "#b6d95d",
+ "#29bc56",
+ "#7dcb35",
+ "#409c43",
+ "#4b6b32",
+ "#96784b",
+ "#d5e7eb",
+ "#0b9131"
+ ];
+ const habitability = [0, 4, 10, 22, 30, 50, 100, 80, 90, 12, 4, 0, 12];
+ const iconsDensity = [0, 3, 2, 120, 120, 120, 120, 150, 150, 100, 5, 0, 150];
+ const icons = [
+ {},
+ {dune: 3, cactus: 6, deadTree: 1},
+ {dune: 9, deadTree: 1},
+ {acacia: 1, grass: 9},
+ {grass: 1},
+ {acacia: 8, palm: 1},
+ {deciduous: 1},
+ {acacia: 5, palm: 3, deciduous: 1, swamp: 1},
+ {deciduous: 6, swamp: 1},
+ {conifer: 1},
+ {grass: 1},
+ {},
+ {swamp: 1}
+ ];
+ const cost = [10, 200, 150, 60, 50, 70, 70, 80, 90, 200, 1000, 5000, 150]; // biome movement cost
+ const biomesMartix = [
+ // hot ↔ cold [>19°C; <-4°C]; dry ↕ wet
+ new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10]),
+ new Uint8Array([3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 10, 10, 10]),
+ new Uint8Array([5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 9, 9, 9, 9, 10, 10, 10]),
+ new Uint8Array([5, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10]),
+ new Uint8Array([7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10, 10])
+ ];
+
+ // parse icons weighted array into a simple array
+ for (let i = 0; i < icons.length; i++) {
+ const parsed = [];
+ for (const icon in icons[i]) {
+ for (let j = 0; j < icons[i][icon]; j++) {
+ parsed.push(icon);
+ }
+ }
+ icons[i] = parsed;
+ }
+
+ return {i: d3.range(0, name.length), name, color, biomesMartix, habitability, iconsDensity, icons, cost};
+ };
+
+ return {getDefault};
+})();
diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js
index e9f942e5..04ca2648 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -1,4 +1,6 @@
-"use strict";
+import {TIME} from "/src/config/logging";
+import {findCell} from "/src/utils/graphUtils";
+import {layerIsOn} from "./ui/layers";
window.BurgsAndStates = (function () {
const generate = function () {
diff --git a/modules/coa-generator.js b/modules/coa-generator.js
index 9491cc0d..56deb52b 100644
--- a/modules/coa-generator.js
+++ b/modules/coa-generator.js
@@ -1,5 +1,3 @@
-"use strict";
-
window.COA = (function () {
const tinctures = {
field: {metals: 3, colours: 4, stains: +P(0.03), patterns: 1},
@@ -305,7 +303,19 @@ window.COA = (function () {
Highland: {tower: 1, raven: 1, wolfHeadErased: 1, wolfPassant: 1, goat: 1, axe: 1},
River: {tower: 1, garb: 1, rake: 1, boat: 1, pike: 2, bullHeadCaboshed: 1, apple: 1, plough: 1},
Lake: {cancer: 2, escallop: 1, pike: 2, heron: 1, boat: 1, boat2: 2},
- Nomadic: {pot: 1, buckle: 1, wheel: 2, sabre: 2, sabresCrossed: 1, bow: 2, arrow: 1, horseRampant: 1, horseSalient: 1, crescent: 1, camel: 3},
+ Nomadic: {
+ pot: 1,
+ buckle: 1,
+ wheel: 2,
+ sabre: 2,
+ sabresCrossed: 1,
+ bow: 2,
+ arrow: 1,
+ horseRampant: 1,
+ horseSalient: 1,
+ crescent: 1,
+ camel: 3
+ },
Hunting: {
bugleHorn: 2,
bugleHorn2: 1,
@@ -322,7 +332,19 @@ window.COA = (function () {
// selection based on type
City: {key: 3, bell: 2, lute: 1, tower: 1, castle: 1, mallet: 1, cannon: 1, anvil: 1},
Capital: {crown: 2, orb: 1, lute: 1, castle: 3, tower: 1, crown2: 2},
- Сathedra: {chalice: 1, orb: 1, crosier: 2, lamb: 1, monk: 2, angel: 3, crossLatin: 2, crossPatriarchal: 1, crossOrthodox: 1, crossCalvary: 1, agnusDei: 3},
+ Сathedra: {
+ chalice: 1,
+ orb: 1,
+ crosier: 2,
+ lamb: 1,
+ monk: 2,
+ angel: 3,
+ crossLatin: 2,
+ crossPatriarchal: 1,
+ crossOrthodox: 1,
+ crossCalvary: 1,
+ agnusDei: 3
+ },
// specific cases
natural: {fountain: "azure", garb: "or", raven: "sable"}, // charges to mainly use predefined colours
sinister: [
@@ -508,7 +530,22 @@ window.COA = (function () {
},
// charges
inescutcheon: {e: 4, jln: 1},
- mascle: {e: 15, abcdefgzi: 3, beh: 3, bdefh: 4, acegi: 1, kn: 3, joe: 2, abc: 3, jlh: 8, jleh: 1, df: 3, abcpqh: 4, pqe: 3, eknpq: 3},
+ mascle: {
+ e: 15,
+ abcdefgzi: 3,
+ beh: 3,
+ bdefh: 4,
+ acegi: 1,
+ kn: 3,
+ joe: 2,
+ abc: 3,
+ jlh: 8,
+ jleh: 1,
+ df: 3,
+ abcpqh: 4,
+ pqe: 3,
+ eknpq: 3
+ },
lionRampant: {e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1},
lionPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
wolfPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
@@ -681,18 +718,41 @@ window.COA = (function () {
const coa = {t1};
let charge = P(usedPattern ? 0.5 : 0.93) ? true : false; // 80% for charge
- const linedOrdinary = (charge && P(0.3)) || P(0.5) ? (parent?.ordinaries && P(kinship) ? parent.ordinaries[0].ordinary : rw(ordinaries.lined)) : null;
+ const linedOrdinary =
+ (charge && P(0.3)) || P(0.5)
+ ? parent?.ordinaries && P(kinship)
+ ? parent.ordinaries[0].ordinary
+ : rw(ordinaries.lined)
+ : null;
const ordinary = (!charge && P(0.65)) || P(0.3) ? (linedOrdinary ? linedOrdinary : rw(ordinaries.straight)) : null; // 36% for ordinary
const rareDivided = ["chief", "terrace", "chevron", "quarter", "flaunches"].includes(ordinary);
- const divisioned = rareDivided ? P(0.03) : charge && ordinary ? P(0.03) : charge ? P(0.3) : ordinary ? P(0.7) : P(0.995); // 33% for division
- const division = divisioned ? (parent?.division && P(kinship - 0.1) ? parent.division.division : rw(divisions.variants)) : null;
+ const divisioned = rareDivided
+ ? P(0.03)
+ : charge && ordinary
+ ? P(0.03)
+ : charge
+ ? P(0.3)
+ : ordinary
+ ? P(0.7)
+ : P(0.995); // 33% for division
+ const division = divisioned
+ ? parent?.division && P(kinship - 0.1)
+ ? parent.division.division
+ : rw(divisions.variants)
+ : null;
if (charge)
- charge = parent?.charges && P(kinship - 0.1) ? parent.charges[0].charge : type && type !== "Generic" && P(0.2) ? rw(charges[type]) : selectCharge();
+ charge =
+ parent?.charges && P(kinship - 0.1)
+ ? parent.charges[0].charge
+ : type && type !== "Generic" && P(0.2)
+ ? rw(charges[type])
+ : selectCharge();
if (division) {
const t = getTincture("division", usedTinctures, P(0.98) ? coa.t1 : null);
coa.division = {division, t};
- if (divisions[division]) coa.division.line = usedPattern || (ordinary && P(0.7)) ? "straight" : rw(divisions[division]);
+ if (divisions[division])
+ coa.division.line = usedPattern || (ordinary && P(0.7)) ? "straight" : rw(divisions[division]);
}
if (ordinary) {
@@ -768,7 +828,14 @@ window.COA = (function () {
// counterchanged, 40%
else if (["perPale", "perFess", "perBend", "perBendSinister"].includes(division) && P(0.8)) {
// place 2 charges in division standard positions
- const [p1, p2] = division === "perPale" ? ["p", "q"] : division === "perFess" ? ["k", "n"] : division === "perBend" ? ["l", "m"] : ["j", "o"]; // perBendSinister
+ const [p1, p2] =
+ division === "perPale"
+ ? ["p", "q"]
+ : division === "perFess"
+ ? ["k", "n"]
+ : division === "perBend"
+ ? ["l", "m"]
+ : ["j", "o"]; // perBendSinister
coa.charges[0].p = p1;
const charge = selectCharge(charges.single);
diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js
index b1072c22..8eff92a4 100644
--- a/modules/cultures-generator.js
+++ b/modules/cultures-generator.js
@@ -1,4 +1,4 @@
-"use strict";
+import {TIME} from "/src/config/logging";
window.Cultures = (function () {
let cells;
diff --git a/modules/define-globals.js b/modules/define-globals.js
new file mode 100644
index 00000000..4b7ab2b2
--- /dev/null
+++ b/modules/define-globals.js
@@ -0,0 +1,33 @@
+"use strict";
+// define global vabiable, each to be refactored and de-globalized 1-by-1
+
+let grid = {}; // initial graph based on jittered square grid and data
+let pack = {}; // packed graph and data
+let seed;
+let mapId;
+let mapHistory = [];
+let elSelected;
+
+let notes = [];
+let customization = 0;
+
+let rulers;
+let biomesData;
+let nameBases;
+
+let color;
+let lineGen;
+
+// defined in main.js
+let graphWidth;
+let graphHeight;
+let svgWidth;
+let svgHeight;
+
+let options = {};
+let mapCoordinates = {};
+let populationRate;
+let distanceScale;
+let urbanization;
+let urbanDensity;
+let statesNeutral;
diff --git a/modules/define-svg.js b/modules/define-svg.js
new file mode 100644
index 00000000..5f59577e
--- /dev/null
+++ b/modules/define-svg.js
@@ -0,0 +1,177 @@
+"use strict";
+// temporary define svg elements as globals
+
+let svg,
+ defs,
+ viewbox,
+ scaleBar,
+ legend,
+ ocean,
+ oceanLayers,
+ oceanPattern,
+ lakes,
+ landmass,
+ texture,
+ terrs,
+ biomes,
+ cells,
+ gridOverlay,
+ coordinates,
+ compass,
+ rivers,
+ terrain,
+ relig,
+ cults,
+ regions,
+ statesBody,
+ statesHalo,
+ provs,
+ zones,
+ borders,
+ stateBorders,
+ provinceBorders,
+ routes,
+ roads,
+ trails,
+ searoutes,
+ temperature,
+ coastline,
+ ice,
+ prec,
+ population,
+ emblems,
+ labels,
+ icons,
+ burgLabels,
+ burgIcons,
+ anchors,
+ armies,
+ markers,
+ fogging,
+ ruler,
+ debug;
+
+function defineSvg(width, height) {
+ // append svg layers (in default order)
+ svg = d3.select("#map");
+ defs = svg.select("#deftemp");
+ viewbox = svg.select("#viewbox");
+ scaleBar = svg.select("#scaleBar");
+ legend = svg.append("g").attr("id", "legend");
+ ocean = viewbox.append("g").attr("id", "ocean");
+ oceanLayers = ocean.append("g").attr("id", "oceanLayers");
+ oceanPattern = ocean.append("g").attr("id", "oceanPattern");
+ lakes = viewbox.append("g").attr("id", "lakes");
+ landmass = viewbox.append("g").attr("id", "landmass");
+ texture = viewbox.append("g").attr("id", "texture");
+ terrs = viewbox.append("g").attr("id", "terrs");
+ biomes = viewbox.append("g").attr("id", "biomes");
+ cells = viewbox.append("g").attr("id", "cells");
+ gridOverlay = viewbox.append("g").attr("id", "gridOverlay");
+ coordinates = viewbox.append("g").attr("id", "coordinates");
+ compass = viewbox.append("g").attr("id", "compass");
+ rivers = viewbox.append("g").attr("id", "rivers");
+ terrain = viewbox.append("g").attr("id", "terrain");
+ relig = viewbox.append("g").attr("id", "relig");
+ cults = viewbox.append("g").attr("id", "cults");
+ regions = viewbox.append("g").attr("id", "regions");
+ statesBody = regions.append("g").attr("id", "statesBody");
+ statesHalo = regions.append("g").attr("id", "statesHalo");
+ provs = viewbox.append("g").attr("id", "provs");
+ zones = viewbox.append("g").attr("id", "zones").style("display", "none");
+ borders = viewbox.append("g").attr("id", "borders");
+ stateBorders = borders.append("g").attr("id", "stateBorders");
+ provinceBorders = borders.append("g").attr("id", "provinceBorders");
+ routes = viewbox.append("g").attr("id", "routes");
+ roads = routes.append("g").attr("id", "roads");
+ trails = routes.append("g").attr("id", "trails");
+ searoutes = routes.append("g").attr("id", "searoutes");
+ temperature = viewbox.append("g").attr("id", "temperature");
+ coastline = viewbox.append("g").attr("id", "coastline");
+ ice = viewbox.append("g").attr("id", "ice").style("display", "none");
+ prec = viewbox.append("g").attr("id", "prec").style("display", "none");
+ population = viewbox.append("g").attr("id", "population");
+ emblems = viewbox.append("g").attr("id", "emblems").style("display", "none");
+ labels = viewbox.append("g").attr("id", "labels");
+ icons = viewbox.append("g").attr("id", "icons");
+ burgIcons = icons.append("g").attr("id", "burgIcons");
+ anchors = icons.append("g").attr("id", "anchors");
+ armies = viewbox.append("g").attr("id", "armies").style("display", "none");
+ markers = viewbox.append("g").attr("id", "markers");
+ fogging = viewbox
+ .append("g")
+ .attr("id", "fogging-cont")
+ .attr("mask", "url(#fog)")
+ .append("g")
+ .attr("id", "fogging")
+ .style("display", "none");
+ ruler = viewbox.append("g").attr("id", "ruler").style("display", "none");
+ debug = viewbox.append("g").attr("id", "debug");
+
+ // lake and coast groups
+ lakes.append("g").attr("id", "freshwater");
+ lakes.append("g").attr("id", "salt");
+ lakes.append("g").attr("id", "sinkhole");
+ lakes.append("g").attr("id", "frozen");
+ lakes.append("g").attr("id", "lava");
+ lakes.append("g").attr("id", "dry");
+ coastline.append("g").attr("id", "sea_island");
+ coastline.append("g").attr("id", "lake_island");
+
+ labels.append("g").attr("id", "states");
+ labels.append("g").attr("id", "addedLabels");
+
+ burgLabels = labels.append("g").attr("id", "burgLabels");
+ burgIcons.append("g").attr("id", "cities");
+ burgLabels.append("g").attr("id", "cities");
+ anchors.append("g").attr("id", "cities");
+
+ burgIcons.append("g").attr("id", "towns");
+ burgLabels.append("g").attr("id", "towns");
+ anchors.append("g").attr("id", "towns");
+
+ // population groups
+ population.append("g").attr("id", "rural");
+ population.append("g").attr("id", "urban");
+
+ // emblem groups
+ emblems.append("g").attr("id", "burgEmblems");
+ emblems.append("g").attr("id", "provinceEmblems");
+ emblems.append("g").attr("id", "stateEmblems");
+
+ // fogging
+ fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
+ fogging
+ .append("rect")
+ .attr("x", 0)
+ .attr("y", 0)
+ .attr("width", "100%")
+ .attr("height", "100%")
+ .attr("fill", "#e8f0f6")
+ .attr("filter", "url(#splotch)");
+
+ // assign events separately as not a viewbox child
+ scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits());
+
+ legend
+ .on("mousemove", () => tip("Drag to change the position. Click to hide the legend"))
+ .on("click", () => clearLegend());
+
+ landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
+
+ oceanPattern
+ .append("rect")
+ .attr("fill", "url(#oceanic)")
+ .attr("x", 0)
+ .attr("y", 0)
+ .attr("width", width)
+ .attr("height", height);
+
+ oceanLayers
+ .append("rect")
+ .attr("id", "oceanBase")
+ .attr("x", 0)
+ .attr("y", 0)
+ .attr("width", width)
+ .attr("height", height);
+}
diff --git a/modules/dynamic/editors/states-editor.js b/modules/dynamic/editors/states-editor.js
index b16246fd..5299be21 100644
--- a/modules/dynamic/editors/states-editor.js
+++ b/modules/dynamic/editors/states-editor.js
@@ -422,8 +422,8 @@ function editStateName(state) {
position: {my: "center", at: "center", of: "svg"}
});
- if (modules.editStateName) return;
- modules.editStateName = true;
+ if (fmg.modules.editStateName) return;
+ fmg.modules.editStateName = true;
// add listeners
byId("stateNameEditorShortCulture").on("click", regenerateShortNameCuture);
diff --git a/modules/dynamic/heightmap-selection.js b/modules/dynamic/heightmap-selection.js
index 9b78d118..0f44f74f 100644
--- a/modules/dynamic/heightmap-selection.js
+++ b/modules/dynamic/heightmap-selection.js
@@ -262,7 +262,7 @@ function getName(id) {
}
function getGraph(currentGraph) {
- const newGraph = shouldRegenerateGrid(currentGraph) ? generateGrid() : deepCopy(currentGraph);
+ const newGraph = shouldRegenerateGrid(currentGraph) ? generateGrid() : structuredClone(currentGraph);
delete newGraph.cells.h;
return newGraph;
}
diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js
index 66696953..1e8c2567 100644
--- a/modules/heightmap-generator.js
+++ b/modules/heightmap-generator.js
@@ -1,4 +1,6 @@
-"use strict";
+import {TIME} from "/src/config/logging";
+import {createTypedArray} from "/src/utils";
+import {findGridCell} from "/src/utils/graphUtils";
window.HeightmapGenerator = (function () {
let grid = null;
@@ -388,8 +390,12 @@ window.HeightmapGenerator = (function () {
const vert = direction === "vertical";
const startX = vert ? Math.floor(Math.random() * graphWidth * 0.4 + graphWidth * 0.3) : 5;
const startY = vert ? 5 : Math.floor(Math.random() * graphHeight * 0.4 + graphHeight * 0.3);
- const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5;
- const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
+ const endX = vert
+ ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2)
+ : graphWidth - 5;
+ const endY = vert
+ ? graphHeight - 5
+ : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
const start = findGridCell(startX, startY, grid);
const end = findGridCell(endX, endY, grid);
diff --git a/modules/lakes.js b/modules/lakes.js
index 093ff84f..fa6427b6 100644
--- a/modules/lakes.js
+++ b/modules/lakes.js
@@ -1,5 +1,3 @@
-"use strict";
-
window.Lakes = (function () {
const setClimateData = function (h) {
const cells = pack.cells;
@@ -12,7 +10,10 @@ window.Lakes = (function () {
f.flux = f.shoreline.reduce((acc, c) => acc + grid.cells.prec[cells.g[c]], 0);
// temperature and evaporation to detect closed lakes
- f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
+ f.temp =
+ f.cells < 6
+ ? grid.cells.temp[cells.g[f.firstCell]]
+ : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
const height = (f.height - 18) ** heightExponentInput.value; // height in meters
const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11]
f.evaporation = rn(evaporation * f.cells);
diff --git a/modules/markers-generator.js b/modules/markers-generator.js
index 6845c815..d7a12ca1 100644
--- a/modules/markers-generator.js
+++ b/modules/markers-generator.js
@@ -1,4 +1,4 @@
-"use strict";
+import {TIME} from "/src/config/logging";
window.Markers = (function () {
let config = [];
@@ -20,6 +20,7 @@ window.Markers = (function () {
list: function to select candidates
add: function to add marker legend
*/
+ // prettier-ignore
return [
{type: "volcanoes", icon: "🌋", dx: 52, px: 13, min: 10, each: 500, multiplier: 1, list: listVolcanoes, add: addVolcano},
{type: "hot-springs", icon: "♨️", dy: 52, min: 30, each: 1200, multiplier: 1, list: listHotSprings, add: addHotSpring},
@@ -199,7 +200,13 @@ window.Markers = (function () {
function listBridges({cells, burgs}) {
const meanFlux = d3.mean(cells.fl.filter(fl => fl));
return cells.i.filter(
- i => !occupied[i] && cells.burg[i] && cells.t[i] !== 1 && burgs[cells.burg[i]].population > 20 && cells.r[i] && cells.fl[i] > meanFlux
+ i =>
+ !occupied[i] &&
+ cells.burg[i] &&
+ cells.t[i] !== 1 &&
+ burgs[cells.burg[i]].population > 20 &&
+ cells.r[i] &&
+ cells.fl[i] > meanFlux
);
}
@@ -441,7 +448,21 @@ window.Markers = (function () {
"rat tails",
"pig ears"
];
- const types = ["hot", "cold", "fire", "ice", "smoky", "misty", "shiny", "sweet", "bitter", "salty", "sour", "sparkling", "smelly"];
+ const types = [
+ "hot",
+ "cold",
+ "fire",
+ "ice",
+ "smoky",
+ "misty",
+ "shiny",
+ "sweet",
+ "bitter",
+ "salty",
+ "sour",
+ "sparkling",
+ "smelly"
+ ];
const drinks = [
"wine",
"brandy",
@@ -469,7 +490,11 @@ window.Markers = (function () {
const typeName = P(0.3) ? "inn" : "tavern";
const isAnimalThemed = P(0.7);
const animal = ra(animals);
- const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(typeName);
+ const name = isAnimalThemed
+ ? P(0.6)
+ ? ra(colors) + " " + animal
+ : ra(adjectives) + " " + animal
+ : ra(adjectives) + " " + capitalize(typeName);
const meal = isAnimalThemed && P(0.3) ? animal : ra(courses);
const course = `${ra(methods)} ${meal}`.toLowerCase();
const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase();
@@ -478,18 +503,26 @@ window.Markers = (function () {
}
function listLighthouses({cells}) {
- return cells.i.filter(i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && cells.road[c]));
+ return cells.i.filter(
+ i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && cells.road[c])
+ );
}
function addLighthouse(id, cell) {
const {cells} = pack;
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 serve as a beacon for ships in the open sea`});
+ notes.push({
+ id,
+ name: getAdjective(proper) + " Lighthouse" + name,
+ legend: `A lighthouse to serve as a beacon for ships in the open sea`
+ });
}
function listWaterfalls({cells}) {
- return cells.i.filter(i => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some(c => cells.h[c] < 40 && cells.r[c]));
+ return cells.i.filter(
+ i => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some(c => cells.h[c] < 40 && cells.r[c])
+ );
}
function addWaterfall(id, cell) {
@@ -509,7 +542,9 @@ window.Markers = (function () {
}
function listBattlefields({cells}) {
- return cells.i.filter(i => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25);
+ return cells.i.filter(
+ i => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25
+ );
}
function addBattlefield(id, cell) {
@@ -555,7 +590,9 @@ window.Markers = (function () {
}
function listSeaMonsters({cells, features}) {
- return cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i] && features[cells.f[i]].type === "ocean");
+ return cells.i.filter(
+ i => !occupied[i] && cells.h[i] < 20 && cells.road[i] && features[cells.f[i]].type === "ocean"
+ );
}
function addSeaMonster(id, cell) {
@@ -589,7 +626,17 @@ window.Markers = (function () {
"horrifying",
"feared"
];
- const subjects = ["Locals", "Elders", "Inscriptions", "Tipplers", "Legends", "Whispers", "Rumors", "Journeying folk", "Tales"];
+ const subjects = [
+ "Locals",
+ "Elders",
+ "Inscriptions",
+ "Tipplers",
+ "Legends",
+ "Whispers",
+ "Rumors",
+ "Journeying folk",
+ "Tales"
+ ];
const species = [
"Ogre",
"Troll",
@@ -625,13 +672,21 @@ window.Markers = (function () {
const monster = ra(species);
const toponym = Names.getCulture(cells.culture[cell]);
const name = `${toponym} ${monster}`;
- const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${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});
}
// Sacred mountains spawn on lonely mountains
function listSacredMountains({cells}) {
- return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70 && cells.c[i].some(c => cells.culture[c]) && cells.c[i].every(c => cells.h[c] < 60));
+ return cells.i.filter(
+ i =>
+ !occupied[i] &&
+ cells.h[i] >= 70 &&
+ cells.c[i].some(c => cells.culture[c]) &&
+ cells.c[i].every(c => cells.h[c] < 60)
+ );
}
function addSacredMountain(id, cell) {
@@ -674,7 +729,9 @@ window.Markers = (function () {
// Sacred palm groves spawn on oasises
function listSacredPalmGroves({cells}) {
- return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.biome[i] === 1 && cells.pop[i] > 1 && cells.road[i]);
+ return cells.i.filter(
+ i => !occupied[i] && cells.culture[i] && cells.biome[i] === 1 && cells.pop[i] > 1 && cells.road[i]
+ );
}
function addSacredPalmGrove(id, cell) {
@@ -765,7 +822,20 @@ window.Markers = (function () {
function addStatue(id, cell) {
const {cells} = pack;
- const variants = ["Statue", "Obelisk", "Monument", "Column", "Monolith", "Pillar", "Megalith", "Stele", "Runestone", "Sculpture", "Effigy", "Idol"];
+ const variants = [
+ "Statue",
+ "Obelisk",
+ "Monument",
+ "Column",
+ "Monolith",
+ "Pillar",
+ "Megalith",
+ "Stele",
+ "Runestone",
+ "Sculpture",
+ "Effigy",
+ "Idol"
+ ];
const scripts = {
cypriot: "𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ",
geez: "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጰጸፀፈፐ ",
@@ -820,7 +890,16 @@ window.Markers = (function () {
}
function addCircuses(id, cell) {
- const adjectives = ["Fantastical", "Wonderous", "Incomprehensible", "Magical", "Extraordinary", "Unmissable", "World-famous", "Breathtaking"];
+ const adjectives = [
+ "Fantastical",
+ "Wonderous",
+ "Incomprehensible",
+ "Magical",
+ "Extraordinary",
+ "Unmissable",
+ "World-famous",
+ "Breathtaking"
+ ];
const adjective = ra(adjectives);
const name = `Travelling ${adjective} Circus`;
@@ -932,8 +1011,26 @@ window.Markers = (function () {
function addDances(id, cell) {
const {cells, burgs} = pack;
const burgName = burgs[cells.burg[cell]].name;
- const socialTypes = ["gala", "dance", "performance", "ball", "soiree", "jamboree", "exhibition", "carnival", "festival", "jubilee"];
- const people = ["great and the good", "nobility", "local elders", "foreign dignitaries", "spiritual leaders", "suspected revolutionaries"];
+ const socialTypes = [
+ "gala",
+ "dance",
+ "performance",
+ "ball",
+ "soiree",
+ "jamboree",
+ "exhibition",
+ "carnival",
+ "festival",
+ "jubilee"
+ ];
+ const people = [
+ "great and the good",
+ "nobility",
+ "local elders",
+ "foreign dignitaries",
+ "spiritual leaders",
+ "suspected revolutionaries"
+ ];
const socialType = ra(socialTypes);
const name = `${burgName} ${socialType}`;
diff --git a/modules/military-generator.js b/modules/military-generator.js
index 648f5637..7990fe0e 100644
--- a/modules/military-generator.js
+++ b/modules/military-generator.js
@@ -1,4 +1,4 @@
-"use strict";
+import {TIME} from "/src/config/logging";
window.Military = (function () {
const generate = function () {
@@ -10,7 +10,18 @@ window.Military = (function () {
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
const area = d3.sum(valid.map(s => s.area)); // total area
- const rate = {x: 0, Ally: -0.2, Friendly: -0.1, Neutral: 0, Suspicion: 0.1, Enemy: 1, Unknown: 0, Rival: 0.5, Vassal: 0.5, Suzerain: -0.5};
+ const rate = {
+ x: 0,
+ Ally: -0.2,
+ Friendly: -0.1,
+ Neutral: 0,
+ Suspicion: 0.1,
+ Enemy: 1,
+ Unknown: 0,
+ Rival: 0.5,
+ Vassal: 0.5,
+ Suzerain: -0.5
+ };
const stateModifier = {
melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1},
@@ -24,14 +35,59 @@ window.Military = (function () {
};
const cellTypeModifier = {
- nomadic: {melee: 0.2, ranged: 0.5, mounted: 3, machinery: 0.4, naval: 0.3, armored: 1.6, aviation: 1, magical: 0.5},
- wetland: {melee: 0.8, ranged: 2, mounted: 0.3, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5},
- highland: {melee: 1.2, ranged: 1.6, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2}
+ nomadic: {
+ melee: 0.2,
+ ranged: 0.5,
+ mounted: 3,
+ machinery: 0.4,
+ naval: 0.3,
+ armored: 1.6,
+ aviation: 1,
+ magical: 0.5
+ },
+ wetland: {
+ melee: 0.8,
+ ranged: 2,
+ mounted: 0.3,
+ machinery: 1.2,
+ naval: 1.0,
+ armored: 0.2,
+ aviation: 0.5,
+ magical: 0.5
+ },
+ highland: {
+ melee: 1.2,
+ ranged: 1.6,
+ mounted: 0.3,
+ machinery: 3,
+ naval: 1.0,
+ armored: 0.8,
+ aviation: 0.3,
+ magical: 2
+ }
};
const burgTypeModifier = {
- nomadic: {melee: 0.3, ranged: 0.8, mounted: 3, machinery: 0.4, naval: 1.0, armored: 1.6, aviation: 1, magical: 0.5},
- wetland: {melee: 1, ranged: 1.6, mounted: 0.2, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5},
+ nomadic: {
+ melee: 0.3,
+ ranged: 0.8,
+ mounted: 3,
+ machinery: 0.4,
+ naval: 1.0,
+ armored: 1.6,
+ aviation: 1,
+ magical: 0.5
+ },
+ wetland: {
+ melee: 1,
+ ranged: 1.6,
+ mounted: 0.2,
+ machinery: 1.2,
+ naval: 1.0,
+ armored: 0.2,
+ aviation: 0.5,
+ magical: 0.5
+ },
highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2}
};
@@ -40,8 +96,16 @@ window.Military = (function () {
const d = s.diplomacy;
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 neighborsRateRaw = s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5);
+ 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 neighborsRateRaw = s.neighbors
+ .map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion"))
+ .reduce((s, r) => (s += rate[r]), 0.5);
const neighborsRate = minmax(neighborsRateRaw, 0.3, 3); // neighbors rate
s.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier)
s.temp.platoons = [];
@@ -86,8 +150,10 @@ window.Military = (function () {
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
+ 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);
for (const unit of options.military) {
@@ -111,7 +177,17 @@ window.Military = (function () {
n = 1;
}
- stateObj.temp.platoons.push({cell: i, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type});
+ stateObj.temp.platoons.push({
+ cell: i,
+ a: total,
+ t: total,
+ x,
+ y,
+ u: unit.name,
+ n,
+ s: unit.separate,
+ type: unit.type
+ });
}
}
@@ -153,7 +229,17 @@ window.Military = (function () {
n = 1;
}
- stateObj.temp.platoons.push({cell: b.cell, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type});
+ stateObj.temp.platoons.push({
+ cell: b.cell,
+ a: total,
+ t: total,
+ x,
+ y,
+ u: unit.name,
+ n,
+ s: unit.separate,
+ type: unit.type
+ });
}
}
@@ -379,7 +465,13 @@ window.Military = (function () {
// get default regiment emblem
const getEmblem = function (r) {
if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops
- if (!r.n && pack.states[r.state].form === "Monarchy" && pack.cells.burg[r.cell] && pack.burgs[pack.cells.burg[r.cell]].capital) return "👑"; // "Royal" regiment based in capital
+ if (
+ !r.n &&
+ pack.states[r.state].form === "Monarchy" &&
+ pack.cells.burg[r.cell] &&
+ pack.burgs[pack.cells.burg[r.cell]].capital
+ )
+ return "👑"; // "Royal" regiment based in capital
const mainUnit = Object.entries(r.u).sort((a, b) => b[1] - a[1])[0][0]; // unit with more troops in regiment
const unit = options.military.find(u => u.name === mainUnit);
return unit.icon;
@@ -400,7 +492,9 @@ window.Military = (function () {
.map(t => `— ${t}: ${r.u[t]}`)
.join("\r\n")
: null;
- const troops = composition ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.` : "";
+ const troops = composition
+ ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.`
+ : "";
const campaign = s.campaigns ? ra(s.campaigns) : null;
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6);
@@ -409,5 +503,16 @@ window.Military = (function () {
notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend});
};
- return {generate, redraw, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, moveRegiment, getTotal, getEmblem};
+ return {
+ generate,
+ redraw,
+ getDefaultOptions,
+ getName,
+ generateNote,
+ drawRegiments,
+ drawRegiment,
+ moveRegiment,
+ getTotal,
+ getEmblem
+ };
})();
diff --git a/modules/names-generator.js b/modules/names-generator.js
index d7078abb..8d80437b 100644
--- a/modules/names-generator.js
+++ b/modules/names-generator.js
@@ -1,5 +1,3 @@
-"use strict";
-
window.Names = (function () {
let chains = [];
@@ -142,7 +140,11 @@ window.Names = (function () {
// generate short name for base
const getBaseShort = function (base) {
if (nameBases[base] === undefined) {
- tip(`Namebase ${base} does not exist. Please upload custom namebases of change the base in Cultures Editor`, false, "error");
+ tip(
+ `Namebase ${base} does not exist. Please upload custom namebases of change the base in Cultures Editor`,
+ false,
+ "error"
+ );
base = 1;
}
const min = nameBases[base].min - 1;
@@ -165,7 +167,8 @@ window.Names = (function () {
// remove -sk/-ev/-ov for Ruthenian
else if (base === 12) return vowel(name.slice(-1)) ? name : name + "u";
// Japanese ends on any vowel or -u
- else if (base === 18 && P(0.4)) name = vowel(name.slice(0, 1).toLowerCase()) ? "Al" + name.toLowerCase() : "Al " + name; // Arabic starts with -Al
+ else if (base === 18 && P(0.4))
+ name = vowel(name.slice(0, 1).toLowerCase()) ? "Al" + name.toLowerCase() : "Al " + name; // Arabic starts with -Al
// no suffix for fantasy bases
if (base > 32 && base < 42) return name;
@@ -304,5 +307,16 @@ window.Names = (function () {
];
};
- return {getBase, getCulture, getCultureShort, getBaseShort, getState, updateChain, clearChains, getNameBases, getMapName, calculateChain};
+ return {
+ getBase,
+ getCulture,
+ getCultureShort,
+ getBaseShort,
+ getState,
+ updateChain,
+ clearChains,
+ getNameBases,
+ getMapName,
+ calculateChain
+ };
})();
diff --git a/modules/ocean-layers.js b/modules/ocean-layers.js
index f21e4722..ae042c82 100644
--- a/modules/ocean-layers.js
+++ b/modules/ocean-layers.js
@@ -1,4 +1,4 @@
-"use strict";
+import {TIME} from "/src/config/logging";
window.OceanLayers = (function () {
let cells, vertices, pointsN, used;
diff --git a/modules/religions-generator.js b/modules/religions-generator.js
index b7b3464c..139a3558 100644
--- a/modules/religions-generator.js
+++ b/modules/religions-generator.js
@@ -1,4 +1,5 @@
-"use strict";
+import {TIME} from "/src/config/logging";
+import {findAll} from "/src/utils/graphUtils";
window.Religions = (function () {
// name generation approach and relative chance to be selected
diff --git a/modules/river-generator.js b/modules/river-generator.js
index 957fe6fc..af1f81a8 100644
--- a/modules/river-generator.js
+++ b/modules/river-generator.js
@@ -1,4 +1,4 @@
-"use strict";
+import {TIME} from "/src/config/logging";
window.Rivers = (function () {
const generate = function (allowErosion = true) {
@@ -48,7 +48,9 @@ window.Rivers = (function () {
cells.fl[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation
// create lake outlet if lake is not in deep depression and flux > evaporation
- const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
+ const lakes = lakeOutCells[i]
+ ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation)
+ : [];
for (const lake of lakes) {
const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i);
cells.fl[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet
@@ -191,7 +193,18 @@ window.Rivers = (function () {
const length = getApproximateLength(meanderedPoints);
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
- pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells});
+ pack.rivers.push({
+ i: riverId,
+ source,
+ mouth,
+ discharge,
+ length,
+ width,
+ widthFactor,
+ sourceWidth: 0,
+ parent,
+ cells: riverCells
+ });
}
}
diff --git a/modules/routes-generator.js b/modules/routes-generator.js
index e4ec3374..86b5ce69 100644
--- a/modules/routes-generator.js
+++ b/modules/routes-generator.js
@@ -1,3 +1,6 @@
+import {TIME} from "/src/config/logging";
+import {findCell} from "/src/utils/graphUtils";
+
window.Routes = (function () {
const getRoads = function () {
TIME && console.time("generateMainRoads");
@@ -39,7 +42,10 @@ window.Routes = (function () {
if (!i) {
// build trail from the first burg on island
// to the farthest one on the same island or the closest road
- const farthest = d3.scan(isle, (a, c) => (c.y - b.y) ** 2 + (c.x - b.x) ** 2 - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2));
+ const farthest = d3.scan(
+ isle,
+ (a, c) => (c.y - b.y) ** 2 + (c.x - b.x) ** 2 - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2)
+ );
const to = isle[farthest].cell;
if (cells.road[to]) return;
const [from, exit] = findLandPath(b.cell, to, true);
@@ -131,7 +137,8 @@ window.Routes = (function () {
const getBurgCoords = b => [burgs[b].x, burgs[b].y];
const getPathPoints = cells => cells.map(i => (Array.isArray(i) ? i : burg[i] ? getBurgCoords(burg[i]) : p[i]));
const getPath = segment => round(lineGen(getPathPoints(segment)), 1);
- const getPathsHTML = (paths, type) => paths.map((path, i) => `
`).join("");
+ const getPathsHTML = (paths, type) =>
+ paths.map((path, i) => `
`).join("");
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
roads.html(getPathsHTML(main, "road"));
diff --git a/modules/ui/battle-screen.js b/modules/ui/battle-screen.js
index 2936549c..a2ad9cb9 100644
--- a/modules/ui/battle-screen.js
+++ b/modules/ui/battle-screen.js
@@ -32,17 +32,27 @@ class Battle {
close: () => Battle.prototype.context.cancelResults()
});
- if (modules.Battle) return;
- modules.Battle = true;
+ if (fmg.modules.Battle) return;
+ fmg.modules.Battle = true;
// add listeners
document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev));
- document.getElementById("battleType").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev));
- document.getElementById("battleNameShow").addEventListener("click", () => Battle.prototype.context.showNameSection());
- document.getElementById("battleNamePlace").addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value));
+ document
+ .getElementById("battleType")
+ .nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev));
+ document
+ .getElementById("battleNameShow")
+ .addEventListener("click", () => Battle.prototype.context.showNameSection());
+ document
+ .getElementById("battleNamePlace")
+ .addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value));
document.getElementById("battleNameFull").addEventListener("change", ev => Battle.prototype.context.changeName(ev));
- document.getElementById("battleNameCulture").addEventListener("click", () => Battle.prototype.context.generateName("culture"));
- document.getElementById("battleNameRandom").addEventListener("click", () => Battle.prototype.context.generateName("random"));
+ document
+ .getElementById("battleNameCulture")
+ .addEventListener("click", () => Battle.prototype.context.generateName("culture"));
+ document
+ .getElementById("battleNameRandom")
+ .addEventListener("click", () => Battle.prototype.context.generateName("random"));
document.getElementById("battleNameHide").addEventListener("click", this.hideNameSection);
document.getElementById("battleAddRegiment").addEventListener("click", this.addSide);
document.getElementById("battleRoll").addEventListener("click", () => Battle.prototype.context.randomize());
@@ -52,11 +62,19 @@ class Battle {
document.getElementById("battleWiki").addEventListener("click", () => wiki("Battle-Simulator"));
document.getElementById("battlePhase_attackers").addEventListener("click", ev => this.toggleChange(ev));
- document.getElementById("battlePhase_attackers").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "attackers"));
+ document
+ .getElementById("battlePhase_attackers")
+ .nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "attackers"));
document.getElementById("battlePhase_defenders").addEventListener("click", ev => this.toggleChange(ev));
- document.getElementById("battlePhase_defenders").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "defenders"));
- document.getElementById("battleDie_attackers").addEventListener("click", () => Battle.prototype.context.rollDie("attackers"));
- document.getElementById("battleDie_defenders").addEventListener("click", () => Battle.prototype.context.rollDie("defenders"));
+ document
+ .getElementById("battlePhase_defenders")
+ .nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "defenders"));
+ document
+ .getElementById("battleDie_attackers")
+ .addEventListener("click", () => Battle.prototype.context.rollDie("attackers"));
+ document
+ .getElementById("battleDie_defenders")
+ .addEventListener("click", () => Battle.prototype.context.rollDie("defenders"));
}
defineType() {
@@ -82,8 +100,12 @@ class Battle {
document.getElementById("battleType").className = "icon-button-" + this.type;
const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers");
- const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_" + this.type).content;
- const defenders = sideSpecific ? document.getElementById("battlePhases_" + this.type + "_defenders").content : attackers;
+ const attackers = sideSpecific
+ ? sideSpecific.content
+ : document.getElementById("battlePhases_" + this.type).content;
+ const defenders = sideSpecific
+ ? document.getElementById("battlePhases_" + this.type + "_defenders").content
+ : attackers;
document.getElementById("battlePhase_attackers").nextElementSibling.innerHTML = "";
document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = "";
@@ -146,19 +168,30 @@ class Battle {
${regiment.icon} `;
const body = `
`;
- let initial = `${icon} ${regiment.name.slice(0, 24)} `;
- let casualties = `${state.fullName.slice(0, 26)} `;
+ let initial = `${icon} ${regiment.name.slice(0, 24)} `;
+ let casualties = `${state.fullName.slice(
+ 0,
+ 26
+ )} `;
let survivors = `Distance to base: ${distance} ${distanceUnitInput.value} `;
for (const u of options.military) {
- initial += `${regiment.u[u.name] || 0} `;
+ initial += `${
+ regiment.u[u.name] || 0
+ } `;
casualties += `0 `;
- survivors += `${regiment.u[u.name] || 0} `;
+ survivors += `${
+ regiment.u[u.name] || 0
+ } `;
}
initial += `${regiment.a || 0} `;
casualties += `0 `;
- survivors += `${regiment.a || 0} `;
+ survivors += `${
+ regiment.a || 0
+ } `;
const div = side === "attackers" ? battleAttackers : battleDefenders;
div.innerHTML += body + initial + casualties + survivors + " ";
@@ -173,17 +206,23 @@ class Battle {
.filter(s => s.military && !s.removed)
.map(s => s.military)
.flat();
- const distance = reg => rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value;
- const isAdded = reg => context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === reg);
+ const distance = reg =>
+ rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value;
+ const isAdded = reg =>
+ context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === reg);
body.innerHTML = regiments
.map(r => {
const s = pack.states[r.state],
added = isAdded(r),
dist = added ? "0 " + distanceUnitInput.value : distance(r);
- return `
-
+
${s.name.slice(0, 11)}
${r.icon}
${r.name.slice(0, 24)}
@@ -267,7 +306,10 @@ class Battle {
}
generateName(type) {
- const place = type === "culture" ? Names.getCulture(pack.cells.culture[this.cell], null, null, "") : Names.getBase(rand(nameBases.length - 1));
+ const place =
+ type === "culture"
+ ? Names.getCulture(pack.cells.culture[this.cell], null, null, "")
+ : Names.getBase(rand(nameBases.length - 1));
document.getElementById("battleNamePlace").value = this.place = place;
document.getElementById("battleNameFull").value = this.name = this.defineName();
$("#battleScreen").dialog({title: this.name});
@@ -286,35 +328,161 @@ class Battle {
calculateStrength(side) {
const scheme = {
// field battle phases
- skirmish: {melee: 0.2, ranged: 2.4, mounted: 0.1, machinery: 3, naval: 1, armored: 0.2, aviation: 1.8, magical: 1.8}, // ranged excel
+ skirmish: {
+ melee: 0.2,
+ ranged: 2.4,
+ mounted: 0.1,
+ machinery: 3,
+ naval: 1,
+ armored: 0.2,
+ aviation: 1.8,
+ magical: 1.8
+ }, // ranged excel
melee: {melee: 2, ranged: 1.2, mounted: 1.5, machinery: 0.5, naval: 0.2, armored: 2, aviation: 0.8, magical: 0.8}, // melee excel
pursue: {melee: 1, ranged: 1, mounted: 4, machinery: 0.05, naval: 1, armored: 1, aviation: 1.5, magical: 0.6}, // mounted excel
- retreat: {melee: 0.1, ranged: 0.01, mounted: 0.5, machinery: 0.01, naval: 0.2, armored: 0.1, aviation: 0.8, magical: 0.05}, // reduced
+ retreat: {
+ melee: 0.1,
+ ranged: 0.01,
+ mounted: 0.5,
+ machinery: 0.01,
+ naval: 0.2,
+ armored: 0.1,
+ aviation: 0.8,
+ magical: 0.05
+ }, // reduced
// naval battle phases
shelling: {melee: 0, ranged: 0.2, mounted: 0, machinery: 2, naval: 2, armored: 0, aviation: 0.1, magical: 0.5}, // naval and machinery excel
- boarding: {melee: 1, ranged: 0.5, mounted: 0.5, machinery: 0, naval: 0.5, armored: 0.4, aviation: 0, magical: 0.2}, // melee excel
+ boarding: {
+ melee: 1,
+ ranged: 0.5,
+ mounted: 0.5,
+ machinery: 0,
+ naval: 0.5,
+ armored: 0.4,
+ aviation: 0,
+ magical: 0.2
+ }, // melee excel
chase: {melee: 0, ranged: 0.15, mounted: 0, machinery: 1, naval: 1, armored: 0, aviation: 0.15, magical: 0.5}, // reduced
- withdrawal: {melee: 0, ranged: 0.02, mounted: 0, machinery: 0.5, naval: 0.1, armored: 0, aviation: 0.1, magical: 0.3}, // reduced
+ withdrawal: {
+ melee: 0,
+ ranged: 0.02,
+ mounted: 0,
+ machinery: 0.5,
+ naval: 0.1,
+ armored: 0,
+ aviation: 0.1,
+ magical: 0.3
+ }, // reduced
// siege phases
- blockade: {melee: 0.25, ranged: 0.25, mounted: 0.2, machinery: 0.5, naval: 0.2, armored: 0.1, aviation: 0.25, magical: 0.25}, // no active actions
- sheltering: {melee: 0.3, ranged: 0.5, mounted: 0.2, machinery: 0.5, naval: 0.2, armored: 0.1, aviation: 0.25, magical: 0.25}, // no active actions
+ blockade: {
+ melee: 0.25,
+ ranged: 0.25,
+ mounted: 0.2,
+ machinery: 0.5,
+ naval: 0.2,
+ armored: 0.1,
+ aviation: 0.25,
+ magical: 0.25
+ }, // no active actions
+ sheltering: {
+ melee: 0.3,
+ ranged: 0.5,
+ mounted: 0.2,
+ machinery: 0.5,
+ naval: 0.2,
+ armored: 0.1,
+ aviation: 0.25,
+ magical: 0.25
+ }, // no active actions
sortie: {melee: 2, ranged: 0.5, mounted: 1.2, machinery: 0.2, naval: 0.1, armored: 0.5, aviation: 1, magical: 1}, // melee excel
- bombardment: {melee: 0.2, ranged: 0.5, mounted: 0.2, machinery: 3, naval: 1, armored: 0.5, aviation: 1, magical: 1}, // machinery excel
- storming: {melee: 1, ranged: 0.6, mounted: 0.5, machinery: 1, naval: 0.1, armored: 0.1, aviation: 0.5, magical: 0.5}, // melee excel
+ bombardment: {
+ melee: 0.2,
+ ranged: 0.5,
+ mounted: 0.2,
+ machinery: 3,
+ naval: 1,
+ armored: 0.5,
+ aviation: 1,
+ magical: 1
+ }, // machinery excel
+ storming: {
+ melee: 1,
+ ranged: 0.6,
+ mounted: 0.5,
+ machinery: 1,
+ naval: 0.1,
+ armored: 0.1,
+ aviation: 0.5,
+ magical: 0.5
+ }, // melee excel
defense: {melee: 2, ranged: 3, mounted: 1, machinery: 1, naval: 0.1, armored: 1, aviation: 0.5, magical: 1}, // ranged excel
- looting: {melee: 1.6, ranged: 1.6, mounted: 0.5, machinery: 0.2, naval: 0.02, armored: 0.2, aviation: 0.1, magical: 0.3}, // melee excel
- surrendering: {melee: 0.1, ranged: 0.1, mounted: 0.05, machinery: 0.01, naval: 0.01, armored: 0.02, aviation: 0.01, magical: 0.03}, // reduced
+ looting: {
+ melee: 1.6,
+ ranged: 1.6,
+ mounted: 0.5,
+ machinery: 0.2,
+ naval: 0.02,
+ armored: 0.2,
+ aviation: 0.1,
+ magical: 0.3
+ }, // melee excel
+ surrendering: {
+ melee: 0.1,
+ ranged: 0.1,
+ mounted: 0.05,
+ machinery: 0.01,
+ naval: 0.01,
+ armored: 0.02,
+ aviation: 0.01,
+ magical: 0.03
+ }, // reduced
// ambush phases
surprise: {melee: 2, ranged: 2.4, mounted: 1, machinery: 1, naval: 1, armored: 1, aviation: 0.8, magical: 1.2}, // increased
- shock: {melee: 0.5, ranged: 0.5, mounted: 0.5, machinery: 0.4, naval: 0.3, armored: 0.1, aviation: 0.4, magical: 0.5}, // reduced
+ shock: {
+ melee: 0.5,
+ ranged: 0.5,
+ mounted: 0.5,
+ machinery: 0.4,
+ naval: 0.3,
+ armored: 0.1,
+ aviation: 0.4,
+ magical: 0.5
+ }, // reduced
// langing phases
- landing: {melee: 0.8, ranged: 0.6, mounted: 0.6, machinery: 0.5, naval: 0.5, armored: 0.5, aviation: 0.5, magical: 0.6}, // reduced
- flee: {melee: 0.1, ranged: 0.01, mounted: 0.5, machinery: 0.01, naval: 0.5, armored: 0.1, aviation: 0.2, magical: 0.05}, // reduced
- waiting: {melee: 0.05, ranged: 0.5, mounted: 0.05, machinery: 0.5, naval: 2, armored: 0.05, aviation: 0.5, magical: 0.5}, // reduced
+ landing: {
+ melee: 0.8,
+ ranged: 0.6,
+ mounted: 0.6,
+ machinery: 0.5,
+ naval: 0.5,
+ armored: 0.5,
+ aviation: 0.5,
+ magical: 0.6
+ }, // reduced
+ flee: {
+ melee: 0.1,
+ ranged: 0.01,
+ mounted: 0.5,
+ machinery: 0.01,
+ naval: 0.5,
+ armored: 0.1,
+ aviation: 0.2,
+ magical: 0.05
+ }, // reduced
+ waiting: {
+ melee: 0.05,
+ ranged: 0.5,
+ mounted: 0.05,
+ machinery: 0.5,
+ naval: 2,
+ armored: 0.05,
+ aviation: 0.5,
+ magical: 0.5
+ }, // reduced
// air battle phases
maneuvering: {melee: 0, ranged: 0.1, mounted: 0, machinery: 0.2, naval: 0, armored: 0, aviation: 1, magical: 0.2}, // aviation
@@ -324,7 +492,8 @@ class Battle {
const forces = this.getJoinedForces(this[side].regiments);
const phase = this[side].phase;
const adjuster = Math.max(populationRate / 10, 10); // population adjuster, by default 100
- this[side].power = d3.sum(options.military.map(u => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster;
+ this[side].power =
+ d3.sum(options.military.map(u => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster;
const UIvalue = this[side].power ? Math.max(this[side].power | 0, 1) : 0;
document.getElementById("battlePower_" + side).innerHTML = UIvalue;
}
@@ -723,11 +892,13 @@ class Battle {
const status = battleStatus[+P(0.7)];
const result = `The ${this.getTypeName(this.type)} ended in ${status}`;
- const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(
- this.defenders.regiments,
- 0
- )}. ${result}.
- \r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`;
+ const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(
+ this.attackers.regiments,
+ 1
+ )} and ${getSide(this.defenders.regiments, 0)}. ${result}.
+ \r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(
+ this.defenders.casualties
+ )}%`;
notes.push({id: `marker${i}`, name: this.name, legend});
tip(`${this.name} is over. ${result}`, true, "success", 4000);
diff --git a/modules/ui/biomes-editor.js b/modules/ui/biomes-editor.js
index 0cfc5ee2..b3752e9e 100644
--- a/modules/ui/biomes-editor.js
+++ b/modules/ui/biomes-editor.js
@@ -12,8 +12,8 @@ function editBiomes() {
const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
refreshBiomesEditor();
- if (modules.editBiomes) return;
- modules.editBiomes = true;
+ if (fmg.modules.editBiomes) return;
+ fmg.modules.editBiomes = true;
$("#biomesEditor").dialog({
title: "Biomes Editor",
@@ -88,7 +88,9 @@ function editBiomes() {
const rural = b.rural[i] * populationRate;
const urban = b.urban[i] * populationRate * urbanization;
const population = rn(rural + urban);
- const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`;
+ const populationTip = `Total population: ${si(population)}; Rural population: ${si(
+ rural
+ )}; Urban population: ${si(urban)}`;
totalArea += area;
totalPopulation += population;
@@ -104,7 +106,9 @@ function editBiomes() {
data-color=${b.color[i]}
>
-
+
%
${si(population)}
- ${i > 12 && !b.cells[i] ? '
' : ""}
+ ${
+ i > 12 && !b.cells[i]
+ ? '
'
+ : ""
+ }
`;
}
@@ -403,7 +411,14 @@ function editBiomes() {
// change of append new element
if (exists.size()) exists.attr("data-biome", biomeNew).attr("fill", color).attr("stroke", color);
- else temp.append("polygon").attr("data-cell", i).attr("data-biome", biomeNew).attr("points", getPackPolygon(i)).attr("fill", color).attr("stroke", color);
+ else
+ temp
+ .append("polygon")
+ .attr("data-cell", i)
+ .attr("data-biome", biomeNew)
+ .attr("points", getPackPolygon(i))
+ .attr("fill", color)
+ .attr("stroke", color);
});
}
diff --git a/modules/ui/burg-editor.js b/modules/ui/burg-editor.js
index ea3a76f2..4e0a10e0 100644
--- a/modules/ui/burg-editor.js
+++ b/modules/ui/burg-editor.js
@@ -17,8 +17,8 @@ function editBurg(id) {
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}
});
- if (modules.editBurg) return;
- modules.editBurg = true;
+ if (fmg.modules.editBurg) return;
+ fmg.modules.editBurg = true;
// add listeners
document.getElementById("burgGroupShow").addEventListener("click", showGroupSection);
@@ -284,7 +284,9 @@ function editBurg(id) {
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
}?
-
Please note that capital or locked burgs will not be deleted.
Burgs to be removed: ${burgsToRemove.length}`;
+
Please note that capital or locked burgs will not be deleted.
Burgs to be removed: ${
+ burgsToRemove.length
+ }`;
$("#alert").dialog({
resizable: false,
title: "Remove burg group",
@@ -433,7 +435,8 @@ function editBurg(id) {
function addCustomMfcgLink() {
const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];
- const message = "Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed";
+ const message =
+ "Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed";
prompt(message, {default: burg.link || "", required: false}, link => {
if (link) burg.link = link;
else delete burg.link;
diff --git a/modules/ui/burgs-overview.js b/modules/ui/burgs-overview.js
index bf819465..5a1f02cf 100644
--- a/modules/ui/burgs-overview.js
+++ b/modules/ui/burgs-overview.js
@@ -11,8 +11,8 @@ function overviewBurgs() {
burgsOverviewAddLines();
$("#burgsOverview").dialog();
- if (modules.overviewBurgs) return;
- modules.overviewBurgs = true;
+ if (fmg.modules.overviewBurgs) return;
+ fmg.modules.overviewBurgs = true;
$("#burgsOverview").dialog({
title: "Burgs Overview",
@@ -93,7 +93,9 @@ function overviewBurgs() {
data-type="${type}"
>
-
+
@@ -106,10 +108,14 @@ function overviewBurgs() {
data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}"
class="icon-star-empty${b.capital ? "" : " inactive pointer"}"
>
-
+
Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.
`;
+ Check out ${link(
+ "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization",
+ "wiki"
+ )} for guidance.
`;
$("#alert").dialog({
resizable: false,
@@ -148,7 +151,11 @@ function editHeightmap(options) {
// Exit customization mode
function finalizeHeightmap() {
if (viewbox.select("#heights").selectAll("*").size() < 200)
- return tip("Insufficient land area! There should be at least 200 land cells to finalize the heightmap", null, "error");
+ return tip(
+ "Insufficient land area! There should be at least 200 land cells to finalize the heightmap",
+ null,
+ "error"
+ );
if (byId("imageConverter").offsetParent) return tip("Please exit the Image Conversion mode first", null, "error");
delete window.edits; // remove global variable
@@ -210,7 +217,8 @@ function editHeightmap(options) {
if (!erosionAllowed) {
for (const i of pack.cells.i) {
const g = pack.cells.g[i];
- if (pack.cells.h[i] !== grid.cells.h[g] && pack.cells.h[i] >= 20 === grid.cells.h[g] >= 20) pack.cells.h[i] = grid.cells.h[g];
+ if (pack.cells.h[i] !== grid.cells.h[g] && pack.cells.h[i] >= 20 === grid.cells.h[g] >= 20)
+ pack.cells.h[i] = grid.cells.h[g];
}
}
@@ -349,7 +357,8 @@ function editHeightmap(options) {
const isLand = pack.cells.h[i] >= 20;
// check biome
- pack.cells.biome[i] = isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]);
+ pack.cells.biome[i] =
+ isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]);
// rivers data
if (!erosionAllowed) {
@@ -373,7 +382,9 @@ function editHeightmap(options) {
const findBurgCell = function (x, y) {
let i = findCell(x, y);
if (pack.cells.h[i] >= 20) return i;
- const dist = pack.cells.c[i].map(c => (pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2));
+ const dist = pack.cells.c[i].map(c =>
+ pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2
+ );
return pack.cells.c[i][d3.scan(dist)];
};
@@ -551,8 +562,8 @@ function editHeightmap(options) {
})
.on("dialogclose", exitBrushMode);
- if (modules.openBrushesPanel) return;
- modules.openBrushesPanel = true;
+ if (fmg.modules.openBrushesPanel) return;
+ fmg.modules.openBrushesPanel = true;
// add listeners
byId("brushesButtons").on("click", e => toggleBrushMode(e));
@@ -630,15 +641,25 @@ function editHeightmap(options) {
const brush = document.querySelector("#brushesButtons > button.pressed").id;
if (brush === "brushRaise") s.forEach(i => (h[i] = h[i] < 20 ? 20 : lim(h[i] + power)));
- else if (brush === "brushElevate") s.forEach((i, d) => (h[i] = lim(h[i] + interpolate(d / Math.max(s.length - 1, 1)))));
+ else if (brush === "brushElevate")
+ s.forEach((i, d) => (h[i] = lim(h[i] + interpolate(d / Math.max(s.length - 1, 1)))));
else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power)));
- else if (brush === "brushDepress") s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1)))));
+ else if (brush === "brushDepress")
+ s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1)))));
else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start])));
else if (brush === "brushSmooth")
s.forEach(
- i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1))
+ i =>
+ (h[i] = rn(
+ (d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) +
+ h[i] * (10 - power) +
+ 0.6) /
+ (11 - power),
+ 1
+ ))
);
- else if (brush === "brushDisrupt") s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
+ else if (brush === "brushDisrupt")
+ s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
mockHeightmapSelection(s);
// updateHistory(); uncomment to update history every step
@@ -662,7 +683,8 @@ function editHeightmap(options) {
const operator = conditionSign.value;
const operand = rescaleModifier.valueAsNumber;
if (Number.isNaN(operand)) return tip("Operand should be a number", false, "error");
- if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) return tip("Operand should be an integer", false, "error");
+ if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand))
+ return tip("Operand should be an integer", false, "error");
HeightmapGenerator.setGraph(grid);
@@ -691,7 +713,8 @@ function editHeightmap(options) {
function startFromScratch() {
if (changeOnlyLand.checked) return tip("Not allowed when 'Change only land cells' mode is set", false, "error");
const someHeights = grid.cells.h.some(h => h);
- if (!someHeights) return tip("Heightmap is already cleared, please do not click twice if not required", false, "error");
+ if (!someHeights)
+ return tip("Heightmap is already cleared, please do not click twice if not required", false, "error");
grid.cells.h = new Uint8Array(grid.cells.i.length);
viewbox.select("#heights").selectAll("*").remove();
@@ -711,10 +734,15 @@ function editHeightmap(options) {
position: {my: "right top", at: "right-10 top+10", of: "svg"}
});
- if (modules.openTemplateEditor) return;
- modules.openTemplateEditor = true;
+ if (fmg.modules.openTemplateEditor) return;
+ fmg.modules.openTemplateEditor = true;
- $("#templateBody").sortable({items: "> div", handle: ".icon-resize-vertical", containment: "#templateBody", axis: "y"});
+ $("#templateBody").sortable({
+ items: "> div",
+ handle: ".icon-resize-vertical",
+ containment: "#templateBody",
+ axis: "y"
+ });
// add listeners
$body.on("click", function (ev) {
@@ -788,22 +816,31 @@ function editHeightmap(options) {
const common = /* html */ ` (options += `${pack.burgs[b].name} `));
+ burgs.forEach(
+ b => (options += `${pack.burgs[b].name} `)
+ );
return options;
}
@@ -265,7 +275,11 @@ function editProvinces() {
const {name, burg: burgId, burgs: provinceBurgs} = province;
if (provinceBurgs.some(b => burgs[b].capital))
- return tip("Cannot declare independence of a province having capital burg. Please change capital first", false, "error");
+ return tip(
+ "Cannot declare independence of a province having capital burg. Please change capital first",
+ false,
+ "error"
+ );
if (!burgId) return tip("Cannot declare independence of a province without burg", false, "error");
const oldStateId = province.state;
@@ -311,7 +325,10 @@ function editProvinces() {
return relations;
});
diplomacy.push("x");
- states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldStateId].name}`]);
+ states[0].diplomacy.push([
+ `Independance declaration`,
+ `${name} declared its independance from ${states[oldStateId].name}`
+ ]);
// create new state
states.push({
@@ -373,8 +390,12 @@ function editProvinces() {
const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = /* html */ ` Rural: Urban:
-
- Total population: ${l(total)} ⇒ ${l(total)} (100 %)
`;
+
+ Total population: ${l(total)} ⇒ ${l(
+ total
+ )} (100 %)
`;
const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
@@ -493,8 +514,8 @@ function editProvinces() {
position: {my: "center", at: "center", of: "svg"}
});
- if (modules.editProvinceName) return;
- modules.editProvinceName = true;
+ if (fmg.modules.editProvinceName) return;
+ fmg.modules.editProvinceName = true;
// add listeners
document.getElementById("provinceNameEditorShortCulture").addEventListener("click", regenerateShortNameCuture);
@@ -692,7 +713,13 @@ function editProvinces() {
function updateChart() {
const value =
- this.value === "area" ? d => d.area : this.value === "rural" ? d => d.rural : this.value === "urban" ? d => d.urban : d => d.rural + d.urban;
+ this.value === "area"
+ ? d => d.area
+ : this.value === "rural"
+ ? d => d.rural
+ : this.value === "urban"
+ ? d => d.urban
+ : d => d.rural + d.urban;
root.sum(value);
node.data(treeLayout(root).leaves());
@@ -774,7 +801,13 @@ function editProvinces() {
customization = 11;
provs.select("g#provincesBody").append("g").attr("id", "temp");
- provs.select("g#provincesBody").append("g").attr("id", "centers").attr("fill", "none").attr("stroke", "#ff0000").attr("stroke-width", 1);
+ provs
+ .select("g#provincesBody")
+ .append("g")
+ .attr("id", "centers")
+ .attr("fill", "none")
+ .attr("stroke", "#ff0000")
+ .attr("stroke-width", 1);
document.querySelectorAll("#provincesBottom > *").forEach(el => (el.style.display = "none"));
document.getElementById("provincesManuallyButtons").style.display = "inline-block";
@@ -786,7 +819,11 @@ function editProvinces() {
$("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
tip("Click on a province to select, drag the circle to change province", true);
- viewbox.style("cursor", "crosshair").on("click", selectProvinceOnMapClick).call(d3.drag().on("start", dragBrush)).on("touchmove mousemove", moveBrush);
+ viewbox
+ .style("cursor", "crosshair")
+ .on("click", selectProvinceOnMapClick)
+ .call(d3.drag().on("start", dragBrush))
+ .on("touchmove mousemove", moveBrush);
body.querySelector("div").classList.add("selected");
selectProvince(+body.querySelector("div").dataset.id);
@@ -857,7 +894,11 @@ function editProvinces() {
if (i === pack.provinces[provinceOld].center) {
const center = centers.select("polygon[data-center='" + i + "']");
if (!center.size()) centers.append("polygon").attr("data-center", i).attr("points", getPackPolygon(i));
- tip("Province center cannot be assigned to a different region. Please remove the province first", false, "error");
+ tip(
+ "Province center cannot be assigned to a different region. Please remove the province first",
+ false,
+ "error"
+ );
return;
}
@@ -919,7 +960,8 @@ function editProvinces() {
provincesHeader.querySelector("div[data-sortby='state']").style.left = "22em";
provincesFooter.style.display = "block";
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all"));
- if (!close) $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
+ if (!close)
+ $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
restoreDefaultEvents();
clearMainTip();
@@ -941,14 +983,20 @@ function editProvinces() {
const {cells, provinces} = pack;
const point = d3.mouse(this);
const center = findCell(point[0], point[1]);
- if (cells.h[center] < 20) return tip("You cannot place province into the water. Please click on a land cell", false, "error");
+ if (cells.h[center] < 20)
+ return tip("You cannot place province into the water. Please click on a land cell", false, "error");
const oldProvince = cells.province[center];
if (oldProvince && provinces[oldProvince].center === center)
return tip("The cell is already a center of a different province. Select other cell", false, "error");
const state = cells.state[center];
- if (!state) return tip("You cannot create a province in neutral lands. Please assign this land to a state first", false, "error");
+ if (!state)
+ return tip(
+ "You cannot create a province in neutral lands. Please assign this land to a state first",
+ false,
+ "error"
+ );
if (d3.event.shiftKey === false) exitAddProvinceMode();
@@ -1014,7 +1062,10 @@ function editProvinces() {
function downloadProvincesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
- let data = "Id,Province,Full Name,Form,State,Color,Capital,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers
+ let data =
+ "Id,Province,Full Name,Form,State,Color,Capital,Area " +
+ unit +
+ ",Total Population,Rural Population,Urban Population\n"; // headers
body.querySelectorAll(":scope > div").forEach(function (el) {
const key = parseInt(el.dataset.id);
diff --git a/modules/ui/regiment-editor.js b/modules/ui/regiment-editor.js
index 359cca91..7043c7ae 100644
--- a/modules/ui/regiment-editor.js
+++ b/modules/ui/regiment-editor.js
@@ -19,8 +19,8 @@ function editRegiment(selector) {
position: {my: "left top", at: "left+10 top+10", of: "#map"}
});
- if (modules.editRegiment) return;
- modules.editRegiment = true;
+ if (fmg.modules.editRegiment) return;
+ fmg.modules.editRegiment = true;
// add listeners
document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
diff --git a/modules/ui/regiments-overview.js b/modules/ui/regiments-overview.js
index 49fd7b8b..9e334437 100644
--- a/modules/ui/regiments-overview.js
+++ b/modules/ui/regiments-overview.js
@@ -9,8 +9,8 @@ function overviewRegiments(state) {
addLines();
$("#regimentsOverview").dialog();
- if (modules.overviewRegiments) return;
- modules.overviewRegiments = true;
+ if (fmg.modules.overviewRegiments) return;
+ fmg.modules.overviewRegiments = true;
updateHeaders();
$("#regimentsOverview").dialog({
@@ -37,7 +37,9 @@ function overviewRegiments(state) {
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
for (const u of options.military) {
const label = capitalize(u.name.replace(/_/g, " "));
- insert(`${label}
`);
+ insert(
+ `${label}
`
+ );
}
header.querySelectorAll(".removable").forEach(function (e) {
e.addEventListener("click", function () {
@@ -60,7 +62,9 @@ function overviewRegiments(state) {
for (const r of s.military) {
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name] || 0}`).join(" ");
const lineData = options.military
- .map(u => `${r.u[u.name] || 0}
`)
+ .map(
+ u => `${r.u[u.name] || 0}
`
+ )
.join(" ");
lines += /* html */ `
@@ -79,7 +83,9 @@ function overviewRegiments(state) {
lines += /* html */ `
Regiments: ${regiments.length}
- ${options.military.map(u => `
${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}
`).join(" ")}
+ ${options.military
+ .map(u => `
${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}
`)
+ .join(" ")}
${si(d3.sum(regiments.map(r => r.a)))}
`;
@@ -92,7 +98,9 @@ function overviewRegiments(state) {
// add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => regimentHighlightOn(ev)));
- body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => regimentHighlightOff(ev)));
+ body
+ .querySelectorAll("div.states")
+ .forEach(el => el.addEventListener("mouseleave", ev => regimentHighlightOff(ev)));
}
function updateFilter(state) {
diff --git a/modules/ui/relief-editor.js b/modules/ui/relief-editor.js
index abb800cd..268e2714 100644
--- a/modules/ui/relief-editor.js
+++ b/modules/ui/relief-editor.js
@@ -19,8 +19,8 @@ function editReliefIcon() {
close: closeReliefEditor
});
- if (modules.editReliefIcon) return;
- modules.editReliefIcon = true;
+ if (fmg.modules.editReliefIcon) return;
+ fmg.modules.editReliefIcon = true;
// add listeners
document.getElementById("reliefIndividual").addEventListener("click", enterIndividualMode);
@@ -260,7 +260,9 @@ function editReliefIcon() {
const type = reliefIconsDiv.querySelector("svg.pressed")?.dataset.type;
selection = type ? terrain.selectAll("use[href='" + type + "']") : terrain.selectAll("use");
const size = selection.size();
- alertMessage.innerHTML = type ? `Are you sure you want to remove all ${type} icons (${size})?` : `Are you sure you want to remove all icons (${size})?`;
+ alertMessage.innerHTML = type
+ ? `Are you sure you want to remove all ${type} icons (${size})?`
+ : `Are you sure you want to remove all icons (${size})?`;
}
$("#alert").dialog({
diff --git a/modules/ui/rivers-creator.js b/modules/ui/rivers-creator.js
index 83a4d1b9..70b8ae06 100644
--- a/modules/ui/rivers-creator.js
+++ b/modules/ui/rivers-creator.js
@@ -21,8 +21,8 @@ function createRiver() {
close: closeRiverCreator
});
- if (modules.createRiver) return;
- modules.createRiver = true;
+ if (fmg.modules.createRiver) return;
+ fmg.modules.createRiver = true;
// add listeners
document.getElementById("riverCreatorComplete").addEventListener("click", addRiver);
@@ -100,12 +100,30 @@ function createRiver() {
const name = getName(mouth);
const basin = getBasin(parent);
- rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"});
+ rivers.push({
+ i: riverId,
+ source,
+ mouth,
+ discharge,
+ length,
+ width,
+ widthFactor,
+ sourceWidth,
+ parent,
+ cells: riverCells,
+ basin,
+ name,
+ type: "River"
+ });
const id = "river" + riverId;
// render river
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
- viewbox.select("#rivers").append("path").attr("id", id).attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
+ viewbox
+ .select("#rivers")
+ .append("path")
+ .attr("id", id)
+ .attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
editRiver(id);
}
diff --git a/modules/ui/rivers-editor.js b/modules/ui/rivers-editor.js
index 7a24cfe5..d492f466 100644
--- a/modules/ui/rivers-editor.js
+++ b/modules/ui/rivers-editor.js
@@ -10,7 +10,10 @@ function editRiver(id) {
elSelected = d3.select("#" + id).on("click", addControlPoint);
- tip("Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead", true);
+ tip(
+ "Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead",
+ true
+ );
debug.append("g").attr("id", "controlCells");
debug.append("g").attr("id", "controlPoints");
@@ -29,8 +32,8 @@ function editRiver(id) {
close: closeRiverEditor
});
- if (modules.editRiver) return;
- modules.editRiver = true;
+ if (fmg.modules.editRiver) return;
+ fmg.modules.editRiver = true;
// add listeners
document.getElementById("riverCreateSelectingCells").addEventListener("click", createRiver);
@@ -163,7 +166,7 @@ function editRiver(id) {
elSelected.attr("d", path);
updateRiverLength(river);
- if (modules.elevation) showEPForRiver(elSelected.node());
+ if (fmg.modules.elevation) showEPForRiver(elSelected.node());
}
function addControlPoint() {
@@ -227,7 +230,7 @@ function editRiver(id) {
}
function showElevationProfile() {
- modules.elevation = true;
+ fmg.modules.elevation = true;
showEPForRiver(elSelected.node());
}
diff --git a/modules/ui/rivers-overview.js b/modules/ui/rivers-overview.js
index e9045fd5..602a8597 100644
--- a/modules/ui/rivers-overview.js
+++ b/modules/ui/rivers-overview.js
@@ -8,8 +8,8 @@ function overviewRivers() {
riversOverviewAddLines();
$("#riversOverview").dialog();
- if (modules.overviewRivers) return;
- modules.overviewRivers = true;
+ if (fmg.modules.overviewRivers) return;
+ fmg.modules.overviewRivers = true;
$("#riversOverview").dialog({
title: "Rivers Overview",
@@ -75,7 +75,9 @@ function overviewRivers() {
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev)));
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver));
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor));
- body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerRiverRemove));
+ body
+ .querySelectorAll("div > span.icon-trash-empty")
+ .forEach(el => el.addEventListener("click", triggerRiverRemove));
applySorting(riversHeader);
}
@@ -110,7 +112,18 @@ function overviewRivers() {
} else {
rivers.attr("data-basin", "hightlighted");
const basins = [...new Set(pack.rivers.map(r => r.basin))];
- const colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
+ const colors = [
+ "#1f77b4",
+ "#ff7f0e",
+ "#2ca02c",
+ "#d62728",
+ "#9467bd",
+ "#8c564b",
+ "#e377c2",
+ "#7f7f7f",
+ "#bcbd22",
+ "#17becf"
+ ];
basins.forEach((b, i) => {
const color = colors[i % colors.length];
diff --git a/modules/ui/routes-editor.js b/modules/ui/routes-editor.js
index 785c22a9..1517ad50 100644
--- a/modules/ui/routes-editor.js
+++ b/modules/ui/routes-editor.js
@@ -21,8 +21,8 @@ function editRoute(onClick) {
viewbox.on("touchmove mousemove", showEditorTips);
if (onClick) toggleRouteCreationMode();
- if (modules.editRoute) return;
- modules.editRoute = true;
+ if (fmg.modules.editRoute) return;
+ fmg.modules.editRoute = true;
// add listeners
document.getElementById("routeGroupsShow").addEventListener("click", showGroupSection);
@@ -97,11 +97,11 @@ function editRoute(onClick) {
const l = elSelected.node().getTotalLength();
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
- if (modules.elevation) showEPForRoute(elSelected.node());
+ if (fmg.modules.elevation) showEPForRoute(elSelected.node());
}
function showElevationProfile() {
- modules.elevation = true;
+ fmg.modules.elevation = true;
showEPForRoute(elSelected.node());
}
diff --git a/modules/ui/style.js b/modules/ui/style.js
index a3d84b02..325cda71 100644
--- a/modules/ui/style.js
+++ b/modules/ui/style.js
@@ -76,9 +76,22 @@ function selectStyleElement() {
// stroke color and width
if (
- ["armies", "routes", "lakes", "borders", "cults", "relig", "cells", "coastline", "prec", "ice", "icons", "coordinates", "zones", "gridOverlay"].includes(
- sel
- )
+ [
+ "armies",
+ "routes",
+ "lakes",
+ "borders",
+ "cults",
+ "relig",
+ "cells",
+ "coastline",
+ "prec",
+ "ice",
+ "icons",
+ "coordinates",
+ "zones",
+ "gridOverlay"
+ ].includes(sel)
) {
styleStroke.style.display = "block";
styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke");
@@ -87,14 +100,29 @@ function selectStyleElement() {
}
// stroke dash
- if (["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes(sel)) {
+ if (
+ ["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes(sel)
+ ) {
styleStrokeDash.style.display = "block";
styleStrokeDasharrayInput.value = el.attr("stroke-dasharray") || "";
styleStrokeLinecapInput.value = el.attr("stroke-linecap") || "inherit";
}
// clipping
- if (["cells", "gridOverlay", "coordinates", "compass", "terrain", "temperature", "routes", "texture", "biomes", "zones"].includes(sel)) {
+ if (
+ [
+ "cells",
+ "gridOverlay",
+ "coordinates",
+ "compass",
+ "terrain",
+ "temperature",
+ "routes",
+ "texture",
+ "biomes",
+ "zones"
+ ].includes(sel)
+ ) {
styleClipping.style.display = "block";
styleClippingInput.value = el.attr("mask") || "";
}
@@ -142,8 +170,12 @@ function selectStyleElement() {
if (sel === "population") {
stylePopulation.style.display = "block";
- stylePopulationRuralStrokeInput.value = stylePopulationRuralStrokeOutput.value = population.select("#rural").attr("stroke");
- stylePopulationUrbanStrokeInput.value = stylePopulationUrbanStrokeOutput.value = population.select("#urban").attr("stroke");
+ stylePopulationRuralStrokeInput.value = stylePopulationRuralStrokeOutput.value = population
+ .select("#rural")
+ .attr("stroke");
+ stylePopulationUrbanStrokeInput.value = stylePopulationUrbanStrokeOutput.value = population
+ .select("#urban")
+ .attr("stroke");
styleStrokeWidth.style.display = "block";
styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || "";
}
@@ -233,7 +265,8 @@ function selectStyleElement() {
styleOcean.style.display = "block";
styleOceanFill.value = styleOceanFillOutput.value = oceanLayers.select("#oceanBase").attr("fill");
styleOceanPattern.value = document.getElementById("oceanicPattern")?.getAttribute("href");
- styleOceanPatternOpacity.value = styleOceanPatternOpacityOutput.value = document.getElementById("oceanicPattern").getAttribute("opacity") || 1;
+ styleOceanPatternOpacity.value = styleOceanPatternOpacityOutput.value =
+ document.getElementById("oceanicPattern").getAttribute("opacity") || 1;
outlineLayers.value = oceanLayers.attr("layers");
}
@@ -551,7 +584,10 @@ styleFontAdd.addEventListener("click", function () {
if (!family) return tip("Please provide a font name", false, "error");
- const existingFont = method === "fontURL" ? fonts.find(font => font.family === family && font.src === src) : fonts.find(font => font.family === family);
+ const existingFont =
+ method === "fontURL"
+ ? fonts.find(font => font.family === family && font.src === src)
+ : fonts.find(font => font.family === family);
if (existingFont) return tip("The font is already added", false, "error");
if (method === "fontURL") addWebFont(family, src);
@@ -710,9 +746,9 @@ styleArmiesSize.addEventListener("input", function () {
});
});
-emblemsStateSizeInput.addEventListener("change", drawEmblems);
-emblemsProvinceSizeInput.addEventListener("change", drawEmblems);
-emblemsBurgSizeInput.addEventListener("change", drawEmblems);
+emblemsStateSizeInput.addEventListener("change", () => drawEmblems());
+emblemsProvinceSizeInput.addEventListener("change", () => drawEmblems());
+emblemsBurgSizeInput.addEventListener("change", () => drawEmblems());
// request a URL to image to be used as a texture
function textureProvideURL() {
diff --git a/modules/ui/stylePresets.js b/modules/ui/stylePresets.js
index 7353b067..75fd330e 100644
--- a/modules/ui/stylePresets.js
+++ b/modules/ui/stylePresets.js
@@ -1,14 +1,26 @@
-// UI module to control the style presets
"use strict";
+// UI module to control the style presets
-const systemPresets = ["default", "ancient", "gloom", "light", "watercolor", "clean", "atlas", "cyberpunk", "monochrome"];
+const systemPresets = [
+ "default",
+ "ancient",
+ "gloom",
+ "light",
+ "watercolor",
+ "clean",
+ "atlas",
+ "cyberpunk",
+ "monochrome"
+];
const customPresetPrefix = "fmgStyle_";
// add style presets to list
{
const systemOptions = systemPresets.map(styleName => `
${styleName} `);
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith(customPresetPrefix));
- const customOptions = storedStyles.map(styleName => `
${styleName.replace(customPresetPrefix, "")} [custom] `);
+ const customOptions = storedStyles.map(
+ styleName => `
${styleName.replace(customPresetPrefix, "")} [custom] `
+ );
const options = systemOptions.join("") + customOptions.join("");
document.getElementById("stylePreset").innerHTML = options;
}
@@ -37,7 +49,8 @@ async function getStylePreset(desiredPreset) {
const isValid = JSON.isValid(storedStyleJSON);
if (isValid) return [desiredPreset, JSON.parse(storedStyleJSON)];
- ERROR && console.error(`Custom style ${desiredPreset} stored in localStorage is not valid. Applying default style`);
+ ERROR &&
+ console.error(`Custom style ${desiredPreset} stored in localStorage is not valid. Applying default style`);
presetToLoad = "default";
}
}
@@ -126,8 +139,8 @@ function addStylePreset() {
styleSaverJSON.value = JSON.stringify(collectStyleData(), null, 2);
checkName();
- if (modules.saveStyle) return;
- modules.saveStyle = true;
+ if (fmg.modules.saveStyle) return;
+ fmg.modules.saveStyle = true;
// add listeners
document.getElementById("styleSaverName").addEventListener("input", checkName);
@@ -145,8 +158,31 @@ function addStylePreset() {
"#stateBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#provinceBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#cells": ["opacity", "stroke", "stroke-width", "filter", "mask"],
- "#gridOverlay": ["opacity", "scale", "dx", "dy", "type", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "transform", "filter", "mask"],
- "#coordinates": ["opacity", "data-size", "font-size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
+ "#gridOverlay": [
+ "opacity",
+ "scale",
+ "dx",
+ "dy",
+ "type",
+ "stroke",
+ "stroke-width",
+ "stroke-dasharray",
+ "stroke-linecap",
+ "transform",
+ "filter",
+ "mask"
+ ],
+ "#coordinates": [
+ "opacity",
+ "data-size",
+ "font-size",
+ "stroke",
+ "stroke-width",
+ "stroke-dasharray",
+ "stroke-linecap",
+ "filter",
+ "mask"
+ ],
"#compass": ["opacity", "transform", "filter", "mask", "shape-rendering"],
"#rose": ["transform"],
"#relig": ["opacity", "stroke", "stroke-width", "filter"],
@@ -174,7 +210,17 @@ function addStylePreset() {
"#statesBody": ["opacity", "filter"],
"#statesHalo": ["opacity", "data-width", "stroke-width", "filter"],
"#provs": ["opacity", "fill", "font-size", "font-family", "filter"],
- "#temperature": ["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
+ "#temperature": [
+ "opacity",
+ "font-size",
+ "fill",
+ "fill-opacity",
+ "stroke",
+ "stroke-width",
+ "stroke-dasharray",
+ "stroke-linecap",
+ "filter"
+ ],
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#emblems": ["opacity", "stroke-width", "filter"],
"#texture": ["opacity", "filter", "mask"],
@@ -184,16 +230,65 @@ function addStylePreset() {
"#oceanBase": ["fill"],
"#oceanicPattern": ["href", "opacity"],
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
- "#legend": ["data-size", "font-size", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"],
+ "#legend": [
+ "data-size",
+ "font-size",
+ "font-family",
+ "stroke",
+ "stroke-width",
+ "stroke-dasharray",
+ "stroke-linecap",
+ "data-x",
+ "data-y",
+ "data-columns"
+ ],
"#legendBox": ["fill", "fill-opacity"],
"#burgLabels > #cities": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
- "#burgIcons > #cities": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
+ "#burgIcons > #cities": [
+ "opacity",
+ "fill",
+ "fill-opacity",
+ "size",
+ "stroke",
+ "stroke-width",
+ "stroke-dasharray",
+ "stroke-linecap"
+ ],
"#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"],
"#burgLabels > #towns": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
- "#burgIcons > #towns": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
+ "#burgIcons > #towns": [
+ "opacity",
+ "fill",
+ "fill-opacity",
+ "size",
+ "stroke",
+ "stroke-width",
+ "stroke-dasharray",
+ "stroke-linecap"
+ ],
"#anchors > #towns": ["opacity", "fill", "size", "stroke", "stroke-width"],
- "#labels > #states": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
- "#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
+ "#labels > #states": [
+ "opacity",
+ "fill",
+ "stroke",
+ "stroke-width",
+ "text-shadow",
+ "data-size",
+ "font-size",
+ "font-family",
+ "filter"
+ ],
+ "#labels > #addedLabels": [
+ "opacity",
+ "fill",
+ "stroke",
+ "stroke-width",
+ "text-shadow",
+ "data-size",
+ "font-size",
+ "font-family",
+ "filter"
+ ],
"#fogging": ["opacity", "fill", "filter"]
};
@@ -238,7 +333,8 @@ function addStylePreset() {
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
if (!JSON.isValid(styleJSON)) return tip("JSON string is not valid, please check the format", false, "error");
if (!desiredName) return tip("Please provide a preset name", false, "error");
- if (styleSaverTip.innerHTML === "default") return tip("You cannot overwrite default preset, please change the name", false, "error");
+ if (styleSaverTip.innerHTML === "default")
+ return tip("You cannot overwrite default preset, please change the name", false, "error");
const presetName = customPresetPrefix + desiredName;
applyOption(stylePreset, presetName, desiredName + " [custom]");
diff --git a/modules/ui/submap.js b/modules/ui/submap.js
index 737560c3..b2a27506 100644
--- a/modules/ui/submap.js
+++ b/modules/ui/submap.js
@@ -136,7 +136,14 @@ window.UISubmap = (function () {
}
async function loadPreview($container, w, h) {
- const url = await getMapURL("png", {globe: false, noWater: true, fullMap: true, noLabels: true, noScaleBar: true, noIce: true});
+ const url = await getMapURL("png", {
+ globe: false,
+ noWater: true,
+ fullMap: true,
+ noLabels: true,
+ noScaleBar: true,
+ noIce: true
+ });
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
@@ -173,7 +180,11 @@ window.UISubmap = (function () {
const {angle, shiftX, shiftY, ratio, mirrorH, mirrorV} = getTransformInput();
const [cx, cy] = [graphWidth / 2, graphHeight / 2];
- const rot = alfa => (x, y) => [(x - cx) * Math.cos(alfa) - (y - cy) * Math.sin(alfa) + cx, (y - cy) * Math.cos(alfa) + (x - cx) * Math.sin(alfa) + cy];
+ const rot = alfa => (x, y) =>
+ [
+ (x - cx) * Math.cos(alfa) - (y - cy) * Math.sin(alfa) + cx,
+ (y - cy) * Math.cos(alfa) + (x - cx) * Math.sin(alfa) + cy
+ ];
const shift = (dx, dy) => (x, y) => [x + dx, y + dy];
const scale = r => (x, y) => [(x - cx) * r + cx, (y - cy) * r + cy];
const flipH = (x, y) => [-x + 2 * cx, y];
@@ -185,7 +196,11 @@ window.UISubmap = (function () {
let inverse = id;
if (angle) [projection, inverse] = [rot(angle), rot(-angle)];
- if (ratio) [projection, inverse] = [app(scale(Math.pow(1.1, ratio)), projection), app(inverse, scale(Math.pow(1.1, -ratio)))];
+ if (ratio)
+ [projection, inverse] = [
+ app(scale(Math.pow(1.1, ratio)), projection),
+ app(inverse, scale(Math.pow(1.1, -ratio)))
+ ];
if (mirrorH) [projection, inverse] = [app(flipH, projection), app(inverse, flipH)];
if (mirrorV) [projection, inverse] = [app(flipV, projection), app(inverse, flipV)];
if (shiftX || shiftY) {
@@ -208,6 +223,14 @@ window.UISubmap = (function () {
});
}, 1000);
+ // calculate x y extreme points of viewBox
+ function getViewBoxExtent() {
+ return [
+ [Math.abs(viewX / scale), Math.abs(viewY / scale)],
+ [Math.abs(viewX / scale) + graphWidth / scale, Math.abs(viewY / scale) + graphHeight / scale]
+ ];
+ }
+
// Create submap from the current map. Submap limits defined by the current window size (canvas viewport)
const generateSubmap = debounce(function () {
WARN && console.warn("Resampling current map");
@@ -244,7 +267,10 @@ window.UISubmap = (function () {
// fix scale
distanceScaleInput.value = distanceScaleOutput.value = rn((distanceScale = distanceScaleOutput.value / scale), 2);
- populationRateInput.value = populationRateOutput.value = rn((populationRate = populationRateOutput.value / scale), 2);
+ populationRateInput.value = populationRateOutput.value = rn(
+ (populationRate = populationRateOutput.value / scale),
+ 2
+ );
customization = 0;
startResample(options);
}, 1000);
@@ -253,9 +279,9 @@ window.UISubmap = (function () {
// Do model changes with Submap.resample then do view changes if needed
resetZoom(0);
let oldstate = {
- grid: deepCopy(grid),
- pack: deepCopy(pack),
- notes: deepCopy(notes),
+ grid: structuredClone(grid),
+ pack: structuredClone(pack),
+ notes: structuredClone(notes),
seed,
graphWidth,
graphHeight
diff --git a/modules/ui/units-editor.js b/modules/ui/units-editor.js
index 37645978..3f5feacf 100644
--- a/modules/ui/units-editor.js
+++ b/modules/ui/units-editor.js
@@ -3,8 +3,8 @@ function editUnits() {
closeDialogs("#unitsEditor, .stable");
$("#unitsEditor").dialog();
- if (modules.editUnits) return;
- modules.editUnits = true;
+ if (fmg.modules.editUnits) return;
+ fmg.modules.editUnits = true;
$("#unitsEditor").dialog({
title: "Units Editor",
diff --git a/modules/ui/world-configurator.js b/modules/ui/world-configurator.js
index 73a3621e..76a91785 100644
--- a/modules/ui/world-configurator.js
+++ b/modules/ui/world-configurator.js
@@ -34,8 +34,8 @@ function editWorld() {
updateGlobeTemperature();
updateGlobePosition();
- if (modules.editWorld) return;
- modules.editWorld = true;
+ if (fmg.modules.editWorld) return;
+ fmg.modules.editWorld = true;
document.getElementById("worldControls").addEventListener("input", e => updateWorld(e.target));
globe.select("#globeWindArrows").on("click", changeWind);
@@ -78,11 +78,15 @@ function editWorld() {
const unit = distanceUnitInput.value;
const meridian = toKilometer(eqD * 2 * scale);
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
- document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
+ document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(
+ graphHeight * scale
+ )} ${unit}`;
document.getElementById("meridianLength").innerHTML = rn(eqD * 2);
document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
- document.getElementById("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
+ document.getElementById("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(
+ mc.latS
+ )} ${rn(mc.lonE)}°E`;
function toKilometer(v) {
if (unit === "km") return v;
@@ -110,8 +114,12 @@ function editWorld() {
const tPole = +document.getElementById("temperaturePoleOutput").value;
document.getElementById("temperaturePoleF").innerHTML = rn((tPole * 9) / 5 + 32);
globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin)));
- globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 2) / 3 - tMin) / (tMax - tMin)));
- globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin)));
+ globe
+ .selectAll(".tempGradient60")
+ .attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 2) / 3 - tMin) / (tMax - tMin)));
+ globe
+ .selectAll(".tempGradient30")
+ .attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin)));
globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin)));
}
diff --git a/modules/ui/zones-editor.js b/modules/ui/zones-editor.js
index 759447dd..8752598c 100644
--- a/modules/ui/zones-editor.js
+++ b/modules/ui/zones-editor.js
@@ -8,8 +8,8 @@ function editZones() {
updateFilters();
zonesEditorAddLines();
- if (modules.editZones) return;
- modules.editZones = true;
+ if (fmg.modules.editZones) return;
+ fmg.modules.editZones = true;
$("#zonesEditor").dialog({
title: "Zones Editor",
@@ -61,7 +61,8 @@ function editZones() {
const filterSelect = document.getElementById("zonesFilterType");
const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
- filterSelect.innerHTML = "
all " + types.map(type => `
${type} `).join("");
+ filterSelect.innerHTML =
+ "
all " + types.map(type => `
${type} `).join("");
filterSelect.value = typeToFilterBy;
}
@@ -80,9 +81,12 @@ function editZones() {
const fill = zoneEl.getAttribute("fill");
const area = getArea(d3.sum(c.map(i => pack.cells.area[i])));
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate;
- const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
+ const urban =
+ d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
const population = rural + urban;
- const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
+ const populationTip = `Total population: ${si(population)}; Rural population: ${si(
+ rural
+ )}; Urban population: ${si(urban)}. Click to change`;
const inactive = zoneEl.style.display === "none";
const focused = defs.select("#fog #focus" + zoneEl.id).size();
@@ -98,8 +102,12 @@ function editZones() {
${si(population)}
-
-
+
+
`;
});
@@ -109,7 +117,9 @@ function editZones() {
// update footer
const totalArea = getArea(graphWidth * graphHeight);
zonesFooterArea.dataset.area = totalArea;
- const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate;
+ const totalPop =
+ (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) *
+ populationRate;
zonesFooterPopulation.dataset.population = totalPop;
zonesFooterNumber.innerHTML = /* html */ `${filteredZones.length} of ${zones.length}`;
zonesFooterCells.innerHTML = pack.cells.i.length;
@@ -150,7 +160,13 @@ function editZones() {
zonesEditorAddLines();
}
- $(body).sortable({items: "div.states", handle: ".icon-resize-vertical", containment: "parent", axis: "y", update: movezone});
+ $(body).sortable({
+ items: "div.states",
+ handle: ".icon-resize-vertical",
+ containment: "parent",
+ axis: "y",
+ update: movezone
+ });
function movezone(ev, ui) {
const zone = $("#" + ui.item.attr("data-id"));
const prev = $("#" + ui.item.prev().attr("data-id"));
@@ -174,7 +190,11 @@ function editZones() {
$("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
tip("Click to select a zone, drag to paint a zone", true);
- viewbox.style("cursor", "crosshair").on("click", selectZoneOnMapClick).call(d3.drag().on("start", dragZoneBrush)).on("touchmove mousemove", moveZoneBrush);
+ viewbox
+ .style("cursor", "crosshair")
+ .on("click", selectZoneOnMapClick)
+ .call(d3.drag().on("start", dragZoneBrush))
+ .on("touchmove mousemove", moveZoneBrush);
body.querySelector("div").classList.add("selected");
zones.selectAll("g").each(function () {
@@ -285,7 +305,8 @@ function editZones() {
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
zonesFooter.style.display = "block";
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "all"));
- if (!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
+ if (!close)
+ $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
restoreDefaultEvents();
clearMainTip();
@@ -356,7 +377,8 @@ function editZones() {
body.querySelectorAll(":scope > div").forEach(function (el) {
el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + "%";
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100, 2) + "%";
- el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + "%";
+ el.querySelector(".culturePopulation").innerHTML =
+ rn((+el.dataset.population / totalPopulation) * 100, 2) + "%";
});
} else {
body.dataset.type = "absolute";
@@ -369,7 +391,13 @@ function editZones() {
const description = "Unknown zone";
const type = "Unknown";
const fill = "url(#hatch" + (id.slice(4) % 42) + ")";
- zones.append("g").attr("id", id).attr("data-description", description).attr("data-type", type).attr("data-cells", "").attr("fill", fill);
+ zones
+ .append("g")
+ .attr("id", id)
+ .attr("data-description", description)
+ .attr("data-type", type)
+ .attr("data-cells", "")
+ .attr("fill", fill);
zonesEditorAddLines();
}
@@ -411,13 +439,19 @@ function editZones() {
const burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell));
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate);
- const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization);
+ const urban = rn(
+ d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization
+ );
const total = rural + urban;
const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = /* html */ `Rural: Urban:
-
- Total population: ${l(total)} ⇒ ${l(total)} (100 %)
`;
+
+ Total population: ${l(total)} ⇒ ${l(
+ total
+ )} (100 %)
`;
const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
diff --git a/modules/zoom.js b/modules/zoom.js
new file mode 100644
index 00000000..d874b5f0
--- /dev/null
+++ b/modules/zoom.js
@@ -0,0 +1,52 @@
+"use strict";
+
+// temporary expose to global
+let scale = 1;
+let viewX = 0;
+let viewY = 0;
+
+window.Zoom = (function () {
+ function onZoom() {
+ const {k, x, y} = d3.event.transform;
+
+ const isScaleChanged = Boolean(scale - k);
+ const isPositionChanged = Boolean(viewX - x || viewY - y);
+ if (!isScaleChanged && !isPositionChanged) return;
+
+ scale = k;
+ viewX = x;
+ viewY = y;
+
+ handleZoom(isScaleChanged, isPositionChanged);
+ }
+ const onZoomDebouced = debounce(onZoom, 50);
+ const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", onZoomDebouced);
+
+ // zoom to a specific point
+ function to(x, y, z = 8, d = 2000) {
+ const transform = d3.zoomIdentity.translate(x * -z + graphWidth / 2, y * -z + graphHeight / 2).scale(z);
+ svg.transition().duration(d).call(zoom.transform, transform);
+ }
+
+ // reset zoom to initial
+ function reset(d = 1000) {
+ svg.transition().duration(d).call(zoom.transform, d3.zoomIdentity);
+ }
+
+ function scaleExtent([min, max]) {
+ zoom.scaleExtent([min, max]);
+ }
+
+ function translateExtent([x1, y1, x2, y2]) {
+ zoom.translateExtent([
+ [x1, y1],
+ [x2, y2]
+ ]);
+ }
+
+ function scaleTo(element, scale) {
+ zoom.scaleTo(element, scale);
+ }
+
+ return {to, reset, scaleExtent, translateExtent, scaleTo};
+})();
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..ebb5e08c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "fantasy-map-generator",
+ "version": "1.87.04",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "typescript": "^4.7.4",
+ "vite": "^2.9.12"
+ }
+}
diff --git a/run_python_server.bat b/run_python_server.bat
deleted file mode 100644
index b74d34c1..00000000
--- a/run_python_server.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-start chrome.exe http://localhost:8000/
-@echo off
-python -m http.server 8000
\ No newline at end of file
diff --git a/src/config/logging.ts b/src/config/logging.ts
new file mode 100644
index 00000000..83d2e4f9
--- /dev/null
+++ b/src/config/logging.ts
@@ -0,0 +1,7 @@
+import {PRODUCTION} from "../constants";
+
+export const DEBUG = Boolean(localStorage.getItem("debug"));
+export const INFO = DEBUG || !PRODUCTION;
+export const TIME = DEBUG || !PRODUCTION;
+export const WARN = true;
+export const ERROR = true;
diff --git a/src/constants/index.ts b/src/constants/index.ts
new file mode 100644
index 00000000..20f1f125
--- /dev/null
+++ b/src/constants/index.ts
@@ -0,0 +1,9 @@
+export const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
+
+// detect device
+export const MOBILE = window.innerWidth < 600 || window.navigator.userAgentData?.mobile;
+
+// typed arrays max values
+export const UINT8_MAX = 255;
+export const UINT16_MAX = 65535;
+export const UINT32_MAX = 4294967295;
diff --git a/main.js b/src/main.ts
similarity index 78%
rename from main.js
rename to src/main.ts
index f443111d..8fe716a4 100644
--- a/main.js
+++ b/src/main.ts
@@ -1,26 +1,22 @@
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License
// https://github.com/Azgaar/Fantasy-Map-Generator
-"use strict";
-// set debug options
-const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
-const DEBUG = localStorage.getItem("debug");
-const INFO = DEBUG || !PRODUCTION;
-const TIME = DEBUG || !PRODUCTION;
-const WARN = true;
-const ERROR = true;
+import {PRODUCTION, UINT16_MAX} from "./constants";
+import {INFO, TIME, WARN, ERROR} from "./config/logging";
+import {createTypedArray} from "./utils";
+import {shouldRegenerateGrid, generateGrid, calculateVoronoi, getPackPolygon, isLand} from "./utils/graphUtils";
+import {drawRivers, drawStates, drawBorders} from "../modules/ui/layers";
+import {invokeActiveZooming} from "../modules/activeZooming";
+import {applyStoredOptions, applyMapSize, randomizeOptions} from "../modules/ui/options";
+import {locked} from "../modules/ui/general";
-// detect device
-const MOBILE = window.innerWidth < 600 || navigator.userAgentData?.mobile;
-
-// typed arrays max values
-const UINT8_MAX = 255;
-const UINT16_MAX = 65535;
-const UINT32_MAX = 4294967295;
+globalThis.fmg = {
+ modules: {}
+};
if (PRODUCTION && "serviceWorker" in navigator) {
window.addEventListener("load", () => {
- navigator.serviceWorker.register("./sw.js").catch(err => {
+ navigator.serviceWorker.register("../sw.js").catch(err => {
console.error("ServiceWorker registration failed: ", err);
});
});
@@ -29,170 +25,47 @@ if (PRODUCTION && "serviceWorker" in navigator) {
"beforeinstallprompt",
async event => {
event.preventDefault();
- const Installation = await import("./modules/dynamic/installation.js");
+ const Installation = await import("../modules/dynamic/installation.js");
Installation.init(event);
},
{once: true}
);
}
-// append svg layers (in default order)
-let svg = d3.select("#map");
-let defs = svg.select("#deftemp");
-let viewbox = svg.select("#viewbox");
-let scaleBar = svg.select("#scaleBar");
-let legend = svg.append("g").attr("id", "legend");
-let ocean = viewbox.append("g").attr("id", "ocean");
-let oceanLayers = ocean.append("g").attr("id", "oceanLayers");
-let oceanPattern = ocean.append("g").attr("id", "oceanPattern");
-let lakes = viewbox.append("g").attr("id", "lakes");
-let landmass = viewbox.append("g").attr("id", "landmass");
-let texture = viewbox.append("g").attr("id", "texture");
-let terrs = viewbox.append("g").attr("id", "terrs");
-let biomes = viewbox.append("g").attr("id", "biomes");
-let cells = viewbox.append("g").attr("id", "cells");
-let gridOverlay = viewbox.append("g").attr("id", "gridOverlay");
-let coordinates = viewbox.append("g").attr("id", "coordinates");
-let compass = viewbox.append("g").attr("id", "compass");
-let rivers = viewbox.append("g").attr("id", "rivers");
-let terrain = viewbox.append("g").attr("id", "terrain");
-let relig = viewbox.append("g").attr("id", "relig");
-let cults = viewbox.append("g").attr("id", "cults");
-let regions = viewbox.append("g").attr("id", "regions");
-let statesBody = regions.append("g").attr("id", "statesBody");
-let statesHalo = regions.append("g").attr("id", "statesHalo");
-let provs = viewbox.append("g").attr("id", "provs");
-let zones = viewbox.append("g").attr("id", "zones").style("display", "none");
-let borders = viewbox.append("g").attr("id", "borders");
-let stateBorders = borders.append("g").attr("id", "stateBorders");
-let provinceBorders = borders.append("g").attr("id", "provinceBorders");
-let routes = viewbox.append("g").attr("id", "routes");
-let roads = routes.append("g").attr("id", "roads");
-let trails = routes.append("g").attr("id", "trails");
-let searoutes = routes.append("g").attr("id", "searoutes");
-let temperature = viewbox.append("g").attr("id", "temperature");
-let coastline = viewbox.append("g").attr("id", "coastline");
-let ice = viewbox.append("g").attr("id", "ice").style("display", "none");
-let prec = viewbox.append("g").attr("id", "prec").style("display", "none");
-let population = viewbox.append("g").attr("id", "population");
-let emblems = viewbox.append("g").attr("id", "emblems").style("display", "none");
-let labels = viewbox.append("g").attr("id", "labels");
-let icons = viewbox.append("g").attr("id", "icons");
-let burgIcons = icons.append("g").attr("id", "burgIcons");
-let anchors = icons.append("g").attr("id", "anchors");
-let armies = viewbox.append("g").attr("id", "armies").style("display", "none");
-let markers = viewbox.append("g").attr("id", "markers");
-let fogging = viewbox.append("g").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none");
-let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none");
-let debug = viewbox.append("g").attr("id", "debug");
-
-// lake and coast groups
-lakes.append("g").attr("id", "freshwater");
-lakes.append("g").attr("id", "salt");
-lakes.append("g").attr("id", "sinkhole");
-lakes.append("g").attr("id", "frozen");
-lakes.append("g").attr("id", "lava");
-lakes.append("g").attr("id", "dry");
-coastline.append("g").attr("id", "sea_island");
-coastline.append("g").attr("id", "lake_island");
-
-labels.append("g").attr("id", "states");
-labels.append("g").attr("id", "addedLabels");
-
-let burgLabels = labels.append("g").attr("id", "burgLabels");
-burgIcons.append("g").attr("id", "cities");
-burgLabels.append("g").attr("id", "cities");
-anchors.append("g").attr("id", "cities");
-
-burgIcons.append("g").attr("id", "towns");
-burgLabels.append("g").attr("id", "towns");
-anchors.append("g").attr("id", "towns");
-
-// population groups
-population.append("g").attr("id", "rural");
-population.append("g").attr("id", "urban");
-
-// emblem groups
-emblems.append("g").attr("id", "burgEmblems");
-emblems.append("g").attr("id", "provinceEmblems");
-emblems.append("g").attr("id", "stateEmblems");
-
-// fogging
-fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
-fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%").attr("fill", "#e8f0f6").attr("filter", "url(#splotch)");
-
-// assign events separately as not a viewbox child
-scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits());
-legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
-
-// main data variables
-let grid = {}; // initial graph based on jittered square grid and data
-let pack = {}; // packed graph and data
-let seed;
-let mapId;
-let mapHistory = [];
-let elSelected;
-let modules = {};
-let notes = [];
-let rulers = new Rulers();
-let customization = 0;
-
-let biomesData = applyDefaultBiomesSystem();
-let nameBases = Names.getNameBases(); // cultures-related data
-
-let color = d3.scaleSequential(d3.interpolateSpectral); // default color scheme
-const lineGen = d3.line().curve(d3.curveBasis); // d3 line generator with default curve interpolation
-
-// d3 zoom behavior
-let scale = 1;
-let viewX = 0;
-let viewY = 0;
-
-function onZoom() {
- const {k, x, y} = d3.event.transform;
-
- const isScaleChanged = Boolean(scale - k);
- const isPositionChanged = Boolean(viewX - x || viewY - y);
- if (!isScaleChanged && !isPositionChanged) return;
-
- scale = k;
- viewX = x;
- viewY = y;
-
- handleZoom(isScaleChanged, isPositionChanged);
-}
-const onZoomDebouced = debounce(onZoom, 50);
-const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", onZoomDebouced);
-
// default options
-let options = {
+options = {
pinNotes: false,
showMFCGMap: true,
winds: [225, 45, 225, 315, 135, 315],
stateLabelsMode: "auto"
};
-let mapCoordinates = {}; // map coordinates on globe
-let populationRate = +document.getElementById("populationRateInput").value;
-let distanceScale = +document.getElementById("distanceScaleInput").value;
-let urbanization = +document.getElementById("urbanizationInput").value;
-let urbanDensity = +document.getElementById("urbanDensityInput").value;
-let statesNeutral = 1; // statesEditor growth parameter
+mapCoordinates = {}; // map coordinates on globe
+populationRate = +byId("populationRateInput").value;
+distanceScale = +byId("distanceScaleInput").value;
+urbanization = +byId("urbanizationInput").value;
+urbanDensity = +byId("urbanDensityInput").value;
+statesNeutral = 1; // statesEditor growth parameter
applyStoredOptions();
+rulers = new Rulers();
+biomesData = Biomes.getDefault();
+nameBases = Names.getNameBases(); // cultures-related data
+
+color = d3.scaleSequential(d3.interpolateSpectral); // default color scheme
+lineGen = d3.line().curve(d3.curveBasis); // d3 line generator with default curve interpolation
+
// voronoi graph extension, cannot be changed after generation
-let graphWidth = +mapWidthInput.value;
-let graphHeight = +mapHeightInput.value;
+graphWidth = +byId("mapWidthInput").value;
+graphHeight = +byId("mapHeightInput").value;
// svg canvas resolution, can be changed
-let svgWidth = graphWidth;
-let svgHeight = graphHeight;
+svgWidth = graphWidth;
+svgHeight = 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);
-oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
+defineSvg(graphWidth, graphHeight);
-document.addEventListener("DOMContentLoaded", async () => {
+document.on("DOMContentLoaded", async () => {
if (!location.hostname) {
const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
alertMessage.innerHTML = /* html */ `Fantasy Map Generator cannot run serverless. Follow the instructions on how you can
@@ -321,7 +194,7 @@ function focusOn() {
if (cellParam) {
const cell = +params.get("cell");
const [x, y] = pack.cells.p[cell];
- zoomTo(x, y, scale, 1600);
+ Zoom.to(x, y, scale, 1600);
return;
}
@@ -330,13 +203,13 @@ function focusOn() {
if (!burg) return;
const {x, y} = burg;
- zoomTo(x, y, scale, 1600);
+ Zoom.to(x, y, scale, 1600);
return;
}
const x = +params.get("x") || graphWidth / 2;
const y = +params.get("y") || graphHeight / 2;
- zoomTo(x, y, scale, 1600);
+ Zoom.to(x, y, scale, 1600);
}
}
@@ -399,183 +272,18 @@ function findBurgForMFCG(params) {
});
}
- zoomTo(b.x, b.y, 8, 1600);
+ Zoom.to(b.x, b.y, 8, 1600);
invokeActiveZooming();
tip("Here stands the glorious city of " + b.name, true, "success", 15000);
}
-// apply default biomes data
-function applyDefaultBiomesSystem() {
- const name = [
- "Marine",
- "Hot desert",
- "Cold desert",
- "Savanna",
- "Grassland",
- "Tropical seasonal forest",
- "Temperate deciduous forest",
- "Tropical rainforest",
- "Temperate rainforest",
- "Taiga",
- "Tundra",
- "Glacier",
- "Wetland"
- ];
- const color = ["#466eab", "#fbe79f", "#b5b887", "#d2d082", "#c8d68f", "#b6d95d", "#29bc56", "#7dcb35", "#409c43", "#4b6b32", "#96784b", "#d5e7eb", "#0b9131"];
- const habitability = [0, 4, 10, 22, 30, 50, 100, 80, 90, 12, 4, 0, 12];
- const iconsDensity = [0, 3, 2, 120, 120, 120, 120, 150, 150, 100, 5, 0, 150];
- const icons = [
- {},
- {dune: 3, cactus: 6, deadTree: 1},
- {dune: 9, deadTree: 1},
- {acacia: 1, grass: 9},
- {grass: 1},
- {acacia: 8, palm: 1},
- {deciduous: 1},
- {acacia: 5, palm: 3, deciduous: 1, swamp: 1},
- {deciduous: 6, swamp: 1},
- {conifer: 1},
- {grass: 1},
- {},
- {swamp: 1}
- ];
- const cost = [10, 200, 150, 60, 50, 70, 70, 80, 90, 200, 1000, 5000, 150]; // biome movement cost
- const biomesMartix = [
- // hot ↔ cold [>19°C; <-4°C]; dry ↕ wet
- new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10]),
- new Uint8Array([3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 10, 10, 10]),
- new Uint8Array([5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 9, 9, 9, 9, 10, 10, 10]),
- new Uint8Array([5, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10]),
- new Uint8Array([7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10, 10])
- ];
-
- // parse icons weighted array into a simple array
- for (let i = 0; i < icons.length; i++) {
- const parsed = [];
- for (const icon in icons[i]) {
- for (let j = 0; j < icons[i][icon]; j++) {
- parsed.push(icon);
- }
- }
- icons[i] = parsed;
- }
-
- return {i: d3.range(0, name.length), name, color, biomesMartix, habitability, iconsDensity, icons, cost};
-}
-
-function handleZoom(isScaleChanged, isPositionChanged) {
- viewbox.attr("transform", `translate(${viewX} ${viewY}) scale(${scale})`);
-
- if (isPositionChanged) drawCoordinates();
-
- if (isScaleChanged) {
- invokeActiveZooming();
- drawScaleBar(scale);
- }
-
- // zoom image converter overlay
- if (customization === 1) {
- const canvas = document.getElementById("canvas");
- if (!canvas || canvas.style.opacity === "0") return;
-
- const img = document.getElementById("imageToConvert");
- if (!img) return;
-
- const ctx = canvas.getContext("2d");
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.setTransform(scale, 0, 0, scale, viewX, viewY);
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- }
-}
-
-// Zoom to a specific point
-function zoomTo(x, y, z = 8, d = 2000) {
- const transform = d3.zoomIdentity.translate(x * -z + graphWidth / 2, y * -z + graphHeight / 2).scale(z);
- svg.transition().duration(d).call(zoom.transform, transform);
-}
-
-// Reset zoom to initial
-function resetZoom(d = 1000) {
- svg.transition().duration(d).call(zoom.transform, d3.zoomIdentity);
-}
-
-// calculate x y extreme points of viewBox
-function getViewBoxExtent() {
- return [
- [Math.abs(viewX / scale), Math.abs(viewY / scale)],
- [Math.abs(viewX / scale) + graphWidth / scale, Math.abs(viewY / scale) + graphHeight / scale]
- ];
-}
-
-// active zooming feature
-function invokeActiveZooming() {
- if (coastline.select("#sea_island").size() && +coastline.select("#sea_island").attr("auto-filter")) {
- // toggle shade/blur filter for coatline on zoom
- const filter = scale > 1.5 && scale <= 2.6 ? null : scale > 2.6 ? "url(#blurFilter)" : "url(#dropShadow)";
- coastline.select("#sea_island").attr("filter", filter);
- }
-
- // rescale labels on zoom
- if (labels.style("display") !== "none") {
- labels.selectAll("g").each(function () {
- if (this.id === "burgLabels") return;
- const desired = +this.dataset.size;
- const relative = Math.max(rn((desired + desired / scale) / 2, 2), 1);
- if (rescaleLabels.checked) this.setAttribute("font-size", relative);
-
- const hidden = hideLabels.checked && (relative * scale < 6 || relative * scale > 60);
- if (hidden) this.classList.add("hidden");
- else this.classList.remove("hidden");
- });
- }
-
- // rescale emblems on zoom
- if (emblems.style("display") !== "none") {
- emblems.selectAll("g").each(function () {
- const size = this.getAttribute("font-size") * scale;
- const hidden = hideEmblems.checked && (size < 25 || size > 300);
- if (hidden) this.classList.add("hidden");
- else this.classList.remove("hidden");
- if (!hidden && window.COArenderer && this.children.length && !this.children[0].getAttribute("href")) renderGroupCOAs(this);
- });
- }
-
- // turn off ocean pattern if scale is big (improves performance)
- oceanPattern
- .select("rect")
- .attr("fill", scale > 10 ? "#fff" : "url(#oceanic)")
- .attr("opacity", scale > 10 ? 0.2 : null);
-
- // change states halo width
- if (!customization) {
- const desired = +statesHalo.attr("data-width");
- const haloSize = rn(desired / scale ** 0.8, 2);
- statesHalo.attr("stroke-width", haloSize).style("display", haloSize > 0.1 ? "block" : "none");
- }
-
- // rescale map markers
- +markers.attr("rescale") &&
- pack.markers?.forEach(marker => {
- const {i, x, y, size = 30, hidden} = marker;
- const el = !hidden && document.getElementById(`marker${i}`);
- if (!el) return;
-
- const zoomedSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
- el.setAttribute("width", zoomedSize);
- el.setAttribute("height", zoomedSize);
- el.setAttribute("x", rn(x - zoomedSize / 2, 1));
- el.setAttribute("y", rn(y - zoomedSize, 1));
- });
-
- // rescale rulers to have always the same size
- if (ruler.style("display") !== "none") {
- const size = rn((10 / scale ** 0.3) * 2, 2);
- ruler.selectAll("text").attr("font-size", size);
- }
-}
-
async function renderGroupCOAs(g) {
- const [group, type] = g.id === "burgEmblems" ? [pack.burgs, "burg"] : g.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
+ const [group, type] =
+ g.id === "burgEmblems"
+ ? [pack.burgs, "burg"]
+ : g.id === "provinceEmblems"
+ ? [pack.provinces, "province"]
+ : [pack.states, "state"];
for (let use of g.children) {
const i = +use.dataset.i;
const id = type + "COA" + i;
@@ -1547,7 +1255,9 @@ function addZones(number = 1) {
const invader = ra(atWar);
const target = invader.diplomacy.findIndex(d => d === "Enemy");
- const cell = ra(cells.i.filter(i => cells.state[i] === target && cells.c[i].some(c => cells.state[c] === invader.i)));
+ const cell = ra(
+ cells.i.filter(i => cells.state[i] === target && cells.c[i].some(c => cells.state[c] === invader.i))
+ );
if (!cell) return;
const cellsArray = [],
@@ -1589,7 +1299,9 @@ function addZones(number = 1) {
const neib = ra(state.neighbors.filter(n => n && !states[n].removed));
if (!neib) return;
- const cell = cells.i.find(i => cells.state[i] === state.i && !state.removed && cells.c[i].some(c => cells.state[c] === neib));
+ const cell = cells.i.find(
+ i => cells.state[i] === state.i && !state.removed && cells.c[i].some(c => cells.state[c] === neib)
+ );
const cellsArray = [];
const queue = [];
if (cell) queue.push(cell);
@@ -1610,7 +1322,17 @@ function addZones(number = 1) {
});
}
- const rebels = rw({Rebels: 5, Insurgents: 2, Mutineers: 1, Rioters: 1, Separatists: 1, Secessionists: 1, Insurrection: 2, Rebellion: 1, Conspiracy: 2});
+ const rebels = rw({
+ Rebels: 5,
+ Insurgents: 2,
+ Mutineers: 1,
+ Rioters: 1,
+ Separatists: 1,
+ Secessionists: 1,
+ Insurrection: 2,
+ Rebellion: 1,
+ Conspiracy: 2
+ });
const name = getAdjective(states[neib].name) + " " + rebels;
zonesData.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"});
}
@@ -1619,7 +1341,14 @@ function addZones(number = 1) {
const organized = ra(pack.religions.filter(r => r.type === "Organized"));
if (!organized) return;
- const cell = ra(cells.i.filter(i => cells.religion[i] && cells.religion[i] !== organized.i && cells.c[i].some(c => cells.religion[c] === organized.i)));
+ const cell = ra(
+ cells.i.filter(
+ i =>
+ cells.religion[i] &&
+ cells.religion[i] !== organized.i &&
+ cells.c[i].some(c => cells.religion[c] === organized.i)
+ )
+ );
if (!cell) return;
const target = cells.religion[cell];
const cellsArray = [],
@@ -1685,11 +1414,54 @@ function addZones(number = 1) {
});
}
- const adjective = () => ra(["Great", "Silent", "Severe", "Blind", "Unknown", "Loud", "Deadly", "Burning", "Bloody", "Brutal", "Fatal"]);
- const animal = () => ra(["Ape", "Bear", "Boar", "Cat", "Cow", "Dog", "Pig", "Fox", "Bird", "Horse", "Rat", "Raven", "Sheep", "Spider", "Wolf"]);
- const color = () => ra(["Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]);
+ const adjective = () =>
+ ra(["Great", "Silent", "Severe", "Blind", "Unknown", "Loud", "Deadly", "Burning", "Bloody", "Brutal", "Fatal"]);
+ const animal = () =>
+ ra([
+ "Ape",
+ "Bear",
+ "Boar",
+ "Cat",
+ "Cow",
+ "Dog",
+ "Pig",
+ "Fox",
+ "Bird",
+ "Horse",
+ "Rat",
+ "Raven",
+ "Sheep",
+ "Spider",
+ "Wolf"
+ ]);
+ const color = () =>
+ ra([
+ "Golden",
+ "White",
+ "Black",
+ "Red",
+ "Pink",
+ "Purple",
+ "Blue",
+ "Green",
+ "Yellow",
+ "Amber",
+ "Orange",
+ "Brown",
+ "Grey"
+ ]);
- const type = rw({Fever: 5, Pestilence: 2, Flu: 2, Pox: 2, Smallpox: 2, Plague: 4, Cholera: 2, Dropsy: 1, Leprosy: 2});
+ const type = rw({
+ Fever: 5,
+ Pestilence: 2,
+ Flu: 2,
+ Pox: 2,
+ Smallpox: 2,
+ Plague: 4,
+ Cholera: 2,
+ Dropsy: 1,
+ Leprosy: 2
+ });
const name = rw({[color()]: 4, [animal()]: 2, [adjective()]: 1}) + " " + type;
zonesData.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
}
@@ -1812,7 +1584,9 @@ function addZones(number = 1) {
meanFlux = d3.mean(fl),
maxFlux = d3.max(fl),
flux = (maxFlux - meanFlux) / 2 + meanFlux;
- const rivers = cells.i.filter(i => !used[i] && cells.h[i] < 50 && cells.r[i] && cells.fl[i] > flux && cells.burg[i]);
+ const rivers = cells.i.filter(
+ i => !used[i] && cells.h[i] < 50 && cells.r[i] && cells.fl[i] > flux && cells.burg[i]
+ );
if (!rivers.length) return;
const cell = +ra(rivers),
@@ -1923,7 +1697,7 @@ const regenerateMap = debounce(async function (options) {
closeDialogs("#worldConfigurator, #options3d");
customization = 0;
- resetZoom(1000);
+ Zoom.reset(1000);
undraw();
await generate(options);
restoreLayers();
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
new file mode 100644
index 00000000..a2d9e3fa
--- /dev/null
+++ b/src/types/global.d.ts
@@ -0,0 +1,7 @@
+interface Navigator {
+ userAgentData?: {
+ mobile: boolean;
+ };
+}
+
+type UnknownObject = {[key: string]: unknown};
diff --git a/src/utils/arrayUtils.ts b/src/utils/arrayUtils.ts
new file mode 100644
index 00000000..74e1623c
--- /dev/null
+++ b/src/utils/arrayUtils.ts
@@ -0,0 +1,33 @@
+import {UINT16_MAX, UINT32_MAX, UINT8_MAX} from "../constants";
+
+export function last(array: T[]) {
+ return array[array.length - 1];
+}
+
+export function unique(array: T[]) {
+ return [...new Set(array)];
+}
+
+function getTypedArray(maxValue: number) {
+ console.assert(
+ Number.isInteger(maxValue) && maxValue >= 0 && maxValue <= UINT32_MAX,
+ `Array maxValue must be an integer between 0 and ${UINT32_MAX}, got ${maxValue}`
+ );
+
+ if (maxValue <= UINT8_MAX) return Uint8Array;
+ if (maxValue <= UINT16_MAX) return Uint16Array;
+ if (maxValue <= UINT32_MAX) return Uint32Array;
+ return Uint32Array;
+}
+
+interface ICreateTypedArray {
+ maxValue: number;
+ length: number;
+ from: ArrayLike;
+}
+
+export function createTypedArray({maxValue, length, from}: ICreateTypedArray) {
+ const typedArray = getTypedArray(maxValue);
+ if (!from) return new typedArray(length);
+ return typedArray.from(from);
+}
diff --git a/utils/graphUtils.js b/src/utils/graphUtils.js
similarity index 92%
rename from utils/graphUtils.js
rename to src/utils/graphUtils.js
index 75b64253..64936b26 100644
--- a/utils/graphUtils.js
+++ b/src/utils/graphUtils.js
@@ -1,8 +1,8 @@
-"use strict";
-// FMG utils related to graph
+import {TIME} from "../config/logging";
+import {createTypedArray} from ".";
// check if new grid graph should be generated or we can use the existing one
-function shouldRegenerateGrid(grid) {
+export function shouldRegenerateGrid(grid) {
const cellsDesired = +byId("pointsInput").dataset.cells;
if (cellsDesired !== grid.cellsDesired) return true;
@@ -13,7 +13,7 @@ function shouldRegenerateGrid(grid) {
return grid.spacing !== newSpacing || grid.cellsX !== newCellsX || grid.cellsY !== newCellsY;
}
-function generateGrid() {
+export function generateGrid() {
Math.random = aleaPRNG(seed); // reset PRNG
const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints();
const {cells, vertices} = calculateVoronoi(points, boundary);
@@ -36,7 +36,7 @@ function placePoints() {
}
// calculate Delaunay and then Voronoi diagram
-function calculateVoronoi(points, boundary) {
+export function calculateVoronoi(points, boundary) {
TIME && console.time("calculateDelaunay");
const allPoints = points.concat(boundary);
const delaunay = Delaunator.from(allPoints);
@@ -95,8 +95,11 @@ function getJitteredGrid(width, height, spacing) {
}
// return cell index on a regular square grid
-function findGridCell(x, y, grid) {
- return Math.floor(Math.min(y / grid.spacing, grid.cellsY - 1)) * grid.cellsX + Math.floor(Math.min(x / grid.spacing, grid.cellsX - 1));
+export function findGridCell(x, y, grid) {
+ return (
+ Math.floor(Math.min(y / grid.spacing, grid.cellsY - 1)) * grid.cellsX +
+ Math.floor(Math.min(x / grid.spacing, grid.cellsX - 1))
+ );
}
// return array of cell indexes in radius on a regular square grid
@@ -130,23 +133,23 @@ function find(x, y, radius = Infinity) {
return pack.cells.q.find(x, y, radius);
}
-// return closest cell index
-function findCell(x, y, radius = Infinity) {
- const found = pack.cells.q.find(x, y, radius);
- return found ? found[2] : undefined;
-}
-
// return array of cell indexes in radius
-function findAll(x, y, radius) {
+export function findAll(x, y, radius) {
const found = pack.cells.q.findAll(x, y, radius);
return found.map(r => r[2]);
}
// get polygon points for packed cells knowing cell id
-function getPackPolygon(i) {
+export function getPackPolygon(i) {
return pack.cells.v[i].map(v => pack.vertices.p[v]);
}
+// return closest cell index
+export function findCell(x, y, radius = Infinity) {
+ const found = pack.cells.q.find(x, y, radius);
+ return found ? found[2] : undefined;
+}
+
// get polygon points for initial cells knowing cell id
function getGridPolygon(i) {
return grid.cells.v[i].map(v => grid.vertices.p[v]);
@@ -215,12 +218,12 @@ function* poissonDiscSampler(x0, y0, x1, y1, r, k = 3) {
}
// filter land cells
-function isLand(i) {
+export function isLand(i) {
return pack.cells.h[i] >= 20;
}
// filter water cells
-function isWater(i) {
+export function isWater(i) {
return pack.cells.h[i] < 20;
}
@@ -246,7 +249,14 @@ void (function addFindAll() {
i++;
// Stop searching if this quadrant can’t contain a closer node.
- if (!(t.node = t.q.node) || (t.x1 = t.q.x0) > t.x3 || (t.y1 = t.q.y0) > t.y3 || (t.x2 = t.q.x1) < t.x0 || (t.y2 = t.q.y1) < t.y0) continue;
+ if (
+ !(t.node = t.q.node) ||
+ (t.x1 = t.q.x0) > t.x3 ||
+ (t.y1 = t.q.y0) > t.y3 ||
+ (t.x2 = t.q.x1) < t.x0 ||
+ (t.y2 = t.q.y1) < t.y0
+ )
+ continue;
// Bisect the current quadrant.
if (t.node.length) {
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 00000000..c627e1db
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1 @@
+export {last, unique, createTypedArray} from "./arrayUtils";
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..5bcbb828
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ESNext", "DOM"],
+ "moduleResolution": "Node",
+ "strict": true,
+ "sourceMap": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "noEmit": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "skipLibCheck": true
+ },
+ "include": ["src", "utils", "modules"]
+}
diff --git a/utils/arrayUtils.js b/utils/arrayUtils.js
index d872d086..cc1e325b 100644
--- a/utils/arrayUtils.js
+++ b/utils/arrayUtils.js
@@ -7,54 +7,3 @@ function last(array) {
function unique(array) {
return [...new Set(array)];
}
-
-// deep copy for Arrays (and other objects)
-function deepCopy(obj) {
- const id = x => x;
- const dcTArray = a => a.map(id);
- const dcObject = x => Object.fromEntries(Object.entries(x).map(([k, d]) => [k, dcAny(d)]));
- const dcAny = x => (x instanceof Object ? (cf.get(x.constructor) || id)(x) : x);
- // don't map keys, probably this is what we would expect
- const dcMapCore = m => [...m.entries()].map(([k, v]) => [k, dcAny(v)]);
-
- const cf = new Map([
- [Int8Array, dcTArray],
- [Uint8Array, dcTArray],
- [Uint8ClampedArray, dcTArray],
- [Int16Array, dcTArray],
- [Uint16Array, dcTArray],
- [Int32Array, dcTArray],
- [Uint32Array, dcTArray],
- [Float32Array, dcTArray],
- [Float64Array, dcTArray],
- [BigInt64Array, dcTArray],
- [BigUint64Array, dcTArray],
- [Map, m => new Map(dcMapCore(m))],
- [WeakMap, m => new WeakMap(dcMapCore(m))],
- [Array, a => a.map(dcAny)],
- [Set, s => [...s.values()].map(dcAny)],
- [Date, d => new Date(d.getTime())],
- [Object, dcObject]
- // ... extend here to implement their custom deep copy
- ]);
-
- return dcAny(obj);
-}
-
-function getTypedArray(maxValue) {
- console.assert(
- Number.isInteger(maxValue) && maxValue >= 0 && maxValue <= UINT32_MAX,
- `Array maxValue must be an integer between 0 and ${UINT32_MAX}, got ${maxValue}`
- );
-
- if (maxValue <= UINT8_MAX) return Uint8Array;
- if (maxValue <= UINT16_MAX) return Uint16Array;
- if (maxValue <= UINT32_MAX) return Uint32Array;
- return Uint32Array;
-}
-
-function createTypedArray({maxValue, length, from}) {
- const typedArray = getTypedArray(maxValue);
- if (!from) return new typedArray(length);
- return typedArray.from(from);
-}
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 00000000..4bc7d407
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,3 @@
+import {defineConfig} from "vite";
+
+export default defineConfig({});
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 00000000..cb43f076
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,220 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+esbuild-android-64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.47.tgz#ef95b42c67bcf4268c869153fa3ad1466c4cea6b"
+ integrity sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==
+
+esbuild-android-arm64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.47.tgz#4ebd7ce9fb250b4695faa3ee46fd3b0754ecd9e6"
+ integrity sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==
+
+esbuild-darwin-64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.47.tgz#e0da6c244f497192f951807f003f6a423ed23188"
+ integrity sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==
+
+esbuild-darwin-arm64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.47.tgz#cd40fd49a672fca581ed202834239dfe540a9028"
+ integrity sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==
+
+esbuild-freebsd-64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.47.tgz#8da6a14c095b29c01fc8087a16cb7906debc2d67"
+ integrity sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==
+
+esbuild-freebsd-arm64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.47.tgz#ad31f9c92817ff8f33fd253af7ab5122dc1b83f6"
+ integrity sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==
+
+esbuild-linux-32@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.47.tgz#de085e4db2e692ea30c71208ccc23fdcf5196c58"
+ integrity sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==
+
+esbuild-linux-64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.47.tgz#2a9321bbccb01f01b04cebfcfccbabeba3658ba1"
+ integrity sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==
+
+esbuild-linux-arm64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.47.tgz#b9da7b6fc4b0ca7a13363a0c5b7bb927e4bc535a"
+ integrity sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==
+
+esbuild-linux-arm@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.47.tgz#56fec2a09b9561c337059d4af53625142aded853"
+ integrity sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==
+
+esbuild-linux-mips64le@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.47.tgz#9db21561f8f22ed79ef2aedb7bbef082b46cf823"
+ integrity sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==
+
+esbuild-linux-ppc64le@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.47.tgz#dc3a3da321222b11e96e50efafec9d2de408198b"
+ integrity sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==
+
+esbuild-linux-riscv64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.47.tgz#9bd6dcd3dca6c0357084ecd06e1d2d4bf105335f"
+ integrity sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==
+
+esbuild-linux-s390x@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.47.tgz#a458af939b52f2cd32fc561410d441a51f69d41f"
+ integrity sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==
+
+esbuild-netbsd-64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.47.tgz#6388e785d7e7e4420cb01348d7483ab511b16aa8"
+ integrity sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==
+
+esbuild-openbsd-64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.47.tgz#309af806db561aa886c445344d1aacab850dbdc5"
+ integrity sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==
+
+esbuild-sunos-64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.47.tgz#3f19612dcdb89ba6c65283a7ff6e16f8afbf8aaa"
+ integrity sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==
+
+esbuild-windows-32@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.47.tgz#a92d279c8458d5dc319abcfeb30aa49e8f2e6f7f"
+ integrity sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==
+
+esbuild-windows-64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.47.tgz#2564c3fcf0c23d701edb71af8c52d3be4cec5f8a"
+ integrity sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==
+
+esbuild-windows-arm64@0.14.47:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.47.tgz#86d9db1a22d83360f726ac5fba41c2f625db6878"
+ integrity sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==
+
+esbuild@^0.14.27:
+ version "0.14.47"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.47.tgz#0d6415f6bd8eb9e73a58f7f9ae04c5276cda0e4d"
+ integrity sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==
+ optionalDependencies:
+ esbuild-android-64 "0.14.47"
+ esbuild-android-arm64 "0.14.47"
+ esbuild-darwin-64 "0.14.47"
+ esbuild-darwin-arm64 "0.14.47"
+ esbuild-freebsd-64 "0.14.47"
+ esbuild-freebsd-arm64 "0.14.47"
+ esbuild-linux-32 "0.14.47"
+ esbuild-linux-64 "0.14.47"
+ esbuild-linux-arm "0.14.47"
+ esbuild-linux-arm64 "0.14.47"
+ esbuild-linux-mips64le "0.14.47"
+ esbuild-linux-ppc64le "0.14.47"
+ esbuild-linux-riscv64 "0.14.47"
+ esbuild-linux-s390x "0.14.47"
+ esbuild-netbsd-64 "0.14.47"
+ esbuild-openbsd-64 "0.14.47"
+ esbuild-sunos-64 "0.14.47"
+ esbuild-windows-32 "0.14.47"
+ esbuild-windows-64 "0.14.47"
+ esbuild-windows-arm64 "0.14.47"
+
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+is-core-module@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
+ integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
+ dependencies:
+ has "^1.0.3"
+
+nanoid@^3.3.4:
+ version "3.3.4"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
+ integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+postcss@^8.4.13:
+ version "8.4.14"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
+ integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
+ dependencies:
+ nanoid "^3.3.4"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
+resolve@^1.22.0:
+ version "1.22.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
+ integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
+ dependencies:
+ is-core-module "^2.9.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+rollup@^2.59.0:
+ version "2.75.7"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.75.7.tgz#221ff11887ae271e37dcc649ba32ce1590aaa0b9"
+ integrity sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+typescript@^4.7.4:
+ version "4.7.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
+ integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
+
+vite@^2.9.12:
+ version "2.9.12"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.12.tgz#b1d636b0a8ac636afe9d83e3792d4895509a941b"
+ integrity sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew==
+ dependencies:
+ esbuild "^0.14.27"
+ postcss "^8.4.13"
+ resolve "^1.22.0"
+ rollup "^2.59.0"
+ optionalDependencies:
+ fsevents "~2.3.2"