From bc65e0e20719635e96046fd6ee6e2399e0697a04 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 25 Jun 2022 00:47:48 +0300 Subject: [PATCH] refactor: start files migration nightmare --- index.html | 43 ++- modules/activeZooming.js | 92 +++++ modules/biomes.js | 76 ++++ modules/burgs-and-states.js | 4 +- modules/coa-generator.js | 89 ++++- modules/cultures-generator.js | 2 +- modules/define-globals.js | 33 ++ modules/define-svg.js | 177 +++++++++ modules/dynamic/editors/states-editor.js | 4 +- modules/dynamic/heightmap-selection.js | 2 +- modules/heightmap-generator.js | 12 +- modules/lakes.js | 7 +- modules/markers-generator.js | 131 ++++++- modules/military-generator.js | 137 ++++++- modules/names-generator.js | 24 +- modules/ocean-layers.js | 2 +- modules/religions-generator.js | 3 +- modules/river-generator.js | 19 +- modules/routes-generator.js | 11 +- modules/ui/battle-screen.js | 257 ++++++++++--- modules/ui/biomes-editor.js | 27 +- modules/ui/burg-editor.js | 11 +- modules/ui/burgs-overview.js | 51 ++- modules/ui/coastline-editor.js | 8 +- modules/ui/diplomacy-editor.js | 4 +- modules/ui/editors.js | 2 - modules/ui/elevation-profile.js | 41 +- modules/ui/general.js | 12 +- modules/ui/heightmap-editor.js | 94 +++-- modules/ui/hotkeys.js | 1 - modules/ui/ice-editor.js | 4 +- modules/ui/labels-editor.js | 19 +- modules/ui/lakes-editor.js | 11 +- modules/ui/layers.js | 12 +- modules/ui/military-overview.js | 72 ++-- modules/ui/namesbase-editor.js | 57 ++- modules/ui/notes-editor.js | 4 +- modules/ui/options.js | 63 ++- modules/ui/provinces-editor.js | 93 ++++- modules/ui/regiment-editor.js | 4 +- modules/ui/regiments-overview.js | 20 +- modules/ui/relief-editor.js | 8 +- modules/ui/rivers-creator.js | 26 +- modules/ui/rivers-editor.js | 13 +- modules/ui/rivers-overview.js | 21 +- modules/ui/routes-editor.js | 8 +- modules/ui/style.js | 60 ++- modules/ui/stylePresets.js | 126 +++++- modules/ui/submap.js | 40 +- modules/ui/units-editor.js | 4 +- modules/ui/world-configurator.js | 20 +- modules/ui/zones-editor.js | 66 +++- modules/zoom.js | 52 +++ package.json | 4 +- src/config/logging.ts | 7 + src/constants/index.ts | 9 + main.js => src/main.ts | 464 ++++++----------------- src/types/global.d.ts | 7 + src/utils/arrayUtils.ts | 33 ++ {utils => src/utils}/graphUtils.js | 46 ++- src/utils/index.ts | 1 + tsconfig.json | 2 +- utils/arrayUtils.js | 51 --- vite.config.js | 3 + 64 files changed, 1990 insertions(+), 816 deletions(-) create mode 100644 modules/activeZooming.js create mode 100644 modules/biomes.js create mode 100644 modules/define-globals.js create mode 100644 modules/define-svg.js create mode 100644 modules/zoom.js create mode 100644 src/config/logging.ts create mode 100644 src/constants/index.ts rename main.js => src/main.ts (78%) create mode 100644 src/types/global.d.ts create mode 100644 src/utils/arrayUtils.ts rename {utils => src/utils}/graphUtils.js (92%) create mode 100644 src/utils/index.ts create mode 100644 vite.config.js diff --git a/index.html b/index.html index 9dfb924a..c7887d63 100644 --- a/index.html +++ b/index.html @@ -7771,7 +7771,6 @@ - @@ -7783,33 +7782,39 @@ - - - - - - - - - - - - + + + + + + + + + + + + + - + + + - - - + + + + + + - + @@ -7829,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}" > - + + `; const TempX = /* html */ `x: - + `; const Height = /* html */ `h: - + `; const Count = /* html */ `n: - + `; - if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return /* html */ `${common}${TempY}${TempX}${Height}${Count}`; + if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") + return /* html */ `${common}${TempY}${TempX}${Height}${Count}`; if (type === "Strait") return /* html */ `${common} @@ -814,7 +851,9 @@ function editHeightmap(options) { w: - + `; @@ -1042,8 +1081,8 @@ function editHeightmap(options) { viewbox.select("#heights").selectAll("*").remove(); updateHistory(); - if (modules.openImageConverter) return; - modules.openImageConverter = true; + if (fmg.modules.openImageConverter) return; + fmg.modules.openImageConverter = true; // add color pallete void (function createColorPallete() { @@ -1245,7 +1284,8 @@ function editHeightmap(options) { const assinged = []; // store assigned heights unassigned.forEach(el => { const clr = el.dataset.color; - const height = type === "hue" ? getHeightByHue(clr) : type === "lum" ? getHeightByLum(clr) : getHeightByScheme(clr); + const height = + type === "hue" ? getHeightByHue(clr) : type === "lum" ? getHeightByLum(clr) : getHeightByScheme(clr); const colorTo = color(1 - (height < 20 ? (height - 5) / 100 : height / 100)); viewbox .select("#heights") diff --git a/modules/ui/hotkeys.js b/modules/ui/hotkeys.js index d8fcaaf8..288ec5fa 100644 --- a/modules/ui/hotkeys.js +++ b/modules/ui/hotkeys.js @@ -13,7 +13,6 @@ function handleKeydown(event) { } function handleKeyup(event) { - if (!modules.editors) return; // if editors are not loaded, do nothing if (!allowHotkeys()) return; // in some cases (e.g. in a textarea) hotkeys are not allowed event.stopPropagation(); diff --git a/modules/ui/ice-editor.js b/modules/ui/ice-editor.js index f07cb6f9..b0d2a986 100644 --- a/modules/ui/ice-editor.js +++ b/modules/ui/ice-editor.js @@ -18,8 +18,8 @@ function editIce() { close: closeEditor }); - if (modules.editIce) return; - modules.editIce = true; + if (fmg.modules.editIce) return; + fmg.modules.editIce = true; // add listeners document.getElementById("iceEditStyle").addEventListener("click", () => editStyle("ice")); diff --git a/modules/ui/labels-editor.js b/modules/ui/labels-editor.js index 8bd04cdd..9995faf8 100644 --- a/modules/ui/labels-editor.js +++ b/modules/ui/labels-editor.js @@ -22,8 +22,8 @@ function editLabel() { selectLabelGroup(text); updateValues(textPath); - if (modules.editLabel) return; - modules.editLabel = true; + if (fmg.modules.editLabel) return; + fmg.modules.editLabel = true; // add listeners document.getElementById("labelGroupShow").addEventListener("click", showGroupSection); @@ -78,7 +78,9 @@ function editLabel() { } function updateValues(textPath) { - document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")].map(tspan => tspan.textContent).join("|"); + document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")] + .map(tspan => tspan.textContent) + .join("|"); document.getElementById("labelStartOffset").value = parseFloat(textPath.getAttribute("startOffset")); document.getElementById("labelRelativeSize").value = parseFloat(textPath.getAttribute("font-size")); } @@ -298,7 +300,13 @@ function editLabel() { function changeText() { const input = document.getElementById("labelText").value; const el = elSelected.select("textPath").node(); - const example = d3.select(elSelected.node().parentNode).append("text").attr("x", 0).attr("x", 0).attr("font-size", el.getAttribute("font-size")).node(); + const example = d3 + .select(elSelected.node().parentNode) + .append("text") + .attr("x", 0) + .attr("x", 0) + .attr("font-size", el.getAttribute("font-size")) + .node(); const lines = input.split("|"); const top = (lines.length - 1) / -2; // y offset @@ -313,7 +321,8 @@ function editLabel() { el.innerHTML = inner; example.remove(); - if (elSelected.attr("id").slice(0, 10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning"); + if (elSelected.attr("id").slice(0, 10) === "stateLabel") + tip("Use States Editor to change an actual state name, not just a label", false, "warning"); } function generateRandomName() { diff --git a/modules/ui/lakes-editor.js b/modules/ui/lakes-editor.js index 972a9cb8..2fcab524 100644 --- a/modules/ui/lakes-editor.js +++ b/modules/ui/lakes-editor.js @@ -19,8 +19,8 @@ function editLake() { drawLakeVertices(); viewbox.on("touchmove mousemove", null); - if (modules.editLake) return; - modules.editLake = true; + if (fmg.modules.editLake) return; + fmg.modules.editLake = true; // add listeners document.getElementById("lakeName").addEventListener("input", changeName); @@ -48,7 +48,8 @@ function editLake() { document.getElementById("lakeArea").value = si(getArea(l.area)) + " " + getAreaUnit(); const length = d3.polygonLength(l.vertices.map(v => pack.vertices.p[v])); - document.getElementById("lakeShoreLength").value = si(length * distanceScaleInput.value) + " " + distanceUnitInput.value; + document.getElementById("lakeShoreLength").value = + si(length * distanceScaleInput.value) + " " + distanceUnitInput.value; const lakeCells = Array.from(cells.i.filter(i => cells.f[i] === l.i)); const heights = lakeCells.map(i => cells.h[i]); @@ -91,7 +92,9 @@ function editLake() { .attr("r", 0.4) .attr("data-v", d => d) .call(d3.drag().on("drag", dragVertex)) - .on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights")); + .on("mousemove", () => + tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights") + ); } function dragVertex() { diff --git a/modules/ui/layers.js b/modules/ui/layers.js index 304f455a..54e11d96 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -1,5 +1,5 @@ -// UI module stub to control map layers -"use strict"; +import {TIME} from "/src/config/logging"; +import {invokeActiveZooming} from "../activeZooming"; let presets = {}; // global object restoreCustomPresets(); // run on-load @@ -946,7 +946,7 @@ function toggleStates(event) { } } -function drawStates() { +export function drawStates() { TIME && console.time("drawStates"); regions.selectAll("path").remove(); @@ -1110,7 +1110,7 @@ function toggleBorders(event) { } // draw state and province borders -function drawBorders() { +export function drawBorders() { TIME && console.time("drawBorders"); borders.selectAll("path").remove(); @@ -1554,7 +1554,7 @@ function toggleRivers(event) { } } -function drawRivers() { +export function drawRivers() { TIME && console.time("drawRivers"); rivers.selectAll("*").remove(); @@ -1870,7 +1870,7 @@ function drawEmblems() { TIME && console.timeEnd("drawEmblems"); } -function layerIsOn(el) { +export function layerIsOn(el) { const buttonoff = document.getElementById(el).classList.contains("buttonoff"); return !buttonoff; } diff --git a/modules/ui/military-overview.js b/modules/ui/military-overview.js index 35a23ed4..c55e1534 100644 --- a/modules/ui/military-overview.js +++ b/modules/ui/military-overview.js @@ -10,8 +10,8 @@ function overviewMilitary() { addLines(); $("#militaryOverview").dialog(); - if (modules.overviewMilitary) return; - modules.overviewMilitary = true; + if (fmg.modules.overviewMilitary) return; + fmg.modules.overviewMilitary = true; updateHeaders(); $("#militaryOverview").dialog({ @@ -54,7 +54,9 @@ function overviewMilitary() { const insert = html => document.getElementById("militaryTotal").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 () { @@ -76,7 +78,9 @@ function overviewMilitary() { const rate = (total / population) * 100; const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); - const lineData = options.military.map(u => `
${getForces(u)}
`).join(" "); + const lineData = options.military + .map(u => `
${getForces(u)}
`) + .join(" "); lines += /* html */ `
${lineData} -
${si(total)}
+
${si( + total + )}
${si(population)}
-
${rn(rate, 2)}%
+
${rn( + rate, + 2 + )}%
s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0); - options.military.forEach(u => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u))); + options.military.forEach( + u => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u)) + ); const population = rn((s.rural + s.urban * urbanization) * populationRate); const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0)); @@ -237,7 +248,16 @@ function overviewMilitary() { position: {my: "center", at: "center", of: "svg"}, buttons: { Apply: applyMilitaryOptions, - Add: () => addUnitLine({icon: "🛡️", name: "custom" + militaryOptionsTable.rows.length, rural: 0.2, urban: 0.5, crew: 1, power: 1, type: "melee"}), + Add: () => + addUnitLine({ + icon: "🛡️", + name: "custom" + militaryOptionsTable.rows.length, + rural: 0.2, + urban: 0.5, + crew: 1, + power: 1, + type: "melee" + }), Restore: restoreDefaultUnits, Cancel: function () { $(this).dialog("close"); @@ -254,8 +274,8 @@ function overviewMilitary() { } }); - if (modules.overviewMilitaryCustomize) return; - modules.overviewMilitaryCustomize = true; + if (fmg.modules.overviewMilitaryCustomize) return; + fmg.modules.overviewMilitaryCustomize = true; tableBody.addEventListener("click", event => { const el = event.target; @@ -294,7 +314,9 @@ function overviewMilitary() { function addUnitLine(unit) { const {type, icon, name, rural, urban, power, crew, separate} = unit; const row = document.createElement("tr"); - const typeOptions = types.map(t => ``).join(" "); + const typeOptions = types + .map(t => ``) + .join(" "); const getLimitButton = attr => ` + row.innerHTML = /* html */ ` ${getLimitButton("biomes")} ${getLimitButton("states")} @@ -344,7 +368,9 @@ function overviewMilitary() { const lines = filtered.map( ({i, name, fullName, color}) => ` - + ` ); @@ -395,14 +421,15 @@ function overviewMilitary() { $("#militaryOptions").dialog("close"); options.military = unitLines.map((r, i) => { const elements = Array.from(r.querySelectorAll("input, button, select")); - const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = elements.map(el => { - const {type, value} = el.dataset || {}; - if (type === "icon") return el.innerHTML || "⠀"; - if (type) return value ? value.split(",").map(v => parseInt(v)) : null; - if (el.type === "number") return +el.value || 0; - if (el.type === "checkbox") return +el.checked || 0; - return el.value; - }); + const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = + elements.map(el => { + const {type, value} = el.dataset || {}; + if (type === "icon") return el.innerHTML || "⠀"; + if (type) return value ? value.split(",").map(v => parseInt(v)) : null; + if (el.type === "number") return +el.value || 0; + if (el.type === "checkbox") return +el.checked || 0; + return el.value; + }); const unit = {icon, name: names[i], rural, urban, crew, power, type, separate}; if (biomes) unit.biomes = biomes; @@ -419,7 +446,8 @@ function overviewMilitary() { } function militaryRecalculate() { - alertMessage.innerHTML = "Are you sure you want to recalculate military forces for all states?
Regiments for all states will be regenerated"; + alertMessage.innerHTML = + "Are you sure you want to recalculate military forces for all states?
Regiments for all states will be regenerated"; $("#alert").dialog({ resizable: false, title: "Remove regiment", diff --git a/modules/ui/namesbase-editor.js b/modules/ui/namesbase-editor.js index 1e08ad2a..dca66754 100644 --- a/modules/ui/namesbase-editor.js +++ b/modules/ui/namesbase-editor.js @@ -4,8 +4,8 @@ function editNamesbase() { closeDialogs("#namesbaseEditor, .stable"); $("#namesbaseEditor").dialog(); - if (modules.editNamesbase) return; - modules.editNamesbase = true; + if (fmg.modules.editNamesbase) return; + fmg.modules.editNamesbase = true; // add listeners document.getElementById("namesbaseSelect").addEventListener("change", updateInputs); @@ -23,15 +23,23 @@ function editNamesbase() { const uploader = document.getElementById("namesbaseToLoad"); document.getElementById("namesbaseUpload").addEventListener("click", () => { - uploader.addEventListener("change", function (event) { - uploadFile(event.target, d => namesbaseUpload(d, true)); - }, { once: true }); + uploader.addEventListener( + "change", + function (event) { + uploadFile(event.target, d => namesbaseUpload(d, true)); + }, + {once: true} + ); uploader.click(); }); document.getElementById("namesbaseUploadExtend").addEventListener("click", () => { - uploader.addEventListener("change", function (event) { - uploadFile(event.target, d => namesbaseUpload(d, false)); - }, { once: true }); + uploader.addEventListener( + "change", + function (event) { + uploadFile(event.target, d => namesbaseUpload(d, false)); + }, + {once: true} + ); uploader.click(); }); @@ -147,21 +155,28 @@ function editNamesbase() { : "none"; const geminate = namesArray.map(name => name.match(/[^\w\s]|(.)(?=\1)/g) || []).flat(); - const doubled = unique(geminate).filter(char => geminate.filter(doudledChar => doudledChar === char).length > 3) || ["none"]; + const doubled = unique(geminate).filter( + char => geminate.filter(doudledChar => doudledChar === char).length > 3 + ) || ["none"]; const duplicates = unique(namesArray.filter((e, i, a) => a.indexOf(e) !== i)).join(", ") || "none"; const multiwordRate = d3.mean(namesArray.map(n => +n.includes(" "))); const getLengthQuality = () => { - if (length < 30) return "[not enough]"; - if (length < 100) return "[low]"; - if (length <= 400) return "[good]"; + if (length < 30) + return "[not enough]"; + if (length < 100) + return "[low]"; + if (length <= 400) + return "[good]"; return "[overmuch]"; }; const getVarietyLevel = () => { - if (variety < 15) return "[low]"; - if (variety < 30) return "[mean]"; + if (variety < 15) + return "[low]"; + if (variety < 30) + return "[mean]"; return "[good]"; }; @@ -175,9 +190,14 @@ function editNamesbase() {
Median name length: ${d3.median(wordsLength)}

Non-basic chars: ${nonBasicLatinChars}
-
Doubled chars: ${doubled.join("")}
+
Doubled chars: ${doubled.join( + "" + )}
Duplicates: ${duplicates}
-
Multi-word names: ${rn(multiwordRate * 100, 2)}%
+
Multi-word names: ${rn( + multiwordRate * 100, + 2 + )}%
`; $("#alert").dialog({ @@ -194,7 +214,8 @@ function editNamesbase() { function namesbaseAdd() { const base = nameBases.length; - const b = "This,is,an,example,of,name,base,showing,correct,format,It,should,have,at,least,one,hundred,names,separated,with,comma"; + const b = + "This,is,an,example,of,name,base,showing,correct,format,It,should,have,at,least,one,hundred,names,separated,with,comma"; nameBases.push({name: "Base" + base, min: 5, max: 12, d: "", m: 0, b}); document.getElementById("namesbaseSelect").add(new Option("Base" + base, base)); document.getElementById("namesbaseSelect").value = base; @@ -232,7 +253,7 @@ function editNamesbase() { downloadFile(data, name); } - function namesbaseUpload(dataLoaded, override=true) { + function namesbaseUpload(dataLoaded, override = true) { const data = dataLoaded.split("\r\n"); if (!data || !data[0]) { tip("Cannot load a namesbase. Please check the data format", false, "error"); diff --git a/modules/ui/notes-editor.js b/modules/ui/notes-editor.js index 4362fb25..6fa266bc 100644 --- a/modules/ui/notes-editor.js +++ b/modules/ui/notes-editor.js @@ -69,12 +69,12 @@ function editNotes(id, name) { if (!window.tinymce) { const url = "https://cdn.tiny.cloud/1/4i6a79ymt2y0cagke174jp3meoi28vyecrch12e5puyw3p9a/tinymce/5/tinymce.min.js"; try { - await import(url); + await import(/* @vite-ignore */ url); } catch (error) { // error may be caused by failed request being cached, try again with random hash try { const hash = Math.random().toString(36).substring(2, 15); - await import(`${url}#${hash}`); + await import(/* @vite-ignore */ `${url}#${hash}`); } catch (error) { console.error(error); } diff --git a/modules/ui/options.js b/modules/ui/options.js index 0683a694..2a65efed 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -1,5 +1,4 @@ -// UI module to control the options (preferences) -"use strict"; +import {stored, lock, locked, applyOption} from "./general"; $("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"}); $("#exitCustomization").draggable({handle: "div"}); @@ -170,10 +169,7 @@ function changeMapSize() { const maxWidth = Math.max(+mapWidthInput.value, graphWidth); const maxHeight = Math.max(+mapHeightInput.value, graphHeight); - zoom.translateExtent([ - [0, 0], - [maxWidth, maxHeight] - ]); + Zoom.translateExtent([0, 0, maxWidth, maxHeight]); landmass.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); @@ -186,7 +182,7 @@ function changeMapSize() { } // just apply canvas size that was already set -function applyMapSize() { +export function applyMapSize() { const zoomMin = +zoomExtentMin.value; const zoomMax = +zoomExtentMax.value; graphWidth = +mapWidthInput.value; @@ -194,13 +190,10 @@ function applyMapSize() { svgWidth = Math.min(graphWidth, window.innerWidth); svgHeight = Math.min(graphHeight, window.innerHeight); svg.attr("width", svgWidth).attr("height", svgHeight); - zoom - .translateExtent([ - [0, 0], - [graphWidth, graphHeight] - ]) - .scaleExtent([zoomMin, zoomMax]) - .scaleTo(svg, zoomMin); + + Zoom.translateExtent([0, 0, graphWidth, graphHeight]); + Zoom.scaleExtent([zoomMin, zoomMax]); + Zoom.scaleTo(svg, zoomMin); } function toggleFullscreen() { @@ -217,17 +210,13 @@ function toggleFullscreen() { } function toggleTranslateExtent(el) { - const on = (el.dataset.on = +!+el.dataset.on); - if (on) - zoom.translateExtent([ - [-graphWidth / 2, -graphHeight / 2], - [graphWidth * 1.5, graphHeight * 1.5] - ]); - else - zoom.translateExtent([ - [0, 0], - [graphWidth, graphHeight] - ]); + const on = !Number(el.dataset.on); + const extent = on + ? [-graphWidth / 2, -graphHeight / 2, graphWidth * 1.5, graphHeight * 1.5] + : [0, 0, graphWidth, graphHeight]; + Zoom.translateExtent(extent); + + el.dataset.on = Number(on); } // add voice options @@ -294,7 +283,8 @@ function restoreSeed(id) { function restoreDefaultZoomExtent() { zoomExtentMin.value = 1; zoomExtentMax.value = 20; - zoom.scaleExtent([1, 20]).scaleTo(svg, 1); + Zoom.scaleExtent([1, 20]); + Zoom.scaleTo(svg, 1); } function copyMapURL() { @@ -461,15 +451,16 @@ function changeDialogsTheme(themeColor, transparency) { } function changeZoomExtent(value) { - const min = Math.max(+zoomExtentMin.value, 0.01); - const max = Math.min(+zoomExtentMax.value, 200); - zoom.scaleExtent([min, max]); + const min = Math.max(+byId("zoomExtentMin").value, 0.01); + const max = Math.min(+byId("zoomExtentMax").value, 200); + Zoom.scaleExtent([min, max]); + const scale = minmax(+value, 0.01, 200); - zoom.scaleTo(svg, scale); + Zoom.scaleTo(svg, scale); } // restore options stored in localStorage -function applyStoredOptions() { +export function applyStoredOptions() { if (!stored("mapWidth") || !stored("mapHeight")) { mapWidthInput.value = window.innerWidth; mapHeightInput.value = window.innerHeight; @@ -530,7 +521,7 @@ function applyStoredOptions() { } // randomize options if randomization is allowed (not locked or options='default') -function randomizeOptions() { +export function randomizeOptions() { const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options // 'Options' settings @@ -595,7 +586,7 @@ function randomizeCultureSet() { } function setRendering(value) { - viewbox.attr("shape-rendering", value); + fmg.viewbox?.attr("shape-rendering", value); } // generate current year and era name @@ -652,7 +643,7 @@ document.getElementById("sticked").addEventListener("click", function (event) { else if (id === "saveButton") showSavePane(); else if (id === "exportButton") showExportPane(); else if (id === "loadButton") showLoadPane(); - else if (id === "zoomReset") resetZoom(1000); + else if (id === "zoomReset") Zoom.reset(1000); }); function regeneratePrompt(options) { @@ -975,8 +966,8 @@ function toggle3dOptions() { updateValues(); - if (modules.options3d) return; - modules.options3d = true; + if (fmg.modules.options3d) return; + fmg.modules.options3d = true; document.getElementById("options3dUpdate").addEventListener("click", ThreeD.update); document.getElementById("options3dSave").addEventListener("click", ThreeD.saveScreenshot); diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js index 3cac626f..6a55f032 100644 --- a/modules/ui/provinces-editor.js +++ b/modules/ui/provinces-editor.js @@ -11,8 +11,8 @@ function editProvinces() { const body = document.getElementById("provincesBodySection"); refreshProvincesEditor(); - if (modules.editProvinces) return; - modules.editProvinces = true; + if (fmg.modules.editProvinces) return; + fmg.modules.editProvinces = true; $("#provincesEditor").dialog({ title: "Provinces Editor", @@ -123,7 +123,9 @@ function editProvinces() { const rural = p.rural * populationRate; const urban = p.urban * 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)}`; totalPopulation += population; const stateName = pack.states[p.state].name; @@ -144,9 +146,15 @@ function editProvinces() { > - - - + + + 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 => ``); const storedStyles = Object.keys(localStorage).filter(key => key.startsWith(customPresetPrefix)); - const customOptions = storedStyles.map(styleName => ``); + const customOptions = storedStyles.map( + styleName => `` + ); 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 = "" + types.map(type => ``).join(""); + filterSelect.innerHTML = + "" + types.map(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 index ff1dcce8..ebb5e08c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "preview": "vite preview" }, "devDependencies": { - "vite": "^2.9.12", - "typescript": "^4.7.4" + "typescript": "^4.7.4", + "vite": "^2.9.12" } } 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 index fbd02253..5bcbb828 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,5 +16,5 @@ "noImplicitReturns": true, "skipLibCheck": true }, - "include": ["src"] + "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({});