mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
refactor: start files migration nightmare
This commit is contained in:
parent
c4736cc640
commit
bc65e0e207
64 changed files with 1990 additions and 816 deletions
43
index.html
43
index.html
|
|
@ -7771,7 +7771,6 @@
|
||||||
<script src="utils/commonUtils.js"></script>
|
<script src="utils/commonUtils.js"></script>
|
||||||
<script src="utils/arrayUtils.js"></script>
|
<script src="utils/arrayUtils.js"></script>
|
||||||
<script src="utils/colorUtils.js"></script>
|
<script src="utils/colorUtils.js"></script>
|
||||||
<script src="utils/graphUtils.js"></script>
|
|
||||||
<script src="utils/nodeUtils.js"></script>
|
<script src="utils/nodeUtils.js"></script>
|
||||||
<script src="utils/numberUtils.js"></script>
|
<script src="utils/numberUtils.js"></script>
|
||||||
<script src="utils/polyfills.js"></script>
|
<script src="utils/polyfills.js"></script>
|
||||||
|
|
@ -7783,33 +7782,39 @@
|
||||||
<script src="modules/voronoi.js"></script>
|
<script src="modules/voronoi.js"></script>
|
||||||
<script src="config/heightmap-templates.js"></script>
|
<script src="config/heightmap-templates.js"></script>
|
||||||
<script src="config/precreated-heightmaps.js"></script>
|
<script src="config/precreated-heightmaps.js"></script>
|
||||||
<script src="modules/heightmap-generator.js"></script>
|
<script type="module" src="modules/heightmap-generator.js"></script>
|
||||||
<script src="modules/ocean-layers.js"></script>
|
<script type="module" src="modules/ocean-layers.js"></script>
|
||||||
<script src="modules/river-generator.js"></script>
|
<script type="module" src="modules/river-generator.js"></script>
|
||||||
<script src="modules/lakes.js"></script>
|
<script type="module" src="modules/lakes.js"></script>
|
||||||
<script src="modules/names-generator.js"></script>
|
<script type="module" src="modules/names-generator.js"></script>
|
||||||
<script src="modules/cultures-generator.js"></script>
|
<script type="module" src="modules/biomes.js"></script>
|
||||||
<script src="modules/burgs-and-states.js?v=1.87.04"></script>
|
<script type="module" src="modules/cultures-generator.js"></script>
|
||||||
<script src="modules/routes-generator.js"></script>
|
<script type="module" src="modules/burgs-and-states.js?v=1.87.04"></script>
|
||||||
<script src="modules/religions-generator.js"></script>
|
<script type="module" src="modules/routes-generator.js"></script>
|
||||||
<script src="modules/military-generator.js"></script>
|
<script type="module" src="modules/religions-generator.js"></script>
|
||||||
<script src="modules/markers-generator.js"></script>
|
<script type="module" src="modules/military-generator.js"></script>
|
||||||
<script src="modules/coa-generator.js"></script>
|
<script type="module" src="modules/markers-generator.js"></script>
|
||||||
|
<script type="module" src="modules/coa-generator.js"></script>
|
||||||
<script src="modules/submap.js"></script>
|
<script src="modules/submap.js"></script>
|
||||||
<script src="libs/polylabel.min.js"></script>
|
<script src="libs/polylabel.min.js"></script>
|
||||||
<script src="libs/lineclip.min.js"></script>
|
<script src="libs/lineclip.min.js"></script>
|
||||||
<script src="libs/alea.min.js"></script>
|
<script src="libs/alea.min.js"></script>
|
||||||
<script src="modules/fonts.js"></script>
|
<script src="modules/fonts.js"></script>
|
||||||
<script src="modules/ui/layers.js"></script>
|
<script type="module" src="modules/ui/layers.js"></script>
|
||||||
<script src="modules/ui/measurers.js?v=1.87.02"></script>
|
<script src="modules/ui/measurers.js?v=1.87.02"></script>
|
||||||
<script src="modules/ui/stylePresets.js"></script>
|
<script src="modules/ui/stylePresets.js"></script>
|
||||||
|
<script type="module" src="modules/ui/general.js?v=1.87.00"></script>
|
||||||
|
<script type="module" src="modules/ui/options.js?v=1.87.00"></script>
|
||||||
|
|
||||||
<script src="modules/ui/general.js?v=1.87.00"></script>
|
<script src="modules/define-globals.js"></script>
|
||||||
<script src="modules/ui/options.js?v=1.87.00"></script>
|
<script src="modules/define-svg.js"></script>
|
||||||
<script src="main.js"></script>
|
<script src="modules/zoom.js"></script>
|
||||||
|
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
|
||||||
|
<script type="module" src="modules/activeZooming.js"></script>
|
||||||
<script defer src="modules/relief-icons.js"></script>
|
<script defer src="modules/relief-icons.js"></script>
|
||||||
<script defer src="modules/ui/style.js"></script>
|
<script type="module" src="modules/ui/style.js"></script>
|
||||||
<script defer src="modules/ui/editors.js?v=1.87.01"></script>
|
<script defer src="modules/ui/editors.js?v=1.87.01"></script>
|
||||||
<script defer src="modules/ui/tools.js?v=1.87.03"></script>
|
<script defer src="modules/ui/tools.js?v=1.87.03"></script>
|
||||||
<script defer src="modules/ui/world-configurator.js"></script>
|
<script defer src="modules/ui/world-configurator.js"></script>
|
||||||
|
|
@ -7829,7 +7834,7 @@
|
||||||
<script defer src="modules/ui/relief-editor.js"></script>
|
<script defer src="modules/ui/relief-editor.js"></script>
|
||||||
<script defer src="modules/ui/burg-editor.js"></script>
|
<script defer src="modules/ui/burg-editor.js"></script>
|
||||||
<script defer src="modules/ui/units-editor.js"></script>
|
<script defer src="modules/ui/units-editor.js"></script>
|
||||||
<script defer src="modules/ui/notes-editor.js"></script>
|
<script type="module" src="modules/ui/notes-editor.js"></script>
|
||||||
<script defer src="modules/ui/diplomacy-editor.js"></script>
|
<script defer src="modules/ui/diplomacy-editor.js"></script>
|
||||||
<script defer src="modules/ui/zones-editor.js"></script>
|
<script defer src="modules/ui/zones-editor.js"></script>
|
||||||
<script defer src="modules/ui/burgs-overview.js"></script>
|
<script defer src="modules/ui/burgs-overview.js"></script>
|
||||||
|
|
|
||||||
92
modules/activeZooming.js
Normal file
92
modules/activeZooming.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
76
modules/biomes.js
Normal file
76
modules/biomes.js
Normal file
|
|
@ -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};
|
||||||
|
})();
|
||||||
|
|
@ -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 () {
|
window.BurgsAndStates = (function () {
|
||||||
const generate = function () {
|
const generate = function () {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
window.COA = (function () {
|
window.COA = (function () {
|
||||||
const tinctures = {
|
const tinctures = {
|
||||||
field: {metals: 3, colours: 4, stains: +P(0.03), patterns: 1},
|
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},
|
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},
|
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},
|
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: {
|
Hunting: {
|
||||||
bugleHorn: 2,
|
bugleHorn: 2,
|
||||||
bugleHorn2: 1,
|
bugleHorn2: 1,
|
||||||
|
|
@ -322,7 +332,19 @@ window.COA = (function () {
|
||||||
// selection based on type
|
// selection based on type
|
||||||
City: {key: 3, bell: 2, lute: 1, tower: 1, castle: 1, mallet: 1, cannon: 1, anvil: 1},
|
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},
|
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
|
// specific cases
|
||||||
natural: {fountain: "azure", garb: "or", raven: "sable"}, // charges to mainly use predefined colours
|
natural: {fountain: "azure", garb: "or", raven: "sable"}, // charges to mainly use predefined colours
|
||||||
sinister: [
|
sinister: [
|
||||||
|
|
@ -508,7 +530,22 @@ window.COA = (function () {
|
||||||
},
|
},
|
||||||
// charges
|
// charges
|
||||||
inescutcheon: {e: 4, jln: 1},
|
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},
|
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},
|
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},
|
wolfPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
|
||||||
|
|
@ -681,18 +718,41 @@ window.COA = (function () {
|
||||||
const coa = {t1};
|
const coa = {t1};
|
||||||
|
|
||||||
let charge = P(usedPattern ? 0.5 : 0.93) ? true : false; // 80% for charge
|
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 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 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 divisioned = rareDivided
|
||||||
const division = divisioned ? (parent?.division && P(kinship - 0.1) ? parent.division.division : rw(divisions.variants)) : null;
|
? 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)
|
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) {
|
if (division) {
|
||||||
const t = getTincture("division", usedTinctures, P(0.98) ? coa.t1 : null);
|
const t = getTincture("division", usedTinctures, P(0.98) ? coa.t1 : null);
|
||||||
coa.division = {division, t};
|
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) {
|
if (ordinary) {
|
||||||
|
|
@ -768,7 +828,14 @@ window.COA = (function () {
|
||||||
// counterchanged, 40%
|
// counterchanged, 40%
|
||||||
else if (["perPale", "perFess", "perBend", "perBendSinister"].includes(division) && P(0.8)) {
|
else if (["perPale", "perFess", "perBend", "perBendSinister"].includes(division) && P(0.8)) {
|
||||||
// place 2 charges in division standard positions
|
// 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;
|
coa.charges[0].p = p1;
|
||||||
|
|
||||||
const charge = selectCharge(charges.single);
|
const charge = selectCharge(charges.single);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"use strict";
|
import {TIME} from "/src/config/logging";
|
||||||
|
|
||||||
window.Cultures = (function () {
|
window.Cultures = (function () {
|
||||||
let cells;
|
let cells;
|
||||||
|
|
|
||||||
33
modules/define-globals.js
Normal file
33
modules/define-globals.js
Normal file
|
|
@ -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;
|
||||||
177
modules/define-svg.js
Normal file
177
modules/define-svg.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -422,8 +422,8 @@ function editStateName(state) {
|
||||||
position: {my: "center", at: "center", of: "svg"}
|
position: {my: "center", at: "center", of: "svg"}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.editStateName) return;
|
if (fmg.modules.editStateName) return;
|
||||||
modules.editStateName = true;
|
fmg.modules.editStateName = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
byId("stateNameEditorShortCulture").on("click", regenerateShortNameCuture);
|
byId("stateNameEditorShortCulture").on("click", regenerateShortNameCuture);
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,7 @@ function getName(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGraph(currentGraph) {
|
function getGraph(currentGraph) {
|
||||||
const newGraph = shouldRegenerateGrid(currentGraph) ? generateGrid() : deepCopy(currentGraph);
|
const newGraph = shouldRegenerateGrid(currentGraph) ? generateGrid() : structuredClone(currentGraph);
|
||||||
delete newGraph.cells.h;
|
delete newGraph.cells.h;
|
||||||
return newGraph;
|
return newGraph;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 () {
|
window.HeightmapGenerator = (function () {
|
||||||
let grid = null;
|
let grid = null;
|
||||||
|
|
@ -388,8 +390,12 @@ window.HeightmapGenerator = (function () {
|
||||||
const vert = direction === "vertical";
|
const vert = direction === "vertical";
|
||||||
const startX = vert ? Math.floor(Math.random() * graphWidth * 0.4 + graphWidth * 0.3) : 5;
|
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 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 endX = vert
|
||||||
const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
|
? 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 start = findGridCell(startX, startY, grid);
|
||||||
const end = findGridCell(endX, endY, grid);
|
const end = findGridCell(endX, endY, grid);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
window.Lakes = (function () {
|
window.Lakes = (function () {
|
||||||
const setClimateData = function (h) {
|
const setClimateData = function (h) {
|
||||||
const cells = pack.cells;
|
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);
|
f.flux = f.shoreline.reduce((acc, c) => acc + grid.cells.prec[cells.g[c]], 0);
|
||||||
|
|
||||||
// temperature and evaporation to detect closed lakes
|
// 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 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]
|
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);
|
f.evaporation = rn(evaporation * f.cells);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"use strict";
|
import {TIME} from "/src/config/logging";
|
||||||
|
|
||||||
window.Markers = (function () {
|
window.Markers = (function () {
|
||||||
let config = [];
|
let config = [];
|
||||||
|
|
@ -20,6 +20,7 @@ window.Markers = (function () {
|
||||||
list: function to select candidates
|
list: function to select candidates
|
||||||
add: function to add marker legend
|
add: function to add marker legend
|
||||||
*/
|
*/
|
||||||
|
// prettier-ignore
|
||||||
return [
|
return [
|
||||||
{type: "volcanoes", icon: "🌋", dx: 52, px: 13, min: 10, each: 500, multiplier: 1, list: listVolcanoes, add: addVolcano},
|
{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},
|
{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}) {
|
function listBridges({cells, burgs}) {
|
||||||
const meanFlux = d3.mean(cells.fl.filter(fl => fl));
|
const meanFlux = d3.mean(cells.fl.filter(fl => fl));
|
||||||
return cells.i.filter(
|
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",
|
"rat tails",
|
||||||
"pig ears"
|
"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 = [
|
const drinks = [
|
||||||
"wine",
|
"wine",
|
||||||
"brandy",
|
"brandy",
|
||||||
|
|
@ -469,7 +490,11 @@ window.Markers = (function () {
|
||||||
const typeName = P(0.3) ? "inn" : "tavern";
|
const typeName = P(0.3) ? "inn" : "tavern";
|
||||||
const isAnimalThemed = P(0.7);
|
const isAnimalThemed = P(0.7);
|
||||||
const animal = ra(animals);
|
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 meal = isAnimalThemed && P(0.3) ? animal : ra(courses);
|
||||||
const course = `${ra(methods)} ${meal}`.toLowerCase();
|
const course = `${ra(methods)} ${meal}`.toLowerCase();
|
||||||
const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase();
|
const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase();
|
||||||
|
|
@ -478,18 +503,26 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function listLighthouses({cells}) {
|
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) {
|
function addLighthouse(id, cell) {
|
||||||
const {cells} = pack;
|
const {cells} = pack;
|
||||||
|
|
||||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||||
notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to 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}) {
|
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) {
|
function addWaterfall(id, cell) {
|
||||||
|
|
@ -509,7 +542,9 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function listBattlefields({cells}) {
|
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) {
|
function addBattlefield(id, cell) {
|
||||||
|
|
@ -555,7 +590,9 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function listSeaMonsters({cells, features}) {
|
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) {
|
function addSeaMonster(id, cell) {
|
||||||
|
|
@ -589,7 +626,17 @@ window.Markers = (function () {
|
||||||
"horrifying",
|
"horrifying",
|
||||||
"feared"
|
"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 = [
|
const species = [
|
||||||
"Ogre",
|
"Ogre",
|
||||||
"Troll",
|
"Troll",
|
||||||
|
|
@ -625,13 +672,21 @@ window.Markers = (function () {
|
||||||
const monster = ra(species);
|
const monster = ra(species);
|
||||||
const toponym = Names.getCulture(cells.culture[cell]);
|
const toponym = Names.getCulture(cells.culture[cell]);
|
||||||
const name = `${toponym} ${monster}`;
|
const name = `${toponym} ${monster}`;
|
||||||
const legend = `${ra(subjects)} 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});
|
notes.push({id, name, legend});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sacred mountains spawn on lonely mountains
|
// Sacred mountains spawn on lonely mountains
|
||||||
function listSacredMountains({cells}) {
|
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) {
|
function addSacredMountain(id, cell) {
|
||||||
|
|
@ -674,7 +729,9 @@ window.Markers = (function () {
|
||||||
|
|
||||||
// Sacred palm groves spawn on oasises
|
// Sacred palm groves spawn on oasises
|
||||||
function listSacredPalmGroves({cells}) {
|
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) {
|
function addSacredPalmGrove(id, cell) {
|
||||||
|
|
@ -765,7 +822,20 @@ window.Markers = (function () {
|
||||||
function addStatue(id, cell) {
|
function addStatue(id, cell) {
|
||||||
const {cells} = pack;
|
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 = {
|
const scripts = {
|
||||||
cypriot: "𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ",
|
cypriot: "𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ",
|
||||||
geez: "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጰጸፀፈፐ ",
|
geez: "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጰጸፀፈፐ ",
|
||||||
|
|
@ -820,7 +890,16 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCircuses(id, cell) {
|
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 adjective = ra(adjectives);
|
||||||
const name = `Travelling ${adjective} Circus`;
|
const name = `Travelling ${adjective} Circus`;
|
||||||
|
|
@ -932,8 +1011,26 @@ window.Markers = (function () {
|
||||||
function addDances(id, cell) {
|
function addDances(id, cell) {
|
||||||
const {cells, burgs} = pack;
|
const {cells, burgs} = pack;
|
||||||
const burgName = burgs[cells.burg[cell]].name;
|
const burgName = burgs[cells.burg[cell]].name;
|
||||||
const socialTypes = ["gala", "dance", "performance", "ball", "soiree", "jamboree", "exhibition", "carnival", "festival", "jubilee"];
|
const socialTypes = [
|
||||||
const people = ["great and the good", "nobility", "local elders", "foreign dignitaries", "spiritual leaders", "suspected revolutionaries"];
|
"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 socialType = ra(socialTypes);
|
||||||
|
|
||||||
const name = `${burgName} ${socialType}`;
|
const name = `${burgName} ${socialType}`;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"use strict";
|
import {TIME} from "/src/config/logging";
|
||||||
|
|
||||||
window.Military = (function () {
|
window.Military = (function () {
|
||||||
const generate = function () {
|
const generate = function () {
|
||||||
|
|
@ -10,7 +10,18 @@ window.Military = (function () {
|
||||||
|
|
||||||
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
|
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
|
||||||
const area = d3.sum(valid.map(s => s.area)); // total area
|
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 = {
|
const stateModifier = {
|
||||||
melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1},
|
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 = {
|
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},
|
nomadic: {
|
||||||
wetland: {melee: 0.8, ranged: 2, mounted: 0.3, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5},
|
melee: 0.2,
|
||||||
highland: {melee: 1.2, ranged: 1.6, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 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 = {
|
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},
|
nomadic: {
|
||||||
wetland: {melee: 1, ranged: 1.6, mounted: 0.2, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5},
|
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}
|
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 d = s.diplomacy;
|
||||||
|
|
||||||
const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized
|
const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized
|
||||||
const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? 0.8 : d.some(d => d === "Suspicion") ? 0.5 : 0.1; // peacefulness
|
const diplomacyRate = d.some(d => d === "Enemy")
|
||||||
const neighborsRateRaw = s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5);
|
? 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
|
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.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier)
|
||||||
s.temp.platoons = [];
|
s.temp.platoons = [];
|
||||||
|
|
@ -86,8 +150,10 @@ window.Military = (function () {
|
||||||
|
|
||||||
let modifier = cells.pop[i] / 100; // basic rural army in percentages
|
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 (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 (religion !== cells.religion[stateObj.center])
|
||||||
if (cells.f[i] !== cells.f[stateObj.center]) modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass
|
modifier = stateObj.form === "Theocracy" ? modifier / 2.2 : modifier / 1.4; // non-dominant religion
|
||||||
|
if (cells.f[i] !== cells.f[stateObj.center])
|
||||||
|
modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass
|
||||||
const type = getType(i);
|
const type = getType(i);
|
||||||
|
|
||||||
for (const unit of options.military) {
|
for (const unit of options.military) {
|
||||||
|
|
@ -111,7 +177,17 @@ window.Military = (function () {
|
||||||
n = 1;
|
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;
|
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
|
// get default regiment emblem
|
||||||
const getEmblem = function (r) {
|
const getEmblem = function (r) {
|
||||||
if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops
|
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 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);
|
const unit = options.military.find(u => u.name === mainUnit);
|
||||||
return unit.icon;
|
return unit.icon;
|
||||||
|
|
@ -400,7 +492,9 @@ window.Military = (function () {
|
||||||
.map(t => `— ${t}: ${r.u[t]}`)
|
.map(t => `— ${t}: ${r.u[t]}`)
|
||||||
.join("\r\n")
|
.join("\r\n")
|
||||||
: null;
|
: 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 campaign = s.campaigns ? ra(s.campaigns) : null;
|
||||||
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6);
|
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});
|
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
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
window.Names = (function () {
|
window.Names = (function () {
|
||||||
let chains = [];
|
let chains = [];
|
||||||
|
|
||||||
|
|
@ -142,7 +140,11 @@ window.Names = (function () {
|
||||||
// generate short name for base
|
// generate short name for base
|
||||||
const getBaseShort = function (base) {
|
const getBaseShort = function (base) {
|
||||||
if (nameBases[base] === undefined) {
|
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;
|
base = 1;
|
||||||
}
|
}
|
||||||
const min = nameBases[base].min - 1;
|
const min = nameBases[base].min - 1;
|
||||||
|
|
@ -165,7 +167,8 @@ window.Names = (function () {
|
||||||
// remove -sk/-ev/-ov for Ruthenian
|
// remove -sk/-ev/-ov for Ruthenian
|
||||||
else if (base === 12) return vowel(name.slice(-1)) ? name : name + "u";
|
else if (base === 12) return vowel(name.slice(-1)) ? name : name + "u";
|
||||||
// Japanese ends on any vowel or -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
|
// no suffix for fantasy bases
|
||||||
if (base > 32 && base < 42) return name;
|
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
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"use strict";
|
import {TIME} from "/src/config/logging";
|
||||||
|
|
||||||
window.OceanLayers = (function () {
|
window.OceanLayers = (function () {
|
||||||
let cells, vertices, pointsN, used;
|
let cells, vertices, pointsN, used;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"use strict";
|
import {TIME} from "/src/config/logging";
|
||||||
|
import {findAll} from "/src/utils/graphUtils";
|
||||||
|
|
||||||
window.Religions = (function () {
|
window.Religions = (function () {
|
||||||
// name generation approach and relative chance to be selected
|
// name generation approach and relative chance to be selected
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"use strict";
|
import {TIME} from "/src/config/logging";
|
||||||
|
|
||||||
window.Rivers = (function () {
|
window.Rivers = (function () {
|
||||||
const generate = function (allowErosion = true) {
|
const generate = function (allowErosion = true) {
|
||||||
|
|
@ -48,7 +48,9 @@ window.Rivers = (function () {
|
||||||
cells.fl[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation
|
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
|
// 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) {
|
for (const lake of lakes) {
|
||||||
const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i);
|
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
|
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 length = getApproximateLength(meanderedPoints);
|
||||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
|
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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import {TIME} from "/src/config/logging";
|
||||||
|
import {findCell} from "/src/utils/graphUtils";
|
||||||
|
|
||||||
window.Routes = (function () {
|
window.Routes = (function () {
|
||||||
const getRoads = function () {
|
const getRoads = function () {
|
||||||
TIME && console.time("generateMainRoads");
|
TIME && console.time("generateMainRoads");
|
||||||
|
|
@ -39,7 +42,10 @@ window.Routes = (function () {
|
||||||
if (!i) {
|
if (!i) {
|
||||||
// build trail from the first burg on island
|
// build trail from the first burg on island
|
||||||
// to the farthest one on the same island or the closest road
|
// 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;
|
const to = isle[farthest].cell;
|
||||||
if (cells.road[to]) return;
|
if (cells.road[to]) return;
|
||||||
const [from, exit] = findLandPath(b.cell, to, true);
|
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 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 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 getPath = segment => round(lineGen(getPathPoints(segment)), 1);
|
||||||
const getPathsHTML = (paths, type) => paths.map((path, i) => `<path id="${type}${i}" d="${getPath(path)}" />`).join("");
|
const getPathsHTML = (paths, type) =>
|
||||||
|
paths.map((path, i) => `<path id="${type}${i}" d="${getPath(path)}" />`).join("");
|
||||||
|
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||||
roads.html(getPathsHTML(main, "road"));
|
roads.html(getPathsHTML(main, "road"));
|
||||||
|
|
|
||||||
|
|
@ -32,17 +32,27 @@ class Battle {
|
||||||
close: () => Battle.prototype.context.cancelResults()
|
close: () => Battle.prototype.context.cancelResults()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.Battle) return;
|
if (fmg.modules.Battle) return;
|
||||||
modules.Battle = true;
|
fmg.modules.Battle = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev));
|
document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev));
|
||||||
document.getElementById("battleType").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev));
|
document
|
||||||
document.getElementById("battleNameShow").addEventListener("click", () => Battle.prototype.context.showNameSection());
|
.getElementById("battleType")
|
||||||
document.getElementById("battleNamePlace").addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value));
|
.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("battleNameFull").addEventListener("change", ev => Battle.prototype.context.changeName(ev));
|
||||||
document.getElementById("battleNameCulture").addEventListener("click", () => Battle.prototype.context.generateName("culture"));
|
document
|
||||||
document.getElementById("battleNameRandom").addEventListener("click", () => Battle.prototype.context.generateName("random"));
|
.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("battleNameHide").addEventListener("click", this.hideNameSection);
|
||||||
document.getElementById("battleAddRegiment").addEventListener("click", this.addSide);
|
document.getElementById("battleAddRegiment").addEventListener("click", this.addSide);
|
||||||
document.getElementById("battleRoll").addEventListener("click", () => Battle.prototype.context.randomize());
|
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("battleWiki").addEventListener("click", () => wiki("Battle-Simulator"));
|
||||||
|
|
||||||
document.getElementById("battlePhase_attackers").addEventListener("click", ev => this.toggleChange(ev));
|
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").addEventListener("click", ev => this.toggleChange(ev));
|
||||||
document.getElementById("battlePhase_defenders").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "defenders"));
|
document
|
||||||
document.getElementById("battleDie_attackers").addEventListener("click", () => Battle.prototype.context.rollDie("attackers"));
|
.getElementById("battlePhase_defenders")
|
||||||
document.getElementById("battleDie_defenders").addEventListener("click", () => Battle.prototype.context.rollDie("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() {
|
defineType() {
|
||||||
|
|
@ -82,8 +100,12 @@ class Battle {
|
||||||
document.getElementById("battleType").className = "icon-button-" + this.type;
|
document.getElementById("battleType").className = "icon-button-" + this.type;
|
||||||
|
|
||||||
const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers");
|
const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers");
|
||||||
const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_" + this.type).content;
|
const attackers = sideSpecific
|
||||||
const defenders = sideSpecific ? document.getElementById("battlePhases_" + this.type + "_defenders").content : attackers;
|
? 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_attackers").nextElementSibling.innerHTML = "";
|
||||||
document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = "";
|
document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = "";
|
||||||
|
|
@ -146,19 +168,30 @@ class Battle {
|
||||||
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
|
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
|
||||||
const body = `<tbody id="battle${state.i}-${regiment.i}">`;
|
const body = `<tbody id="battle${state.i}-${regiment.i}">`;
|
||||||
|
|
||||||
let initial = `<tr class="battleInitial"><td>${icon}</td><td class="regiment" data-tip="${regiment.name}">${regiment.name.slice(0, 24)}</td>`;
|
let initial = `<tr class="battleInitial"><td>${icon}</td><td class="regiment" data-tip="${
|
||||||
let casualties = `<tr class="battleCasualties"><td></td><td data-tip="${state.fullName}">${state.fullName.slice(0, 26)}</td>`;
|
regiment.name
|
||||||
|
}">${regiment.name.slice(0, 24)}</td>`;
|
||||||
|
let casualties = `<tr class="battleCasualties"><td></td><td data-tip="${state.fullName}">${state.fullName.slice(
|
||||||
|
0,
|
||||||
|
26
|
||||||
|
)}</td>`;
|
||||||
let survivors = `<tr class="battleSurvivors"><td></td><td data-tip="Supply line length, affects morale">Distance to base: ${distance} ${distanceUnitInput.value}</td>`;
|
let survivors = `<tr class="battleSurvivors"><td></td><td data-tip="Supply line length, affects morale">Distance to base: ${distance} ${distanceUnitInput.value}</td>`;
|
||||||
|
|
||||||
for (const u of options.military) {
|
for (const u of options.military) {
|
||||||
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.u[u.name] || 0}</td>`;
|
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${
|
||||||
|
regiment.u[u.name] || 0
|
||||||
|
}</td>`;
|
||||||
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td>`;
|
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td>`;
|
||||||
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.u[u.name] || 0}</td>`;
|
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${
|
||||||
|
regiment.u[u.name] || 0
|
||||||
|
}</td>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.a || 0}</td></tr>`;
|
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.a || 0}</td></tr>`;
|
||||||
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td></tr>`;
|
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td></tr>`;
|
||||||
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.a || 0}</td></tr>`;
|
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${
|
||||||
|
regiment.a || 0
|
||||||
|
}</td></tr>`;
|
||||||
|
|
||||||
const div = side === "attackers" ? battleAttackers : battleDefenders;
|
const div = side === "attackers" ? battleAttackers : battleDefenders;
|
||||||
div.innerHTML += body + initial + casualties + survivors + "</tbody>";
|
div.innerHTML += body + initial + casualties + survivors + "</tbody>";
|
||||||
|
|
@ -173,17 +206,23 @@ class Battle {
|
||||||
.filter(s => s.military && !s.removed)
|
.filter(s => s.military && !s.removed)
|
||||||
.map(s => s.military)
|
.map(s => s.military)
|
||||||
.flat();
|
.flat();
|
||||||
const distance = reg => rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
const distance = reg =>
|
||||||
const isAdded = reg => context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === 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
|
body.innerHTML = regiments
|
||||||
.map(r => {
|
.map(r => {
|
||||||
const s = pack.states[r.state],
|
const s = pack.states[r.state],
|
||||||
added = isAdded(r),
|
added = isAdded(r),
|
||||||
dist = added ? "0 " + distanceUnitInput.value : distance(r);
|
dist = added ? "0 " + distanceUnitInput.value : distance(r);
|
||||||
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
|
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${
|
||||||
|
s.name
|
||||||
|
} data-regiment=${r.name}
|
||||||
data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
|
data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
|
||||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px; stroke: #333"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" ></svg>
|
<svg width=".9em" height=".9em" style="margin-bottom:-1px; stroke: #333"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||||
|
s.color
|
||||||
|
}" ></svg>
|
||||||
<div style="width:6em">${s.name.slice(0, 11)}</div>
|
<div style="width:6em">${s.name.slice(0, 11)}</div>
|
||||||
<div style="width:1.2em">${r.icon}</div>
|
<div style="width:1.2em">${r.icon}</div>
|
||||||
<div style="width:13em">${r.name.slice(0, 24)}</div>
|
<div style="width:13em">${r.name.slice(0, 24)}</div>
|
||||||
|
|
@ -267,7 +306,10 @@ class Battle {
|
||||||
}
|
}
|
||||||
|
|
||||||
generateName(type) {
|
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("battleNamePlace").value = this.place = place;
|
||||||
document.getElementById("battleNameFull").value = this.name = this.defineName();
|
document.getElementById("battleNameFull").value = this.name = this.defineName();
|
||||||
$("#battleScreen").dialog({title: this.name});
|
$("#battleScreen").dialog({title: this.name});
|
||||||
|
|
@ -286,35 +328,161 @@ class Battle {
|
||||||
calculateStrength(side) {
|
calculateStrength(side) {
|
||||||
const scheme = {
|
const scheme = {
|
||||||
// field battle phases
|
// 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
|
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
|
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
|
// 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
|
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
|
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
|
// 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
|
blockade: {
|
||||||
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
|
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
|
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
|
bombardment: {
|
||||||
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
|
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
|
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
|
looting: {
|
||||||
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
|
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
|
// ambush phases
|
||||||
surprise: {melee: 2, ranged: 2.4, mounted: 1, machinery: 1, naval: 1, armored: 1, aviation: 0.8, magical: 1.2}, // increased
|
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
|
// 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
|
landing: {
|
||||||
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
|
melee: 0.8,
|
||||||
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
|
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
|
// air battle phases
|
||||||
maneuvering: {melee: 0, ranged: 0.1, mounted: 0, machinery: 0.2, naval: 0, armored: 0, aviation: 1, magical: 0.2}, // aviation
|
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 forces = this.getJoinedForces(this[side].regiments);
|
||||||
const phase = this[side].phase;
|
const phase = this[side].phase;
|
||||||
const adjuster = Math.max(populationRate / 10, 10); // population adjuster, by default 100
|
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;
|
const UIvalue = this[side].power ? Math.max(this[side].power | 0, 1) : 0;
|
||||||
document.getElementById("battlePower_" + side).innerHTML = UIvalue;
|
document.getElementById("battlePower_" + side).innerHTML = UIvalue;
|
||||||
}
|
}
|
||||||
|
|
@ -723,11 +892,13 @@ class Battle {
|
||||||
|
|
||||||
const status = battleStatus[+P(0.7)];
|
const status = battleStatus[+P(0.7)];
|
||||||
const result = `The ${this.getTypeName(this.type)} ended in ${status}`;
|
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(
|
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(
|
||||||
this.defenders.regiments,
|
this.attackers.regiments,
|
||||||
0
|
1
|
||||||
)}. ${result}.
|
)} and ${getSide(this.defenders.regiments, 0)}. ${result}.
|
||||||
\r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`;
|
\r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(
|
||||||
|
this.defenders.casualties
|
||||||
|
)}%`;
|
||||||
notes.push({id: `marker${i}`, name: this.name, legend});
|
notes.push({id: `marker${i}`, name: this.name, legend});
|
||||||
|
|
||||||
tip(`${this.name} is over. ${result}`, true, "success", 4000);
|
tip(`${this.name} is over. ${result}`, true, "success", 4000);
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ function editBiomes() {
|
||||||
const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
|
const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||||
refreshBiomesEditor();
|
refreshBiomesEditor();
|
||||||
|
|
||||||
if (modules.editBiomes) return;
|
if (fmg.modules.editBiomes) return;
|
||||||
modules.editBiomes = true;
|
fmg.modules.editBiomes = true;
|
||||||
|
|
||||||
$("#biomesEditor").dialog({
|
$("#biomesEditor").dialog({
|
||||||
title: "Biomes Editor",
|
title: "Biomes Editor",
|
||||||
|
|
@ -88,7 +88,9 @@ function editBiomes() {
|
||||||
const rural = b.rural[i] * populationRate;
|
const rural = b.rural[i] * populationRate;
|
||||||
const urban = b.urban[i] * populationRate * urbanization;
|
const urban = b.urban[i] * populationRate * urbanization;
|
||||||
const population = rn(rural + urban);
|
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;
|
totalArea += area;
|
||||||
totalPopulation += population;
|
totalPopulation += population;
|
||||||
|
|
||||||
|
|
@ -104,7 +106,9 @@ function editBiomes() {
|
||||||
data-color=${b.color[i]}
|
data-color=${b.color[i]}
|
||||||
>
|
>
|
||||||
<fill-box fill="${b.color[i]}"></fill-box>
|
<fill-box fill="${b.color[i]}"></fill-box>
|
||||||
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false" />
|
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${
|
||||||
|
b.name[i]
|
||||||
|
}" autocorrect="off" spellcheck="false" />
|
||||||
<span data-tip="Biome habitability percent" class="hide">%</span>
|
<span data-tip="Biome habitability percent" class="hide">%</span>
|
||||||
<input
|
<input
|
||||||
data-tip="Biome habitability percent. Click and set new value to change"
|
data-tip="Biome habitability percent. Click and set new value to change"
|
||||||
|
|
@ -121,7 +125,11 @@ function editBiomes() {
|
||||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||||
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div>
|
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div>
|
||||||
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span>
|
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span>
|
||||||
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ""}
|
${
|
||||||
|
i > 12 && !b.cells[i]
|
||||||
|
? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>'
|
||||||
|
: ""
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +411,14 @@ function editBiomes() {
|
||||||
|
|
||||||
// change of append new element
|
// change of append new element
|
||||||
if (exists.size()) exists.attr("data-biome", biomeNew).attr("fill", color).attr("stroke", color);
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ function editBurg(id) {
|
||||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}
|
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.editBurg) return;
|
if (fmg.modules.editBurg) return;
|
||||||
modules.editBurg = true;
|
fmg.modules.editBurg = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("burgGroupShow").addEventListener("click", showGroupSection);
|
document.getElementById("burgGroupShow").addEventListener("click", showGroupSection);
|
||||||
|
|
@ -284,7 +284,9 @@ function editBurg(id) {
|
||||||
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
|
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
|
||||||
basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
|
basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
|
||||||
}?
|
}?
|
||||||
<br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${burgsToRemove.length}`;
|
<br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${
|
||||||
|
burgsToRemove.length
|
||||||
|
}`;
|
||||||
$("#alert").dialog({
|
$("#alert").dialog({
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: "Remove burg group",
|
title: "Remove burg group",
|
||||||
|
|
@ -433,7 +435,8 @@ function editBurg(id) {
|
||||||
function addCustomMfcgLink() {
|
function addCustomMfcgLink() {
|
||||||
const id = +elSelected.attr("data-id");
|
const id = +elSelected.attr("data-id");
|
||||||
const burg = pack.burgs[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 => {
|
prompt(message, {default: burg.link || "", required: false}, link => {
|
||||||
if (link) burg.link = link;
|
if (link) burg.link = link;
|
||||||
else delete burg.link;
|
else delete burg.link;
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ function overviewBurgs() {
|
||||||
burgsOverviewAddLines();
|
burgsOverviewAddLines();
|
||||||
$("#burgsOverview").dialog();
|
$("#burgsOverview").dialog();
|
||||||
|
|
||||||
if (modules.overviewBurgs) return;
|
if (fmg.modules.overviewBurgs) return;
|
||||||
modules.overviewBurgs = true;
|
fmg.modules.overviewBurgs = true;
|
||||||
|
|
||||||
$("#burgsOverview").dialog({
|
$("#burgsOverview").dialog({
|
||||||
title: "Burgs Overview",
|
title: "Burgs Overview",
|
||||||
|
|
@ -93,7 +93,9 @@ function overviewBurgs() {
|
||||||
data-type="${type}"
|
data-type="${type}"
|
||||||
>
|
>
|
||||||
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
|
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
|
||||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false" />
|
<input data-tip="Burg name. Click and type to change" class="burgName" value="${
|
||||||
|
b.name
|
||||||
|
}" autocorrect="off" spellcheck="false" />
|
||||||
<input data-tip="Burg province" class="burgState" value="${province}" disabled />
|
<input data-tip="Burg province" class="burgState" value="${province}" disabled />
|
||||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled />
|
<input data-tip="Burg state" class="burgState" value="${state}" disabled />
|
||||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">
|
<select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">
|
||||||
|
|
@ -106,10 +108,14 @@ function overviewBurgs() {
|
||||||
data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}"
|
data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}"
|
||||||
class="icon-star-empty${b.capital ? "" : " inactive pointer"}"
|
class="icon-star-empty${b.capital ? "" : " inactive pointer"}"
|
||||||
></span>
|
></span>
|
||||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? "" : " inactive"}" style="font-size:.9em"></span>
|
<span data-tip="Click to toggle port status" class="icon-anchor pointer${
|
||||||
|
b.port ? "" : " inactive"
|
||||||
|
}" style="font-size:.9em"></span>
|
||||||
</div>
|
</div>
|
||||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||||
<span class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
<span class="locks pointer ${
|
||||||
|
b.lock ? "icon-lock" : "icon-lock-open inactive"
|
||||||
|
}" onmouseover="showElementLockTip(event)"></span>
|
||||||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
@ -125,8 +131,12 @@ function overviewBurgs() {
|
||||||
body.querySelectorAll("div > input.burgName").forEach(el => el.addEventListener("input", changeBurgName));
|
body.querySelectorAll("div > input.burgName").forEach(el => el.addEventListener("input", changeBurgName));
|
||||||
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomIntoBurg));
|
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomIntoBurg));
|
||||||
body.querySelectorAll("div > select.stateCulture").forEach(el => el.addEventListener("change", changeBurgCulture));
|
body.querySelectorAll("div > select.stateCulture").forEach(el => el.addEventListener("change", changeBurgCulture));
|
||||||
body.querySelectorAll("div > input.burgPopulation").forEach(el => el.addEventListener("change", changeBurgPopulation));
|
body
|
||||||
body.querySelectorAll("div > span.icon-star-empty").forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
.querySelectorAll("div > input.burgPopulation")
|
||||||
|
.forEach(el => el.addEventListener("change", changeBurgPopulation));
|
||||||
|
body
|
||||||
|
.querySelectorAll("div > span.icon-star-empty")
|
||||||
|
.forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
||||||
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus));
|
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus));
|
||||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus));
|
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus));
|
||||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor));
|
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor));
|
||||||
|
|
@ -137,7 +147,9 @@ function overviewBurgs() {
|
||||||
|
|
||||||
function getCultureOptions(culture) {
|
function getCultureOptions(culture) {
|
||||||
let options = "";
|
let options = "";
|
||||||
pack.cultures.filter(c => !c.removed).forEach(c => (options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`));
|
pack.cultures
|
||||||
|
.filter(c => !c.removed)
|
||||||
|
.forEach(c => (options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`));
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,7 +240,8 @@ function overviewBurgs() {
|
||||||
|
|
||||||
function triggerBurgRemove() {
|
function triggerBurgRemove() {
|
||||||
const burg = +this.parentNode.dataset.id;
|
const burg = +this.parentNode.dataset.id;
|
||||||
if (pack.burgs[burg].capital) return tip("You cannot remove the capital. Please change the capital first", false, "error");
|
if (pack.burgs[burg].capital)
|
||||||
|
return tip("You cannot remove the capital. Please change the capital first", false, "error");
|
||||||
|
|
||||||
confirmationDialog({
|
confirmationDialog({
|
||||||
title: "Remove burg",
|
title: "Remove burg",
|
||||||
|
|
@ -266,8 +279,10 @@ function overviewBurgs() {
|
||||||
function addBurgOnClick() {
|
function addBurgOnClick() {
|
||||||
const point = d3.mouse(this);
|
const point = d3.mouse(this);
|
||||||
const cell = findCell(point[0], point[1]);
|
const cell = findCell(point[0], point[1]);
|
||||||
if (pack.cells.h[cell] < 20) return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
if (pack.cells.h[cell] < 20)
|
||||||
if (pack.cells.burg[cell]) return tip("There is already a burg in this cell. Please select a free cell", false, "error");
|
return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||||
|
if (pack.cells.burg[cell])
|
||||||
|
return tip("There is already a burg in this cell. Please select a free cell", false, "error");
|
||||||
|
|
||||||
addBurg(point); // add new burg
|
addBurg(point); // add new burg
|
||||||
|
|
||||||
|
|
@ -301,7 +316,19 @@ function overviewBurgs() {
|
||||||
const capital = b.capital;
|
const capital = b.capital;
|
||||||
const province = pack.cells.province[b.cell];
|
const province = pack.cells.province[b.cell];
|
||||||
const parent = province ? province + states.length - 1 : b.state;
|
const parent = province ? province + states.length - 1 : b.state;
|
||||||
return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y};
|
return {
|
||||||
|
id,
|
||||||
|
i: b.i,
|
||||||
|
state: b.state,
|
||||||
|
culture: b.culture,
|
||||||
|
province,
|
||||||
|
parent,
|
||||||
|
name: b.name,
|
||||||
|
population,
|
||||||
|
capital,
|
||||||
|
x: b.x,
|
||||||
|
y: b.y
|
||||||
|
};
|
||||||
});
|
});
|
||||||
const data = states.concat(burgs);
|
const data = states.concat(burgs);
|
||||||
if (data.length < 2) return tip("No burgs to show", false, "error");
|
if (data.length < 2) return tip("No burgs to show", false, "error");
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ function editCoastline(node = d3.event.target) {
|
||||||
drawCoastlineVertices();
|
drawCoastlineVertices();
|
||||||
viewbox.on("touchmove mousemove", null);
|
viewbox.on("touchmove mousemove", null);
|
||||||
|
|
||||||
if (modules.editCoastline) return;
|
if (fmg.modules.editCoastline) return;
|
||||||
modules.editCoastline = true;
|
fmg.modules.editCoastline = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("coastlineGroupsShow").addEventListener("click", showGroupSection);
|
document.getElementById("coastlineGroupsShow").addEventListener("click", showGroupSection);
|
||||||
|
|
@ -55,7 +55,9 @@ function editCoastline(node = d3.event.target) {
|
||||||
.attr("r", 0.4)
|
.attr("r", 0.4)
|
||||||
.attr("data-v", d => d)
|
.attr("data-v", d => d)
|
||||||
.call(d3.drag().on("drag", dragVertex))
|
.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")
|
||||||
|
);
|
||||||
|
|
||||||
const area = pack.features[f].area;
|
const area = pack.features[f].area;
|
||||||
coastlineArea.innerHTML = si(getArea(area)) + " " + getAreaUnit();
|
coastlineArea.innerHTML = si(getArea(area)) + " " + getAreaUnit();
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ function editDiplomacy() {
|
||||||
refreshDiplomacyEditor();
|
refreshDiplomacyEditor();
|
||||||
viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick);
|
viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick);
|
||||||
|
|
||||||
if (modules.editDiplomacy) return;
|
if (fmg.modules.editDiplomacy) return;
|
||||||
modules.editDiplomacy = true;
|
fmg.modules.editDiplomacy = true;
|
||||||
|
|
||||||
$("#diplomacyEditor").dialog({
|
$("#diplomacyEditor").dialog({
|
||||||
title: "Diplomacy Editor",
|
title: "Diplomacy Editor",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// module stub to store common functions for ui editors
|
// module stub to store common functions for ui editors
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
modules.editors = true;
|
|
||||||
|
|
||||||
// restore default viewbox events
|
// restore default viewbox events
|
||||||
function restoreDefaultEvents() {
|
function restoreDefaultEvents() {
|
||||||
svg.call(zoom);
|
svg.call(zoom);
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,14 @@ function showElevationProfile(data, routeLen, isRiver) {
|
||||||
.attr("fill", "darkgray");
|
.attr("fill", "darkgray");
|
||||||
|
|
||||||
let colors = getColorScheme(terrs.attr("scheme"));
|
let colors = getColorScheme(terrs.attr("scheme"));
|
||||||
const landdef = chart.select("defs").append("linearGradient").attr("id", "landdef").attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
|
const landdef = chart
|
||||||
|
.select("defs")
|
||||||
|
.append("linearGradient")
|
||||||
|
.attr("id", "landdef")
|
||||||
|
.attr("x1", "0%")
|
||||||
|
.attr("y1", "0%")
|
||||||
|
.attr("x2", "0%")
|
||||||
|
.attr("y2", "100%");
|
||||||
|
|
||||||
if (chartData.mah == chartData.mih) {
|
if (chartData.mah == chartData.mih) {
|
||||||
landdef
|
landdef
|
||||||
|
|
@ -247,7 +254,14 @@ function showElevationProfile(data, routeLen, isRiver) {
|
||||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||||
path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||||
path += "Z";
|
path += "Z";
|
||||||
chart.append("g").attr("id", "epland").append("path").attr("d", path).attr("stroke", "purple").attr("stroke-width", "0").attr("fill", "url(#landdef)");
|
chart
|
||||||
|
.append("g")
|
||||||
|
.attr("id", "epland")
|
||||||
|
.append("path")
|
||||||
|
.attr("d", path)
|
||||||
|
.attr("stroke", "purple")
|
||||||
|
.attr("stroke-width", "0")
|
||||||
|
.attr("fill", "url(#landdef)");
|
||||||
|
|
||||||
// biome / heights
|
// biome / heights
|
||||||
let g = chart.append("g").attr("id", "epbiomes");
|
let g = chart.append("g").attr("id", "epbiomes");
|
||||||
|
|
@ -289,7 +303,14 @@ function showElevationProfile(data, routeLen, isRiver) {
|
||||||
chartData.cell[k] +
|
chartData.cell[k] +
|
||||||
")";
|
")";
|
||||||
|
|
||||||
g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip);
|
g.append("rect")
|
||||||
|
.attr("stroke", c)
|
||||||
|
.attr("fill", c)
|
||||||
|
.attr("x", x)
|
||||||
|
.attr("y", y)
|
||||||
|
.attr("width", xscale(1))
|
||||||
|
.attr("height", 15)
|
||||||
|
.attr("data-tip", dataTip);
|
||||||
}
|
}
|
||||||
|
|
||||||
const xAxis = d3
|
const xAxis = d3
|
||||||
|
|
@ -371,7 +392,17 @@ function showElevationProfile(data, routeLen, isRiver) {
|
||||||
// arrow from burg name to graph line
|
// arrow from burg name to graph line
|
||||||
g.append("path")
|
g.append("path")
|
||||||
.attr("id", "eparrow" + b)
|
.attr("id", "eparrow" + b)
|
||||||
.attr("d", "M" + x1.toString() + "," + (y1 + 3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1] - 3).toString())
|
.attr(
|
||||||
|
"d",
|
||||||
|
"M" +
|
||||||
|
x1.toString() +
|
||||||
|
"," +
|
||||||
|
(y1 + 3).toString() +
|
||||||
|
"L" +
|
||||||
|
x1.toString() +
|
||||||
|
"," +
|
||||||
|
parseInt(chartData.points[k][1] - 3).toString()
|
||||||
|
)
|
||||||
.attr("stroke", "darkgray")
|
.attr("stroke", "darkgray")
|
||||||
.attr("fill", "lightgray")
|
.attr("fill", "lightgray")
|
||||||
.attr("stroke-width", "1")
|
.attr("stroke-width", "1")
|
||||||
|
|
@ -385,6 +416,6 @@ function showElevationProfile(data, routeLen, isRiver) {
|
||||||
document.getElementById("epCurve").removeEventListener("change", draw);
|
document.getElementById("epCurve").removeEventListener("change", draw);
|
||||||
document.getElementById("epSave").removeEventListener("click", downloadCSV);
|
document.getElementById("epSave").removeEventListener("click", downloadCSV);
|
||||||
document.getElementById("elevationGraph").innerHTML = "";
|
document.getElementById("elevationGraph").innerHTML = "";
|
||||||
modules.elevation = false;
|
fmg.modules.elevation = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"use strict";
|
import {findCell} from "/src/utils/graphUtils";
|
||||||
// Module to store general UI functions
|
import {MOBILE} from "/src/constants";
|
||||||
|
|
||||||
// fit full-screen map if window is resized
|
// fit full-screen map if window is resized
|
||||||
window.addEventListener("resize", function (e) {
|
window.addEventListener("resize", function (e) {
|
||||||
|
|
@ -431,7 +431,7 @@ document.querySelectorAll("[data-locked]").forEach(function (e) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// lock option
|
// lock option
|
||||||
function lock(id) {
|
export function lock(id) {
|
||||||
const input = document.querySelector('[data-stored="' + id + '"]');
|
const input = document.querySelector('[data-stored="' + id + '"]');
|
||||||
if (input) store(id, input.value);
|
if (input) store(id, input.value);
|
||||||
const el = document.getElementById("lock_" + id);
|
const el = document.getElementById("lock_" + id);
|
||||||
|
|
@ -450,13 +450,13 @@ function unlock(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if option is locked
|
// check if option is locked
|
||||||
function locked(id) {
|
export function locked(id) {
|
||||||
const lockEl = document.getElementById("lock_" + id);
|
const lockEl = document.getElementById("lock_" + id);
|
||||||
return lockEl.dataset.locked == 1;
|
return lockEl.dataset.locked == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return key value stored in localStorage or null
|
// return key value stored in localStorage or null
|
||||||
function stored(key) {
|
export function stored(key) {
|
||||||
return localStorage.getItem(key) || null;
|
return localStorage.getItem(key) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -482,7 +482,7 @@ function speak(text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply drop-down menu option. If the value is not in options, add it
|
// apply drop-down menu option. If the value is not in options, add it
|
||||||
function applyOption($select, value, name = value) {
|
export function applyOption($select, value, name = value) {
|
||||||
const isExisting = Array.from($select.options).some(o => o.value === value);
|
const isExisting = Array.from($select.options).some(o => o.value === value);
|
||||||
if (!isExisting) $select.options.add(new Option(name, value));
|
if (!isExisting) $select.options.add(new Option(name, value));
|
||||||
$select.value = value;
|
$select.value = value;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ function editHeightmap(options) {
|
||||||
if (!mode) showModeDialog();
|
if (!mode) showModeDialog();
|
||||||
else enterHeightmapEditMode(mode);
|
else enterHeightmapEditMode(mode);
|
||||||
|
|
||||||
if (modules.editHeightmap) return;
|
if (fmg.modules.editHeightmap) return;
|
||||||
modules.editHeightmap = true;
|
fmg.modules.editHeightmap = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
byId("paintBrushes").on("click", openBrushesPanel);
|
byId("paintBrushes").on("click", openBrushesPanel);
|
||||||
|
|
@ -29,7 +29,10 @@ function editHeightmap(options) {
|
||||||
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
|
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
|
||||||
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
|
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
|
||||||
<p>Please <span class="pseudoLink" onclick="dowloadMap();">save the map</span> before editing the heightmap!</p>
|
<p>Please <span class="pseudoLink" onclick="dowloadMap();">save the map</span> before editing the heightmap!</p>
|
||||||
<p style="margin-bottom: 0">Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
|
<p style="margin-bottom: 0">Check out ${link(
|
||||||
|
"https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization",
|
||||||
|
"wiki"
|
||||||
|
)} for guidance.</p>`;
|
||||||
|
|
||||||
$("#alert").dialog({
|
$("#alert").dialog({
|
||||||
resizable: false,
|
resizable: false,
|
||||||
|
|
@ -148,7 +151,11 @@ function editHeightmap(options) {
|
||||||
// Exit customization mode
|
// Exit customization mode
|
||||||
function finalizeHeightmap() {
|
function finalizeHeightmap() {
|
||||||
if (viewbox.select("#heights").selectAll("*").size() < 200)
|
if (viewbox.select("#heights").selectAll("*").size() < 200)
|
||||||
return tip("Insufficient land area! There should be at least 200 land cells to finalize the heightmap", null, "error");
|
return tip(
|
||||||
|
"Insufficient land area! There should be at least 200 land cells to finalize the heightmap",
|
||||||
|
null,
|
||||||
|
"error"
|
||||||
|
);
|
||||||
if (byId("imageConverter").offsetParent) return tip("Please exit the Image Conversion mode first", null, "error");
|
if (byId("imageConverter").offsetParent) return tip("Please exit the Image Conversion mode first", null, "error");
|
||||||
|
|
||||||
delete window.edits; // remove global variable
|
delete window.edits; // remove global variable
|
||||||
|
|
@ -210,7 +217,8 @@ function editHeightmap(options) {
|
||||||
if (!erosionAllowed) {
|
if (!erosionAllowed) {
|
||||||
for (const i of pack.cells.i) {
|
for (const i of pack.cells.i) {
|
||||||
const g = pack.cells.g[i];
|
const g = pack.cells.g[i];
|
||||||
if (pack.cells.h[i] !== grid.cells.h[g] && pack.cells.h[i] >= 20 === grid.cells.h[g] >= 20) pack.cells.h[i] = grid.cells.h[g];
|
if (pack.cells.h[i] !== grid.cells.h[g] && pack.cells.h[i] >= 20 === grid.cells.h[g] >= 20)
|
||||||
|
pack.cells.h[i] = grid.cells.h[g];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,7 +357,8 @@ function editHeightmap(options) {
|
||||||
const isLand = pack.cells.h[i] >= 20;
|
const isLand = pack.cells.h[i] >= 20;
|
||||||
|
|
||||||
// check biome
|
// check biome
|
||||||
pack.cells.biome[i] = isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]);
|
pack.cells.biome[i] =
|
||||||
|
isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]);
|
||||||
|
|
||||||
// rivers data
|
// rivers data
|
||||||
if (!erosionAllowed) {
|
if (!erosionAllowed) {
|
||||||
|
|
@ -373,7 +382,9 @@ function editHeightmap(options) {
|
||||||
const findBurgCell = function (x, y) {
|
const findBurgCell = function (x, y) {
|
||||||
let i = findCell(x, y);
|
let i = findCell(x, y);
|
||||||
if (pack.cells.h[i] >= 20) return i;
|
if (pack.cells.h[i] >= 20) return i;
|
||||||
const dist = pack.cells.c[i].map(c => (pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2));
|
const dist = pack.cells.c[i].map(c =>
|
||||||
|
pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2
|
||||||
|
);
|
||||||
return pack.cells.c[i][d3.scan(dist)];
|
return pack.cells.c[i][d3.scan(dist)];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -551,8 +562,8 @@ function editHeightmap(options) {
|
||||||
})
|
})
|
||||||
.on("dialogclose", exitBrushMode);
|
.on("dialogclose", exitBrushMode);
|
||||||
|
|
||||||
if (modules.openBrushesPanel) return;
|
if (fmg.modules.openBrushesPanel) return;
|
||||||
modules.openBrushesPanel = true;
|
fmg.modules.openBrushesPanel = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
byId("brushesButtons").on("click", e => toggleBrushMode(e));
|
byId("brushesButtons").on("click", e => toggleBrushMode(e));
|
||||||
|
|
@ -630,15 +641,25 @@ function editHeightmap(options) {
|
||||||
|
|
||||||
const brush = document.querySelector("#brushesButtons > button.pressed").id;
|
const brush = document.querySelector("#brushesButtons > button.pressed").id;
|
||||||
if (brush === "brushRaise") s.forEach(i => (h[i] = h[i] < 20 ? 20 : lim(h[i] + power)));
|
if (brush === "brushRaise") s.forEach(i => (h[i] = h[i] < 20 ? 20 : lim(h[i] + power)));
|
||||||
else if (brush === "brushElevate") s.forEach((i, d) => (h[i] = lim(h[i] + interpolate(d / Math.max(s.length - 1, 1)))));
|
else if (brush === "brushElevate")
|
||||||
|
s.forEach((i, d) => (h[i] = lim(h[i] + interpolate(d / Math.max(s.length - 1, 1)))));
|
||||||
else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power)));
|
else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power)));
|
||||||
else if (brush === "brushDepress") s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1)))));
|
else if (brush === "brushDepress")
|
||||||
|
s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1)))));
|
||||||
else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start])));
|
else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start])));
|
||||||
else if (brush === "brushSmooth")
|
else if (brush === "brushSmooth")
|
||||||
s.forEach(
|
s.forEach(
|
||||||
i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1))
|
i =>
|
||||||
|
(h[i] = rn(
|
||||||
|
(d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) +
|
||||||
|
h[i] * (10 - power) +
|
||||||
|
0.6) /
|
||||||
|
(11 - power),
|
||||||
|
1
|
||||||
|
))
|
||||||
);
|
);
|
||||||
else if (brush === "brushDisrupt") s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
|
else if (brush === "brushDisrupt")
|
||||||
|
s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
|
||||||
|
|
||||||
mockHeightmapSelection(s);
|
mockHeightmapSelection(s);
|
||||||
// updateHistory(); uncomment to update history every step
|
// updateHistory(); uncomment to update history every step
|
||||||
|
|
@ -662,7 +683,8 @@ function editHeightmap(options) {
|
||||||
const operator = conditionSign.value;
|
const operator = conditionSign.value;
|
||||||
const operand = rescaleModifier.valueAsNumber;
|
const operand = rescaleModifier.valueAsNumber;
|
||||||
if (Number.isNaN(operand)) return tip("Operand should be a number", false, "error");
|
if (Number.isNaN(operand)) return tip("Operand should be a number", false, "error");
|
||||||
if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) return tip("Operand should be an integer", false, "error");
|
if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand))
|
||||||
|
return tip("Operand should be an integer", false, "error");
|
||||||
|
|
||||||
HeightmapGenerator.setGraph(grid);
|
HeightmapGenerator.setGraph(grid);
|
||||||
|
|
||||||
|
|
@ -691,7 +713,8 @@ function editHeightmap(options) {
|
||||||
function startFromScratch() {
|
function startFromScratch() {
|
||||||
if (changeOnlyLand.checked) return tip("Not allowed when 'Change only land cells' mode is set", false, "error");
|
if (changeOnlyLand.checked) return tip("Not allowed when 'Change only land cells' mode is set", false, "error");
|
||||||
const someHeights = grid.cells.h.some(h => h);
|
const someHeights = grid.cells.h.some(h => h);
|
||||||
if (!someHeights) return tip("Heightmap is already cleared, please do not click twice if not required", false, "error");
|
if (!someHeights)
|
||||||
|
return tip("Heightmap is already cleared, please do not click twice if not required", false, "error");
|
||||||
|
|
||||||
grid.cells.h = new Uint8Array(grid.cells.i.length);
|
grid.cells.h = new Uint8Array(grid.cells.i.length);
|
||||||
viewbox.select("#heights").selectAll("*").remove();
|
viewbox.select("#heights").selectAll("*").remove();
|
||||||
|
|
@ -711,10 +734,15 @@ function editHeightmap(options) {
|
||||||
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.openTemplateEditor) return;
|
if (fmg.modules.openTemplateEditor) return;
|
||||||
modules.openTemplateEditor = true;
|
fmg.modules.openTemplateEditor = true;
|
||||||
|
|
||||||
$("#templateBody").sortable({items: "> div", handle: ".icon-resize-vertical", containment: "#templateBody", axis: "y"});
|
$("#templateBody").sortable({
|
||||||
|
items: "> div",
|
||||||
|
handle: ".icon-resize-vertical",
|
||||||
|
containment: "#templateBody",
|
||||||
|
axis: "y"
|
||||||
|
});
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
$body.on("click", function (ev) {
|
$body.on("click", function (ev) {
|
||||||
|
|
@ -788,22 +816,31 @@ function editHeightmap(options) {
|
||||||
const common = /* html */ `<div data-type="${type}">${Hide}<div style="width:4em">${type}</div>${Trash}${Reorder}`;
|
const common = /* html */ `<div data-type="${type}">${Hide}<div style="width:4em">${type}</div>${Trash}${Reorder}`;
|
||||||
|
|
||||||
const TempY = /* html */ `<span>y:
|
const TempY = /* html */ `<span>y:
|
||||||
<input class="templateY" data-tip="Placement range percentage along Y axis (minY-maxY)" value=${arg5 || "20-80"} />
|
<input class="templateY" data-tip="Placement range percentage along Y axis (minY-maxY)" value=${
|
||||||
|
arg5 || "20-80"
|
||||||
|
} />
|
||||||
</span>`;
|
</span>`;
|
||||||
|
|
||||||
const TempX = /* html */ `<span>x:
|
const TempX = /* html */ `<span>x:
|
||||||
<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4 || "15-85"} />
|
<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${
|
||||||
|
arg4 || "15-85"
|
||||||
|
} />
|
||||||
</span>`;
|
</span>`;
|
||||||
|
|
||||||
const Height = /* html */ `<span>h:
|
const Height = /* html */ `<span>h:
|
||||||
<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${arg3 || "40-50"} />
|
<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${
|
||||||
|
arg3 || "40-50"
|
||||||
|
} />
|
||||||
</span>`;
|
</span>`;
|
||||||
|
|
||||||
const Count = /* html */ `<span>n:
|
const Count = /* html */ `<span>n:
|
||||||
<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count || "1-2"} />
|
<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${
|
||||||
|
count || "1-2"
|
||||||
|
} />
|
||||||
</span>`;
|
</span>`;
|
||||||
|
|
||||||
if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return /* html */ `${common}${TempY}${TempX}${Height}${Count}</div>`;
|
if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough")
|
||||||
|
return /* html */ `${common}${TempY}${TempX}${Height}${Count}</div>`;
|
||||||
|
|
||||||
if (type === "Strait")
|
if (type === "Strait")
|
||||||
return /* html */ `${common}
|
return /* html */ `${common}
|
||||||
|
|
@ -814,7 +851,9 @@ function editHeightmap(options) {
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
<span>w:
|
<span>w:
|
||||||
<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${count || "2-7"} />
|
<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${
|
||||||
|
count || "2-7"
|
||||||
|
} />
|
||||||
</span>
|
</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
|
@ -1042,8 +1081,8 @@ function editHeightmap(options) {
|
||||||
viewbox.select("#heights").selectAll("*").remove();
|
viewbox.select("#heights").selectAll("*").remove();
|
||||||
updateHistory();
|
updateHistory();
|
||||||
|
|
||||||
if (modules.openImageConverter) return;
|
if (fmg.modules.openImageConverter) return;
|
||||||
modules.openImageConverter = true;
|
fmg.modules.openImageConverter = true;
|
||||||
|
|
||||||
// add color pallete
|
// add color pallete
|
||||||
void (function createColorPallete() {
|
void (function createColorPallete() {
|
||||||
|
|
@ -1245,7 +1284,8 @@ function editHeightmap(options) {
|
||||||
const assinged = []; // store assigned heights
|
const assinged = []; // store assigned heights
|
||||||
unassigned.forEach(el => {
|
unassigned.forEach(el => {
|
||||||
const clr = el.dataset.color;
|
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));
|
const colorTo = color(1 - (height < 20 ? (height - 5) / 100 : height / 100));
|
||||||
viewbox
|
viewbox
|
||||||
.select("#heights")
|
.select("#heights")
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ function handleKeydown(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyup(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
|
if (!allowHotkeys()) return; // in some cases (e.g. in a textarea) hotkeys are not allowed
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ function editIce() {
|
||||||
close: closeEditor
|
close: closeEditor
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.editIce) return;
|
if (fmg.modules.editIce) return;
|
||||||
modules.editIce = true;
|
fmg.modules.editIce = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("iceEditStyle").addEventListener("click", () => editStyle("ice"));
|
document.getElementById("iceEditStyle").addEventListener("click", () => editStyle("ice"));
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ function editLabel() {
|
||||||
selectLabelGroup(text);
|
selectLabelGroup(text);
|
||||||
updateValues(textPath);
|
updateValues(textPath);
|
||||||
|
|
||||||
if (modules.editLabel) return;
|
if (fmg.modules.editLabel) return;
|
||||||
modules.editLabel = true;
|
fmg.modules.editLabel = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("labelGroupShow").addEventListener("click", showGroupSection);
|
document.getElementById("labelGroupShow").addEventListener("click", showGroupSection);
|
||||||
|
|
@ -78,7 +78,9 @@ function editLabel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateValues(textPath) {
|
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("labelStartOffset").value = parseFloat(textPath.getAttribute("startOffset"));
|
||||||
document.getElementById("labelRelativeSize").value = parseFloat(textPath.getAttribute("font-size"));
|
document.getElementById("labelRelativeSize").value = parseFloat(textPath.getAttribute("font-size"));
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +300,13 @@ function editLabel() {
|
||||||
function changeText() {
|
function changeText() {
|
||||||
const input = document.getElementById("labelText").value;
|
const input = document.getElementById("labelText").value;
|
||||||
const el = elSelected.select("textPath").node();
|
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 lines = input.split("|");
|
||||||
const top = (lines.length - 1) / -2; // y offset
|
const top = (lines.length - 1) / -2; // y offset
|
||||||
|
|
@ -313,7 +321,8 @@ function editLabel() {
|
||||||
el.innerHTML = inner;
|
el.innerHTML = inner;
|
||||||
example.remove();
|
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() {
|
function generateRandomName() {
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ function editLake() {
|
||||||
drawLakeVertices();
|
drawLakeVertices();
|
||||||
viewbox.on("touchmove mousemove", null);
|
viewbox.on("touchmove mousemove", null);
|
||||||
|
|
||||||
if (modules.editLake) return;
|
if (fmg.modules.editLake) return;
|
||||||
modules.editLake = true;
|
fmg.modules.editLake = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("lakeName").addEventListener("input", changeName);
|
document.getElementById("lakeName").addEventListener("input", changeName);
|
||||||
|
|
@ -48,7 +48,8 @@ function editLake() {
|
||||||
document.getElementById("lakeArea").value = si(getArea(l.area)) + " " + getAreaUnit();
|
document.getElementById("lakeArea").value = si(getArea(l.area)) + " " + getAreaUnit();
|
||||||
|
|
||||||
const length = d3.polygonLength(l.vertices.map(v => pack.vertices.p[v]));
|
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 lakeCells = Array.from(cells.i.filter(i => cells.f[i] === l.i));
|
||||||
const heights = lakeCells.map(i => cells.h[i]);
|
const heights = lakeCells.map(i => cells.h[i]);
|
||||||
|
|
@ -91,7 +92,9 @@ function editLake() {
|
||||||
.attr("r", 0.4)
|
.attr("r", 0.4)
|
||||||
.attr("data-v", d => d)
|
.attr("data-v", d => d)
|
||||||
.call(d3.drag().on("drag", dragVertex))
|
.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() {
|
function dragVertex() {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// UI module stub to control map layers
|
import {TIME} from "/src/config/logging";
|
||||||
"use strict";
|
import {invokeActiveZooming} from "../activeZooming";
|
||||||
|
|
||||||
let presets = {}; // global object
|
let presets = {}; // global object
|
||||||
restoreCustomPresets(); // run on-load
|
restoreCustomPresets(); // run on-load
|
||||||
|
|
@ -946,7 +946,7 @@ function toggleStates(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawStates() {
|
export function drawStates() {
|
||||||
TIME && console.time("drawStates");
|
TIME && console.time("drawStates");
|
||||||
regions.selectAll("path").remove();
|
regions.selectAll("path").remove();
|
||||||
|
|
||||||
|
|
@ -1110,7 +1110,7 @@ function toggleBorders(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw state and province borders
|
// draw state and province borders
|
||||||
function drawBorders() {
|
export function drawBorders() {
|
||||||
TIME && console.time("drawBorders");
|
TIME && console.time("drawBorders");
|
||||||
borders.selectAll("path").remove();
|
borders.selectAll("path").remove();
|
||||||
|
|
||||||
|
|
@ -1554,7 +1554,7 @@ function toggleRivers(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawRivers() {
|
export function drawRivers() {
|
||||||
TIME && console.time("drawRivers");
|
TIME && console.time("drawRivers");
|
||||||
rivers.selectAll("*").remove();
|
rivers.selectAll("*").remove();
|
||||||
|
|
||||||
|
|
@ -1870,7 +1870,7 @@ function drawEmblems() {
|
||||||
TIME && console.timeEnd("drawEmblems");
|
TIME && console.timeEnd("drawEmblems");
|
||||||
}
|
}
|
||||||
|
|
||||||
function layerIsOn(el) {
|
export function layerIsOn(el) {
|
||||||
const buttonoff = document.getElementById(el).classList.contains("buttonoff");
|
const buttonoff = document.getElementById(el).classList.contains("buttonoff");
|
||||||
return !buttonoff;
|
return !buttonoff;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ function overviewMilitary() {
|
||||||
addLines();
|
addLines();
|
||||||
$("#militaryOverview").dialog();
|
$("#militaryOverview").dialog();
|
||||||
|
|
||||||
if (modules.overviewMilitary) return;
|
if (fmg.modules.overviewMilitary) return;
|
||||||
modules.overviewMilitary = true;
|
fmg.modules.overviewMilitary = true;
|
||||||
updateHeaders();
|
updateHeaders();
|
||||||
|
|
||||||
$("#militaryOverview").dialog({
|
$("#militaryOverview").dialog({
|
||||||
|
|
@ -54,7 +54,9 @@ function overviewMilitary() {
|
||||||
const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html);
|
const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html);
|
||||||
for (const u of options.military) {
|
for (const u of options.military) {
|
||||||
const label = capitalize(u.name.replace(/_/g, " "));
|
const label = capitalize(u.name.replace(/_/g, " "));
|
||||||
insert(`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
insert(
|
||||||
|
`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
header.querySelectorAll(".removable").forEach(function (e) {
|
header.querySelectorAll(".removable").forEach(function (e) {
|
||||||
e.addEventListener("click", function () {
|
e.addEventListener("click", function () {
|
||||||
|
|
@ -76,7 +78,9 @@ function overviewMilitary() {
|
||||||
const rate = (total / population) * 100;
|
const rate = (total / population) * 100;
|
||||||
|
|
||||||
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
|
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
|
||||||
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
|
const lineData = options.military
|
||||||
|
.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
lines += /* html */ `<div
|
lines += /* html */ `<div
|
||||||
class="states"
|
class="states"
|
||||||
|
|
@ -91,9 +95,14 @@ function overviewMilitary() {
|
||||||
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
|
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
|
||||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly />
|
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly />
|
||||||
${lineData}
|
${lineData}
|
||||||
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>
|
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(
|
||||||
|
total
|
||||||
|
)}</div>
|
||||||
<div data-type="population" data-tip="State population">${si(population)}</div>
|
<div data-type="population" data-tip="State population">${si(population)}</div>
|
||||||
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div>
|
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(
|
||||||
|
rate,
|
||||||
|
2
|
||||||
|
)}%</div>
|
||||||
<input
|
<input
|
||||||
data-tip="War Alert. Editable modifier to military forces number, depends of political situation"
|
data-tip="War Alert. Editable modifier to military forces number, depends of political situation"
|
||||||
style="width:4.1em"
|
style="width:4.1em"
|
||||||
|
|
@ -131,7 +140,9 @@ function overviewMilitary() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const getForces = u => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
|
const getForces = u => 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 population = rn((s.rural + s.urban * urbanization) * populationRate);
|
||||||
const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0));
|
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"},
|
position: {my: "center", at: "center", of: "svg"},
|
||||||
buttons: {
|
buttons: {
|
||||||
Apply: applyMilitaryOptions,
|
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,
|
Restore: restoreDefaultUnits,
|
||||||
Cancel: function () {
|
Cancel: function () {
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
|
|
@ -254,8 +274,8 @@ function overviewMilitary() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.overviewMilitaryCustomize) return;
|
if (fmg.modules.overviewMilitaryCustomize) return;
|
||||||
modules.overviewMilitaryCustomize = true;
|
fmg.modules.overviewMilitaryCustomize = true;
|
||||||
|
|
||||||
tableBody.addEventListener("click", event => {
|
tableBody.addEventListener("click", event => {
|
||||||
const el = event.target;
|
const el = event.target;
|
||||||
|
|
@ -294,7 +314,9 @@ function overviewMilitary() {
|
||||||
function addUnitLine(unit) {
|
function addUnitLine(unit) {
|
||||||
const {type, icon, name, rural, urban, power, crew, separate} = unit;
|
const {type, icon, name, rural, urban, power, crew, separate} = unit;
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
const typeOptions = types.map(t => `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ");
|
const typeOptions = types
|
||||||
|
.map(t => `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
const getLimitButton = attr =>
|
const getLimitButton = attr =>
|
||||||
`<button
|
`<button
|
||||||
|
|
@ -305,7 +327,9 @@ function overviewMilitary() {
|
||||||
${getLimitText(unit[attr])}
|
${getLimitText(unit[attr])}
|
||||||
</button>`;
|
</button>`;
|
||||||
|
|
||||||
row.innerHTML = /* html */ `<td><button data-type="icon" data-tip="Click to select unit icon">${icon || " "}</button></td>
|
row.innerHTML = /* html */ `<td><button data-type="icon" data-tip="Click to select unit icon">${
|
||||||
|
icon || " "
|
||||||
|
}</button></td>
|
||||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${name}" /></td>
|
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${name}" /></td>
|
||||||
<td>${getLimitButton("biomes")}</td>
|
<td>${getLimitButton("biomes")}</td>
|
||||||
<td>${getLimitButton("states")}</td>
|
<td>${getLimitButton("states")}</td>
|
||||||
|
|
@ -344,7 +368,9 @@ function overviewMilitary() {
|
||||||
const lines = filtered.map(
|
const lines = filtered.map(
|
||||||
({i, name, fullName, color}) =>
|
({i, name, fullName, color}) =>
|
||||||
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
|
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
|
||||||
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${!initial.length || initial.includes(i) ? "checked" : ""} >
|
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${
|
||||||
|
!initial.length || initial.includes(i) ? "checked" : ""
|
||||||
|
} >
|
||||||
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
|
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
|
||||||
</td></tr>`
|
</td></tr>`
|
||||||
);
|
);
|
||||||
|
|
@ -395,7 +421,8 @@ function overviewMilitary() {
|
||||||
$("#militaryOptions").dialog("close");
|
$("#militaryOptions").dialog("close");
|
||||||
options.military = unitLines.map((r, i) => {
|
options.military = unitLines.map((r, i) => {
|
||||||
const elements = Array.from(r.querySelectorAll("input, button, select"));
|
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 [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] =
|
||||||
|
elements.map(el => {
|
||||||
const {type, value} = el.dataset || {};
|
const {type, value} = el.dataset || {};
|
||||||
if (type === "icon") return el.innerHTML || "⠀";
|
if (type === "icon") return el.innerHTML || "⠀";
|
||||||
if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
|
if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
|
||||||
|
|
@ -419,7 +446,8 @@ function overviewMilitary() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function militaryRecalculate() {
|
function militaryRecalculate() {
|
||||||
alertMessage.innerHTML = "Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated";
|
alertMessage.innerHTML =
|
||||||
|
"Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated";
|
||||||
$("#alert").dialog({
|
$("#alert").dialog({
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: "Remove regiment",
|
title: "Remove regiment",
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ function editNamesbase() {
|
||||||
closeDialogs("#namesbaseEditor, .stable");
|
closeDialogs("#namesbaseEditor, .stable");
|
||||||
$("#namesbaseEditor").dialog();
|
$("#namesbaseEditor").dialog();
|
||||||
|
|
||||||
if (modules.editNamesbase) return;
|
if (fmg.modules.editNamesbase) return;
|
||||||
modules.editNamesbase = true;
|
fmg.modules.editNamesbase = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("namesbaseSelect").addEventListener("change", updateInputs);
|
document.getElementById("namesbaseSelect").addEventListener("change", updateInputs);
|
||||||
|
|
@ -23,15 +23,23 @@ function editNamesbase() {
|
||||||
|
|
||||||
const uploader = document.getElementById("namesbaseToLoad");
|
const uploader = document.getElementById("namesbaseToLoad");
|
||||||
document.getElementById("namesbaseUpload").addEventListener("click", () => {
|
document.getElementById("namesbaseUpload").addEventListener("click", () => {
|
||||||
uploader.addEventListener("change", function (event) {
|
uploader.addEventListener(
|
||||||
|
"change",
|
||||||
|
function (event) {
|
||||||
uploadFile(event.target, d => namesbaseUpload(d, true));
|
uploadFile(event.target, d => namesbaseUpload(d, true));
|
||||||
}, { once: true });
|
},
|
||||||
|
{once: true}
|
||||||
|
);
|
||||||
uploader.click();
|
uploader.click();
|
||||||
});
|
});
|
||||||
document.getElementById("namesbaseUploadExtend").addEventListener("click", () => {
|
document.getElementById("namesbaseUploadExtend").addEventListener("click", () => {
|
||||||
uploader.addEventListener("change", function (event) {
|
uploader.addEventListener(
|
||||||
|
"change",
|
||||||
|
function (event) {
|
||||||
uploadFile(event.target, d => namesbaseUpload(d, false));
|
uploadFile(event.target, d => namesbaseUpload(d, false));
|
||||||
}, { once: true });
|
},
|
||||||
|
{once: true}
|
||||||
|
);
|
||||||
uploader.click();
|
uploader.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -147,21 +155,28 @@ function editNamesbase() {
|
||||||
: "none";
|
: "none";
|
||||||
|
|
||||||
const geminate = namesArray.map(name => name.match(/[^\w\s]|(.)(?=\1)/g) || []).flat();
|
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 duplicates = unique(namesArray.filter((e, i, a) => a.indexOf(e) !== i)).join(", ") || "none";
|
||||||
const multiwordRate = d3.mean(namesArray.map(n => +n.includes(" ")));
|
const multiwordRate = d3.mean(namesArray.map(n => +n.includes(" ")));
|
||||||
|
|
||||||
const getLengthQuality = () => {
|
const getLengthQuality = () => {
|
||||||
if (length < 30) return "<span data-tip='Namesbase contains < 30 names - not enough to generate reasonable data' style='color:red'>[not enough]</span>";
|
if (length < 30)
|
||||||
if (length < 100) return "<span data-tip='Namesbase contains < 100 names - not enough to generate good names' style='color:darkred'>[low]</span>";
|
return "<span data-tip='Namesbase contains < 30 names - not enough to generate reasonable data' style='color:red'>[not enough]</span>";
|
||||||
if (length <= 400) return "<span data-tip='Namesbase contains a reasonable number of samples' style='color:green'>[good]</span>";
|
if (length < 100)
|
||||||
|
return "<span data-tip='Namesbase contains < 100 names - not enough to generate good names' style='color:darkred'>[low]</span>";
|
||||||
|
if (length <= 400)
|
||||||
|
return "<span data-tip='Namesbase contains a reasonable number of samples' style='color:green'>[good]</span>";
|
||||||
return "<span data-tip='Namesbase contains > 400 names. That is too much, try to reduce it to ~300 names' style='color:darkred'>[overmuch]</span>";
|
return "<span data-tip='Namesbase contains > 400 names. That is too much, try to reduce it to ~300 names' style='color:darkred'>[overmuch]</span>";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVarietyLevel = () => {
|
const getVarietyLevel = () => {
|
||||||
if (variety < 15) return "<span data-tip='Namesbase average variety < 15 - generated names will be too repetitive' style='color:red'>[low]</span>";
|
if (variety < 15)
|
||||||
if (variety < 30) return "<span data-tip='Namesbase average variety < 30 - names can be too repetitive' style='color:orange'>[mean]</span>";
|
return "<span data-tip='Namesbase average variety < 15 - generated names will be too repetitive' style='color:red'>[low]</span>";
|
||||||
|
if (variety < 30)
|
||||||
|
return "<span data-tip='Namesbase average variety < 30 - names can be too repetitive' style='color:orange'>[mean]</span>";
|
||||||
return "<span data-tip='Namesbase variety is good' style='color:green'>[good]</span>";
|
return "<span data-tip='Namesbase variety is good' style='color:green'>[good]</span>";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -175,9 +190,14 @@ function editNamesbase() {
|
||||||
<div data-tip="Common name length">Median name length: ${d3.median(wordsLength)}</div>
|
<div data-tip="Common name length">Median name length: ${d3.median(wordsLength)}</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div data-tip="Characters outside of Basic Latin have bad font support">Non-basic chars: ${nonBasicLatinChars}</div>
|
<div data-tip="Characters outside of Basic Latin have bad font support">Non-basic chars: ${nonBasicLatinChars}</div>
|
||||||
<div data-tip="Characters that are frequently (more than 3 times) doubled">Doubled chars: ${doubled.join("")}</div>
|
<div data-tip="Characters that are frequently (more than 3 times) doubled">Doubled chars: ${doubled.join(
|
||||||
|
""
|
||||||
|
)}</div>
|
||||||
<div data-tip="Names used more than one time">Duplicates: ${duplicates}</div>
|
<div data-tip="Names used more than one time">Duplicates: ${duplicates}</div>
|
||||||
<div data-tip="Percentage of names containing space character">Multi-word names: ${rn(multiwordRate * 100, 2)}%</div>
|
<div data-tip="Percentage of names containing space character">Multi-word names: ${rn(
|
||||||
|
multiwordRate * 100,
|
||||||
|
2
|
||||||
|
)}%</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
$("#alert").dialog({
|
$("#alert").dialog({
|
||||||
|
|
@ -194,7 +214,8 @@ function editNamesbase() {
|
||||||
|
|
||||||
function namesbaseAdd() {
|
function namesbaseAdd() {
|
||||||
const base = nameBases.length;
|
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});
|
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").add(new Option("Base" + base, base));
|
||||||
document.getElementById("namesbaseSelect").value = base;
|
document.getElementById("namesbaseSelect").value = base;
|
||||||
|
|
@ -232,7 +253,7 @@ function editNamesbase() {
|
||||||
downloadFile(data, name);
|
downloadFile(data, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function namesbaseUpload(dataLoaded, override=true) {
|
function namesbaseUpload(dataLoaded, override = true) {
|
||||||
const data = dataLoaded.split("\r\n");
|
const data = dataLoaded.split("\r\n");
|
||||||
if (!data || !data[0]) {
|
if (!data || !data[0]) {
|
||||||
tip("Cannot load a namesbase. Please check the data format", false, "error");
|
tip("Cannot load a namesbase. Please check the data format", false, "error");
|
||||||
|
|
|
||||||
|
|
@ -69,12 +69,12 @@ function editNotes(id, name) {
|
||||||
if (!window.tinymce) {
|
if (!window.tinymce) {
|
||||||
const url = "https://cdn.tiny.cloud/1/4i6a79ymt2y0cagke174jp3meoi28vyecrch12e5puyw3p9a/tinymce/5/tinymce.min.js";
|
const url = "https://cdn.tiny.cloud/1/4i6a79ymt2y0cagke174jp3meoi28vyecrch12e5puyw3p9a/tinymce/5/tinymce.min.js";
|
||||||
try {
|
try {
|
||||||
await import(url);
|
await import(/* @vite-ignore */ url);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// error may be caused by failed request being cached, try again with random hash
|
// error may be caused by failed request being cached, try again with random hash
|
||||||
try {
|
try {
|
||||||
const hash = Math.random().toString(36).substring(2, 15);
|
const hash = Math.random().toString(36).substring(2, 15);
|
||||||
await import(`${url}#${hash}`);
|
await import(/* @vite-ignore */ `${url}#${hash}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
// UI module to control the options (preferences)
|
import {stored, lock, locked, applyOption} from "./general";
|
||||||
"use strict";
|
|
||||||
|
|
||||||
$("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"});
|
$("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"});
|
||||||
$("#exitCustomization").draggable({handle: "div"});
|
$("#exitCustomization").draggable({handle: "div"});
|
||||||
|
|
@ -170,10 +169,7 @@ function changeMapSize() {
|
||||||
|
|
||||||
const maxWidth = Math.max(+mapWidthInput.value, graphWidth);
|
const maxWidth = Math.max(+mapWidthInput.value, graphWidth);
|
||||||
const maxHeight = Math.max(+mapHeightInput.value, graphHeight);
|
const maxHeight = Math.max(+mapHeightInput.value, graphHeight);
|
||||||
zoom.translateExtent([
|
Zoom.translateExtent([0, 0, maxWidth, maxHeight]);
|
||||||
[0, 0],
|
|
||||||
[maxWidth, maxHeight]
|
|
||||||
]);
|
|
||||||
landmass.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", 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);
|
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);
|
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
|
// just apply canvas size that was already set
|
||||||
function applyMapSize() {
|
export function applyMapSize() {
|
||||||
const zoomMin = +zoomExtentMin.value;
|
const zoomMin = +zoomExtentMin.value;
|
||||||
const zoomMax = +zoomExtentMax.value;
|
const zoomMax = +zoomExtentMax.value;
|
||||||
graphWidth = +mapWidthInput.value;
|
graphWidth = +mapWidthInput.value;
|
||||||
|
|
@ -194,13 +190,10 @@ function applyMapSize() {
|
||||||
svgWidth = Math.min(graphWidth, window.innerWidth);
|
svgWidth = Math.min(graphWidth, window.innerWidth);
|
||||||
svgHeight = Math.min(graphHeight, window.innerHeight);
|
svgHeight = Math.min(graphHeight, window.innerHeight);
|
||||||
svg.attr("width", svgWidth).attr("height", svgHeight);
|
svg.attr("width", svgWidth).attr("height", svgHeight);
|
||||||
zoom
|
|
||||||
.translateExtent([
|
Zoom.translateExtent([0, 0, graphWidth, graphHeight]);
|
||||||
[0, 0],
|
Zoom.scaleExtent([zoomMin, zoomMax]);
|
||||||
[graphWidth, graphHeight]
|
Zoom.scaleTo(svg, zoomMin);
|
||||||
])
|
|
||||||
.scaleExtent([zoomMin, zoomMax])
|
|
||||||
.scaleTo(svg, zoomMin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFullscreen() {
|
function toggleFullscreen() {
|
||||||
|
|
@ -217,17 +210,13 @@ function toggleFullscreen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleTranslateExtent(el) {
|
function toggleTranslateExtent(el) {
|
||||||
const on = (el.dataset.on = +!+el.dataset.on);
|
const on = !Number(el.dataset.on);
|
||||||
if (on)
|
const extent = on
|
||||||
zoom.translateExtent([
|
? [-graphWidth / 2, -graphHeight / 2, graphWidth * 1.5, graphHeight * 1.5]
|
||||||
[-graphWidth / 2, -graphHeight / 2],
|
: [0, 0, graphWidth, graphHeight];
|
||||||
[graphWidth * 1.5, graphHeight * 1.5]
|
Zoom.translateExtent(extent);
|
||||||
]);
|
|
||||||
else
|
el.dataset.on = Number(on);
|
||||||
zoom.translateExtent([
|
|
||||||
[0, 0],
|
|
||||||
[graphWidth, graphHeight]
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add voice options
|
// add voice options
|
||||||
|
|
@ -294,7 +283,8 @@ function restoreSeed(id) {
|
||||||
function restoreDefaultZoomExtent() {
|
function restoreDefaultZoomExtent() {
|
||||||
zoomExtentMin.value = 1;
|
zoomExtentMin.value = 1;
|
||||||
zoomExtentMax.value = 20;
|
zoomExtentMax.value = 20;
|
||||||
zoom.scaleExtent([1, 20]).scaleTo(svg, 1);
|
Zoom.scaleExtent([1, 20]);
|
||||||
|
Zoom.scaleTo(svg, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyMapURL() {
|
function copyMapURL() {
|
||||||
|
|
@ -461,15 +451,16 @@ function changeDialogsTheme(themeColor, transparency) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeZoomExtent(value) {
|
function changeZoomExtent(value) {
|
||||||
const min = Math.max(+zoomExtentMin.value, 0.01);
|
const min = Math.max(+byId("zoomExtentMin").value, 0.01);
|
||||||
const max = Math.min(+zoomExtentMax.value, 200);
|
const max = Math.min(+byId("zoomExtentMax").value, 200);
|
||||||
zoom.scaleExtent([min, max]);
|
Zoom.scaleExtent([min, max]);
|
||||||
|
|
||||||
const scale = minmax(+value, 0.01, 200);
|
const scale = minmax(+value, 0.01, 200);
|
||||||
zoom.scaleTo(svg, scale);
|
Zoom.scaleTo(svg, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore options stored in localStorage
|
// restore options stored in localStorage
|
||||||
function applyStoredOptions() {
|
export function applyStoredOptions() {
|
||||||
if (!stored("mapWidth") || !stored("mapHeight")) {
|
if (!stored("mapWidth") || !stored("mapHeight")) {
|
||||||
mapWidthInput.value = window.innerWidth;
|
mapWidthInput.value = window.innerWidth;
|
||||||
mapHeightInput.value = window.innerHeight;
|
mapHeightInput.value = window.innerHeight;
|
||||||
|
|
@ -530,7 +521,7 @@ function applyStoredOptions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// randomize options if randomization is allowed (not locked or options='default')
|
// 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
|
const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options
|
||||||
|
|
||||||
// 'Options' settings
|
// 'Options' settings
|
||||||
|
|
@ -595,7 +586,7 @@ function randomizeCultureSet() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRendering(value) {
|
function setRendering(value) {
|
||||||
viewbox.attr("shape-rendering", value);
|
fmg.viewbox?.attr("shape-rendering", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate current year and era name
|
// 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 === "saveButton") showSavePane();
|
||||||
else if (id === "exportButton") showExportPane();
|
else if (id === "exportButton") showExportPane();
|
||||||
else if (id === "loadButton") showLoadPane();
|
else if (id === "loadButton") showLoadPane();
|
||||||
else if (id === "zoomReset") resetZoom(1000);
|
else if (id === "zoomReset") Zoom.reset(1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
function regeneratePrompt(options) {
|
function regeneratePrompt(options) {
|
||||||
|
|
@ -975,8 +966,8 @@ function toggle3dOptions() {
|
||||||
|
|
||||||
updateValues();
|
updateValues();
|
||||||
|
|
||||||
if (modules.options3d) return;
|
if (fmg.modules.options3d) return;
|
||||||
modules.options3d = true;
|
fmg.modules.options3d = true;
|
||||||
|
|
||||||
document.getElementById("options3dUpdate").addEventListener("click", ThreeD.update);
|
document.getElementById("options3dUpdate").addEventListener("click", ThreeD.update);
|
||||||
document.getElementById("options3dSave").addEventListener("click", ThreeD.saveScreenshot);
|
document.getElementById("options3dSave").addEventListener("click", ThreeD.saveScreenshot);
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ function editProvinces() {
|
||||||
const body = document.getElementById("provincesBodySection");
|
const body = document.getElementById("provincesBodySection");
|
||||||
refreshProvincesEditor();
|
refreshProvincesEditor();
|
||||||
|
|
||||||
if (modules.editProvinces) return;
|
if (fmg.modules.editProvinces) return;
|
||||||
modules.editProvinces = true;
|
fmg.modules.editProvinces = true;
|
||||||
|
|
||||||
$("#provincesEditor").dialog({
|
$("#provincesEditor").dialog({
|
||||||
title: "Provinces Editor",
|
title: "Provinces Editor",
|
||||||
|
|
@ -123,7 +123,9 @@ function editProvinces() {
|
||||||
const rural = p.rural * populationRate;
|
const rural = p.rural * populationRate;
|
||||||
const urban = p.urban * populationRate * urbanization;
|
const urban = p.urban * populationRate * urbanization;
|
||||||
const population = rn(rural + urban);
|
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;
|
totalPopulation += population;
|
||||||
|
|
||||||
const stateName = pack.states[p.state].name;
|
const stateName = pack.states[p.state].name;
|
||||||
|
|
@ -144,9 +146,15 @@ function editProvinces() {
|
||||||
>
|
>
|
||||||
<fill-box fill="${p.color}"></fill-box>
|
<fill-box fill="${p.color}"></fill-box>
|
||||||
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly />
|
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly />
|
||||||
<svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg>
|
<svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${
|
||||||
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly />
|
p.i
|
||||||
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg ? "" : "placeholder"}"></span>
|
}"></use></svg>
|
||||||
|
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${
|
||||||
|
p.formName
|
||||||
|
}" readonly />
|
||||||
|
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${
|
||||||
|
p.burg ? "" : "placeholder"
|
||||||
|
}"></span>
|
||||||
<select
|
<select
|
||||||
data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital"
|
data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital"
|
||||||
class="cultureBase hide ${p.burgs.length ? "" : "placeholder"}"
|
class="cultureBase hide ${p.burgs.length ? "" : "placeholder"}"
|
||||||
|
|
@ -191,7 +199,9 @@ function editProvinces() {
|
||||||
|
|
||||||
function getCapitalOptions(burgs, capital) {
|
function getCapitalOptions(burgs, capital) {
|
||||||
let options = "";
|
let options = "";
|
||||||
burgs.forEach(b => (options += `<option ${b === capital ? "selected" : ""} value="${b}">${pack.burgs[b].name}</option>`));
|
burgs.forEach(
|
||||||
|
b => (options += `<option ${b === capital ? "selected" : ""} value="${b}">${pack.burgs[b].name}</option>`)
|
||||||
|
);
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,7 +275,11 @@ function editProvinces() {
|
||||||
const {name, burg: burgId, burgs: provinceBurgs} = province;
|
const {name, burg: burgId, burgs: provinceBurgs} = province;
|
||||||
|
|
||||||
if (provinceBurgs.some(b => burgs[b].capital))
|
if (provinceBurgs.some(b => burgs[b].capital))
|
||||||
return tip("Cannot declare independence of a province having capital burg. Please change capital first", false, "error");
|
return tip(
|
||||||
|
"Cannot declare independence of a province having capital burg. Please change capital first",
|
||||||
|
false,
|
||||||
|
"error"
|
||||||
|
);
|
||||||
if (!burgId) return tip("Cannot declare independence of a province without burg", false, "error");
|
if (!burgId) return tip("Cannot declare independence of a province without burg", false, "error");
|
||||||
|
|
||||||
const oldStateId = province.state;
|
const oldStateId = province.state;
|
||||||
|
|
@ -311,7 +325,10 @@ function editProvinces() {
|
||||||
return relations;
|
return relations;
|
||||||
});
|
});
|
||||||
diplomacy.push("x");
|
diplomacy.push("x");
|
||||||
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldStateId].name}`]);
|
states[0].diplomacy.push([
|
||||||
|
`Independance declaration`,
|
||||||
|
`${name} declared its independance from ${states[oldStateId].name}`
|
||||||
|
]);
|
||||||
|
|
||||||
// create new state
|
// create new state
|
||||||
states.push({
|
states.push({
|
||||||
|
|
@ -373,8 +390,12 @@ function editProvinces() {
|
||||||
const l = n => Number(n).toLocaleString();
|
const l = n => Number(n).toLocaleString();
|
||||||
|
|
||||||
alertMessage.innerHTML = /* html */ ` Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
|
alertMessage.innerHTML = /* html */ ` Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
|
||||||
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${p.burgs.length ? "" : "disabled"} />
|
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${
|
||||||
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
p.burgs.length ? "" : "disabled"
|
||||||
|
} />
|
||||||
|
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(
|
||||||
|
total
|
||||||
|
)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
||||||
|
|
||||||
const update = function () {
|
const update = function () {
|
||||||
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
|
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
|
||||||
|
|
@ -493,8 +514,8 @@ function editProvinces() {
|
||||||
position: {my: "center", at: "center", of: "svg"}
|
position: {my: "center", at: "center", of: "svg"}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.editProvinceName) return;
|
if (fmg.modules.editProvinceName) return;
|
||||||
modules.editProvinceName = true;
|
fmg.modules.editProvinceName = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("provinceNameEditorShortCulture").addEventListener("click", regenerateShortNameCuture);
|
document.getElementById("provinceNameEditorShortCulture").addEventListener("click", regenerateShortNameCuture);
|
||||||
|
|
@ -692,7 +713,13 @@ function editProvinces() {
|
||||||
|
|
||||||
function updateChart() {
|
function updateChart() {
|
||||||
const value =
|
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);
|
root.sum(value);
|
||||||
node.data(treeLayout(root).leaves());
|
node.data(treeLayout(root).leaves());
|
||||||
|
|
@ -774,7 +801,13 @@ function editProvinces() {
|
||||||
|
|
||||||
customization = 11;
|
customization = 11;
|
||||||
provs.select("g#provincesBody").append("g").attr("id", "temp");
|
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.querySelectorAll("#provincesBottom > *").forEach(el => (el.style.display = "none"));
|
||||||
document.getElementById("provincesManuallyButtons").style.display = "inline-block";
|
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"}});
|
$("#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);
|
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");
|
body.querySelector("div").classList.add("selected");
|
||||||
selectProvince(+body.querySelector("div").dataset.id);
|
selectProvince(+body.querySelector("div").dataset.id);
|
||||||
|
|
@ -857,7 +894,11 @@ function editProvinces() {
|
||||||
if (i === pack.provinces[provinceOld].center) {
|
if (i === pack.provinces[provinceOld].center) {
|
||||||
const center = centers.select("polygon[data-center='" + i + "']");
|
const center = centers.select("polygon[data-center='" + i + "']");
|
||||||
if (!center.size()) centers.append("polygon").attr("data-center", i).attr("points", getPackPolygon(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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -919,7 +960,8 @@ function editProvinces() {
|
||||||
provincesHeader.querySelector("div[data-sortby='state']").style.left = "22em";
|
provincesHeader.querySelector("div[data-sortby='state']").style.left = "22em";
|
||||||
provincesFooter.style.display = "block";
|
provincesFooter.style.display = "block";
|
||||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all"));
|
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();
|
restoreDefaultEvents();
|
||||||
clearMainTip();
|
clearMainTip();
|
||||||
|
|
@ -941,14 +983,20 @@ function editProvinces() {
|
||||||
const {cells, provinces} = pack;
|
const {cells, provinces} = pack;
|
||||||
const point = d3.mouse(this);
|
const point = d3.mouse(this);
|
||||||
const center = findCell(point[0], point[1]);
|
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];
|
const oldProvince = cells.province[center];
|
||||||
if (oldProvince && provinces[oldProvince].center === center)
|
if (oldProvince && provinces[oldProvince].center === center)
|
||||||
return tip("The cell is already a center of a different province. Select other cell", false, "error");
|
return tip("The cell is already a center of a different province. Select other cell", false, "error");
|
||||||
|
|
||||||
const state = cells.state[center];
|
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();
|
if (d3.event.shiftKey === false) exitAddProvinceMode();
|
||||||
|
|
||||||
|
|
@ -1014,7 +1062,10 @@ function editProvinces() {
|
||||||
|
|
||||||
function downloadProvincesData() {
|
function downloadProvincesData() {
|
||||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
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) {
|
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||||
const key = parseInt(el.dataset.id);
|
const key = parseInt(el.dataset.id);
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ function editRegiment(selector) {
|
||||||
position: {my: "left top", at: "left+10 top+10", of: "#map"}
|
position: {my: "left top", at: "left+10 top+10", of: "#map"}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.editRegiment) return;
|
if (fmg.modules.editRegiment) return;
|
||||||
modules.editRegiment = true;
|
fmg.modules.editRegiment = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
|
document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ function overviewRegiments(state) {
|
||||||
addLines();
|
addLines();
|
||||||
$("#regimentsOverview").dialog();
|
$("#regimentsOverview").dialog();
|
||||||
|
|
||||||
if (modules.overviewRegiments) return;
|
if (fmg.modules.overviewRegiments) return;
|
||||||
modules.overviewRegiments = true;
|
fmg.modules.overviewRegiments = true;
|
||||||
updateHeaders();
|
updateHeaders();
|
||||||
|
|
||||||
$("#regimentsOverview").dialog({
|
$("#regimentsOverview").dialog({
|
||||||
|
|
@ -37,7 +37,9 @@ function overviewRegiments(state) {
|
||||||
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
|
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
|
||||||
for (const u of options.military) {
|
for (const u of options.military) {
|
||||||
const label = capitalize(u.name.replace(/_/g, " "));
|
const label = capitalize(u.name.replace(/_/g, " "));
|
||||||
insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
insert(
|
||||||
|
`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
header.querySelectorAll(".removable").forEach(function (e) {
|
header.querySelectorAll(".removable").forEach(function (e) {
|
||||||
e.addEventListener("click", function () {
|
e.addEventListener("click", function () {
|
||||||
|
|
@ -60,7 +62,9 @@ function overviewRegiments(state) {
|
||||||
for (const r of s.military) {
|
for (const r of s.military) {
|
||||||
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name] || 0}`).join(" ");
|
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name] || 0}`).join(" ");
|
||||||
const lineData = options.military
|
const lineData = options.military
|
||||||
.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`)
|
.map(
|
||||||
|
u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`
|
||||||
|
)
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
lines += /* html */ `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
|
lines += /* html */ `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
|
||||||
|
|
@ -79,7 +83,9 @@ function overviewRegiments(state) {
|
||||||
|
|
||||||
lines += /* html */ `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
|
lines += /* html */ `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
|
||||||
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
|
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
|
||||||
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`).join(" ")}
|
${options.military
|
||||||
|
.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`)
|
||||||
|
.join(" ")}
|
||||||
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
|
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
|
@ -92,7 +98,9 @@ function overviewRegiments(state) {
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => regimentHighlightOn(ev)));
|
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) {
|
function updateFilter(state) {
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ function editReliefIcon() {
|
||||||
close: closeReliefEditor
|
close: closeReliefEditor
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.editReliefIcon) return;
|
if (fmg.modules.editReliefIcon) return;
|
||||||
modules.editReliefIcon = true;
|
fmg.modules.editReliefIcon = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("reliefIndividual").addEventListener("click", enterIndividualMode);
|
document.getElementById("reliefIndividual").addEventListener("click", enterIndividualMode);
|
||||||
|
|
@ -260,7 +260,9 @@ function editReliefIcon() {
|
||||||
const type = reliefIconsDiv.querySelector("svg.pressed")?.dataset.type;
|
const type = reliefIconsDiv.querySelector("svg.pressed")?.dataset.type;
|
||||||
selection = type ? terrain.selectAll("use[href='" + type + "']") : terrain.selectAll("use");
|
selection = type ? terrain.selectAll("use[href='" + type + "']") : terrain.selectAll("use");
|
||||||
const size = selection.size();
|
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({
|
$("#alert").dialog({
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ function createRiver() {
|
||||||
close: closeRiverCreator
|
close: closeRiverCreator
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.createRiver) return;
|
if (fmg.modules.createRiver) return;
|
||||||
modules.createRiver = true;
|
fmg.modules.createRiver = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("riverCreatorComplete").addEventListener("click", addRiver);
|
document.getElementById("riverCreatorComplete").addEventListener("click", addRiver);
|
||||||
|
|
@ -100,12 +100,30 @@ function createRiver() {
|
||||||
const name = getName(mouth);
|
const name = getName(mouth);
|
||||||
const basin = getBasin(parent);
|
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;
|
const id = "river" + riverId;
|
||||||
|
|
||||||
// render river
|
// render river
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
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);
|
editRiver(id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,10 @@ function editRiver(id) {
|
||||||
|
|
||||||
elSelected = d3.select("#" + id).on("click", addControlPoint);
|
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", "controlCells");
|
||||||
debug.append("g").attr("id", "controlPoints");
|
debug.append("g").attr("id", "controlPoints");
|
||||||
|
|
||||||
|
|
@ -29,8 +32,8 @@ function editRiver(id) {
|
||||||
close: closeRiverEditor
|
close: closeRiverEditor
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.editRiver) return;
|
if (fmg.modules.editRiver) return;
|
||||||
modules.editRiver = true;
|
fmg.modules.editRiver = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("riverCreateSelectingCells").addEventListener("click", createRiver);
|
document.getElementById("riverCreateSelectingCells").addEventListener("click", createRiver);
|
||||||
|
|
@ -163,7 +166,7 @@ function editRiver(id) {
|
||||||
elSelected.attr("d", path);
|
elSelected.attr("d", path);
|
||||||
|
|
||||||
updateRiverLength(river);
|
updateRiverLength(river);
|
||||||
if (modules.elevation) showEPForRiver(elSelected.node());
|
if (fmg.modules.elevation) showEPForRiver(elSelected.node());
|
||||||
}
|
}
|
||||||
|
|
||||||
function addControlPoint() {
|
function addControlPoint() {
|
||||||
|
|
@ -227,7 +230,7 @@ function editRiver(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showElevationProfile() {
|
function showElevationProfile() {
|
||||||
modules.elevation = true;
|
fmg.modules.elevation = true;
|
||||||
showEPForRiver(elSelected.node());
|
showEPForRiver(elSelected.node());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ function overviewRivers() {
|
||||||
riversOverviewAddLines();
|
riversOverviewAddLines();
|
||||||
$("#riversOverview").dialog();
|
$("#riversOverview").dialog();
|
||||||
|
|
||||||
if (modules.overviewRivers) return;
|
if (fmg.modules.overviewRivers) return;
|
||||||
modules.overviewRivers = true;
|
fmg.modules.overviewRivers = true;
|
||||||
|
|
||||||
$("#riversOverview").dialog({
|
$("#riversOverview").dialog({
|
||||||
title: "Rivers Overview",
|
title: "Rivers Overview",
|
||||||
|
|
@ -75,7 +75,9 @@ function overviewRivers() {
|
||||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev)));
|
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-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-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);
|
applySorting(riversHeader);
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +112,18 @@ function overviewRivers() {
|
||||||
} else {
|
} else {
|
||||||
rivers.attr("data-basin", "hightlighted");
|
rivers.attr("data-basin", "hightlighted");
|
||||||
const basins = [...new Set(pack.rivers.map(r => r.basin))];
|
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) => {
|
basins.forEach((b, i) => {
|
||||||
const color = colors[i % colors.length];
|
const color = colors[i % colors.length];
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ function editRoute(onClick) {
|
||||||
viewbox.on("touchmove mousemove", showEditorTips);
|
viewbox.on("touchmove mousemove", showEditorTips);
|
||||||
if (onClick) toggleRouteCreationMode();
|
if (onClick) toggleRouteCreationMode();
|
||||||
|
|
||||||
if (modules.editRoute) return;
|
if (fmg.modules.editRoute) return;
|
||||||
modules.editRoute = true;
|
fmg.modules.editRoute = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("routeGroupsShow").addEventListener("click", showGroupSection);
|
document.getElementById("routeGroupsShow").addEventListener("click", showGroupSection);
|
||||||
|
|
@ -97,11 +97,11 @@ function editRoute(onClick) {
|
||||||
const l = elSelected.node().getTotalLength();
|
const l = elSelected.node().getTotalLength();
|
||||||
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||||
|
|
||||||
if (modules.elevation) showEPForRoute(elSelected.node());
|
if (fmg.modules.elevation) showEPForRoute(elSelected.node());
|
||||||
}
|
}
|
||||||
|
|
||||||
function showElevationProfile() {
|
function showElevationProfile() {
|
||||||
modules.elevation = true;
|
fmg.modules.elevation = true;
|
||||||
showEPForRoute(elSelected.node());
|
showEPForRoute(elSelected.node());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,22 @@ function selectStyleElement() {
|
||||||
|
|
||||||
// stroke color and width
|
// stroke color and width
|
||||||
if (
|
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";
|
styleStroke.style.display = "block";
|
||||||
styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke");
|
styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke");
|
||||||
|
|
@ -87,14 +100,29 @@ function selectStyleElement() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stroke dash
|
// 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";
|
styleStrokeDash.style.display = "block";
|
||||||
styleStrokeDasharrayInput.value = el.attr("stroke-dasharray") || "";
|
styleStrokeDasharrayInput.value = el.attr("stroke-dasharray") || "";
|
||||||
styleStrokeLinecapInput.value = el.attr("stroke-linecap") || "inherit";
|
styleStrokeLinecapInput.value = el.attr("stroke-linecap") || "inherit";
|
||||||
}
|
}
|
||||||
|
|
||||||
// clipping
|
// 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";
|
styleClipping.style.display = "block";
|
||||||
styleClippingInput.value = el.attr("mask") || "";
|
styleClippingInput.value = el.attr("mask") || "";
|
||||||
}
|
}
|
||||||
|
|
@ -142,8 +170,12 @@ function selectStyleElement() {
|
||||||
|
|
||||||
if (sel === "population") {
|
if (sel === "population") {
|
||||||
stylePopulation.style.display = "block";
|
stylePopulation.style.display = "block";
|
||||||
stylePopulationRuralStrokeInput.value = stylePopulationRuralStrokeOutput.value = population.select("#rural").attr("stroke");
|
stylePopulationRuralStrokeInput.value = stylePopulationRuralStrokeOutput.value = population
|
||||||
stylePopulationUrbanStrokeInput.value = stylePopulationUrbanStrokeOutput.value = population.select("#urban").attr("stroke");
|
.select("#rural")
|
||||||
|
.attr("stroke");
|
||||||
|
stylePopulationUrbanStrokeInput.value = stylePopulationUrbanStrokeOutput.value = population
|
||||||
|
.select("#urban")
|
||||||
|
.attr("stroke");
|
||||||
styleStrokeWidth.style.display = "block";
|
styleStrokeWidth.style.display = "block";
|
||||||
styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || "";
|
styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || "";
|
||||||
}
|
}
|
||||||
|
|
@ -233,7 +265,8 @@ function selectStyleElement() {
|
||||||
styleOcean.style.display = "block";
|
styleOcean.style.display = "block";
|
||||||
styleOceanFill.value = styleOceanFillOutput.value = oceanLayers.select("#oceanBase").attr("fill");
|
styleOceanFill.value = styleOceanFillOutput.value = oceanLayers.select("#oceanBase").attr("fill");
|
||||||
styleOceanPattern.value = document.getElementById("oceanicPattern")?.getAttribute("href");
|
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");
|
outlineLayers.value = oceanLayers.attr("layers");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -551,7 +584,10 @@ styleFontAdd.addEventListener("click", function () {
|
||||||
|
|
||||||
if (!family) return tip("Please provide a font name", false, "error");
|
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 (existingFont) return tip("The font is already added", false, "error");
|
||||||
|
|
||||||
if (method === "fontURL") addWebFont(family, src);
|
if (method === "fontURL") addWebFont(family, src);
|
||||||
|
|
@ -710,9 +746,9 @@ styleArmiesSize.addEventListener("input", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
emblemsStateSizeInput.addEventListener("change", drawEmblems);
|
emblemsStateSizeInput.addEventListener("change", () => drawEmblems());
|
||||||
emblemsProvinceSizeInput.addEventListener("change", drawEmblems);
|
emblemsProvinceSizeInput.addEventListener("change", () => drawEmblems());
|
||||||
emblemsBurgSizeInput.addEventListener("change", drawEmblems);
|
emblemsBurgSizeInput.addEventListener("change", () => drawEmblems());
|
||||||
|
|
||||||
// request a URL to image to be used as a texture
|
// request a URL to image to be used as a texture
|
||||||
function textureProvideURL() {
|
function textureProvideURL() {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,26 @@
|
||||||
// UI module to control the style presets
|
|
||||||
"use strict";
|
"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_";
|
const customPresetPrefix = "fmgStyle_";
|
||||||
|
|
||||||
// add style presets to list
|
// add style presets to list
|
||||||
{
|
{
|
||||||
const systemOptions = systemPresets.map(styleName => `<option value="${styleName}">${styleName}</option>`);
|
const systemOptions = systemPresets.map(styleName => `<option value="${styleName}">${styleName}</option>`);
|
||||||
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith(customPresetPrefix));
|
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith(customPresetPrefix));
|
||||||
const customOptions = storedStyles.map(styleName => `<option value="${styleName}">${styleName.replace(customPresetPrefix, "")} [custom]</option>`);
|
const customOptions = storedStyles.map(
|
||||||
|
styleName => `<option value="${styleName}">${styleName.replace(customPresetPrefix, "")} [custom]</option>`
|
||||||
|
);
|
||||||
const options = systemOptions.join("") + customOptions.join("");
|
const options = systemOptions.join("") + customOptions.join("");
|
||||||
document.getElementById("stylePreset").innerHTML = options;
|
document.getElementById("stylePreset").innerHTML = options;
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +49,8 @@ async function getStylePreset(desiredPreset) {
|
||||||
const isValid = JSON.isValid(storedStyleJSON);
|
const isValid = JSON.isValid(storedStyleJSON);
|
||||||
if (isValid) return [desiredPreset, JSON.parse(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";
|
presetToLoad = "default";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,8 +139,8 @@ function addStylePreset() {
|
||||||
styleSaverJSON.value = JSON.stringify(collectStyleData(), null, 2);
|
styleSaverJSON.value = JSON.stringify(collectStyleData(), null, 2);
|
||||||
checkName();
|
checkName();
|
||||||
|
|
||||||
if (modules.saveStyle) return;
|
if (fmg.modules.saveStyle) return;
|
||||||
modules.saveStyle = true;
|
fmg.modules.saveStyle = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("styleSaverName").addEventListener("input", checkName);
|
document.getElementById("styleSaverName").addEventListener("input", checkName);
|
||||||
|
|
@ -145,8 +158,31 @@ function addStylePreset() {
|
||||||
"#stateBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
"#stateBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||||
"#provinceBorders": ["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"],
|
"#cells": ["opacity", "stroke", "stroke-width", "filter", "mask"],
|
||||||
"#gridOverlay": ["opacity", "scale", "dx", "dy", "type", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "transform", "filter", "mask"],
|
"#gridOverlay": [
|
||||||
"#coordinates": ["opacity", "data-size", "font-size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
"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"],
|
"#compass": ["opacity", "transform", "filter", "mask", "shape-rendering"],
|
||||||
"#rose": ["transform"],
|
"#rose": ["transform"],
|
||||||
"#relig": ["opacity", "stroke", "stroke-width", "filter"],
|
"#relig": ["opacity", "stroke", "stroke-width", "filter"],
|
||||||
|
|
@ -174,7 +210,17 @@ function addStylePreset() {
|
||||||
"#statesBody": ["opacity", "filter"],
|
"#statesBody": ["opacity", "filter"],
|
||||||
"#statesHalo": ["opacity", "data-width", "stroke-width", "filter"],
|
"#statesHalo": ["opacity", "data-width", "stroke-width", "filter"],
|
||||||
"#provs": ["opacity", "fill", "font-size", "font-family", "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"],
|
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||||
"#emblems": ["opacity", "stroke-width", "filter"],
|
"#emblems": ["opacity", "stroke-width", "filter"],
|
||||||
"#texture": ["opacity", "filter", "mask"],
|
"#texture": ["opacity", "filter", "mask"],
|
||||||
|
|
@ -184,16 +230,65 @@ function addStylePreset() {
|
||||||
"#oceanBase": ["fill"],
|
"#oceanBase": ["fill"],
|
||||||
"#oceanicPattern": ["href", "opacity"],
|
"#oceanicPattern": ["href", "opacity"],
|
||||||
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
|
"#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"],
|
"#legendBox": ["fill", "fill-opacity"],
|
||||||
"#burgLabels > #cities": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
|
"#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"],
|
"#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"],
|
||||||
"#burgLabels > #towns": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
|
"#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"],
|
"#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 > #states": [
|
||||||
"#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
|
"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"]
|
"#fogging": ["opacity", "fill", "filter"]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -238,7 +333,8 @@ function addStylePreset() {
|
||||||
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
|
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 (!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 (!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;
|
const presetName = customPresetPrefix + desiredName;
|
||||||
applyOption(stylePreset, presetName, desiredName + " [custom]");
|
applyOption(stylePreset, presetName, desiredName + " [custom]");
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,14 @@ window.UISubmap = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPreview($container, w, h) {
|
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 canvas = document.createElement("canvas");
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
@ -173,7 +180,11 @@ window.UISubmap = (function () {
|
||||||
const {angle, shiftX, shiftY, ratio, mirrorH, mirrorV} = getTransformInput();
|
const {angle, shiftX, shiftY, ratio, mirrorH, mirrorV} = getTransformInput();
|
||||||
|
|
||||||
const [cx, cy] = [graphWidth / 2, graphHeight / 2];
|
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 shift = (dx, dy) => (x, y) => [x + dx, y + dy];
|
||||||
const scale = r => (x, y) => [(x - cx) * r + cx, (y - cy) * r + cy];
|
const scale = r => (x, y) => [(x - cx) * r + cx, (y - cy) * r + cy];
|
||||||
const flipH = (x, y) => [-x + 2 * cx, y];
|
const flipH = (x, y) => [-x + 2 * cx, y];
|
||||||
|
|
@ -185,7 +196,11 @@ window.UISubmap = (function () {
|
||||||
let inverse = id;
|
let inverse = id;
|
||||||
|
|
||||||
if (angle) [projection, inverse] = [rot(angle), rot(-angle)];
|
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 (mirrorH) [projection, inverse] = [app(flipH, projection), app(inverse, flipH)];
|
||||||
if (mirrorV) [projection, inverse] = [app(flipV, projection), app(inverse, flipV)];
|
if (mirrorV) [projection, inverse] = [app(flipV, projection), app(inverse, flipV)];
|
||||||
if (shiftX || shiftY) {
|
if (shiftX || shiftY) {
|
||||||
|
|
@ -208,6 +223,14 @@ window.UISubmap = (function () {
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 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)
|
// Create submap from the current map. Submap limits defined by the current window size (canvas viewport)
|
||||||
const generateSubmap = debounce(function () {
|
const generateSubmap = debounce(function () {
|
||||||
WARN && console.warn("Resampling current map");
|
WARN && console.warn("Resampling current map");
|
||||||
|
|
@ -244,7 +267,10 @@ window.UISubmap = (function () {
|
||||||
|
|
||||||
// fix scale
|
// fix scale
|
||||||
distanceScaleInput.value = distanceScaleOutput.value = rn((distanceScale = distanceScaleOutput.value / scale), 2);
|
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;
|
customization = 0;
|
||||||
startResample(options);
|
startResample(options);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
@ -253,9 +279,9 @@ window.UISubmap = (function () {
|
||||||
// Do model changes with Submap.resample then do view changes if needed
|
// Do model changes with Submap.resample then do view changes if needed
|
||||||
resetZoom(0);
|
resetZoom(0);
|
||||||
let oldstate = {
|
let oldstate = {
|
||||||
grid: deepCopy(grid),
|
grid: structuredClone(grid),
|
||||||
pack: deepCopy(pack),
|
pack: structuredClone(pack),
|
||||||
notes: deepCopy(notes),
|
notes: structuredClone(notes),
|
||||||
seed,
|
seed,
|
||||||
graphWidth,
|
graphWidth,
|
||||||
graphHeight
|
graphHeight
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ function editUnits() {
|
||||||
closeDialogs("#unitsEditor, .stable");
|
closeDialogs("#unitsEditor, .stable");
|
||||||
$("#unitsEditor").dialog();
|
$("#unitsEditor").dialog();
|
||||||
|
|
||||||
if (modules.editUnits) return;
|
if (fmg.modules.editUnits) return;
|
||||||
modules.editUnits = true;
|
fmg.modules.editUnits = true;
|
||||||
|
|
||||||
$("#unitsEditor").dialog({
|
$("#unitsEditor").dialog({
|
||||||
title: "Units Editor",
|
title: "Units Editor",
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ function editWorld() {
|
||||||
updateGlobeTemperature();
|
updateGlobeTemperature();
|
||||||
updateGlobePosition();
|
updateGlobePosition();
|
||||||
|
|
||||||
if (modules.editWorld) return;
|
if (fmg.modules.editWorld) return;
|
||||||
modules.editWorld = true;
|
fmg.modules.editWorld = true;
|
||||||
|
|
||||||
document.getElementById("worldControls").addEventListener("input", e => updateWorld(e.target));
|
document.getElementById("worldControls").addEventListener("input", e => updateWorld(e.target));
|
||||||
globe.select("#globeWindArrows").on("click", changeWind);
|
globe.select("#globeWindArrows").on("click", changeWind);
|
||||||
|
|
@ -78,11 +78,15 @@ function editWorld() {
|
||||||
const unit = distanceUnitInput.value;
|
const unit = distanceUnitInput.value;
|
||||||
const meridian = toKilometer(eqD * 2 * scale);
|
const meridian = toKilometer(eqD * 2 * scale);
|
||||||
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
|
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("meridianLength").innerHTML = rn(eqD * 2);
|
||||||
document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
|
document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
|
||||||
document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
|
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) {
|
function toKilometer(v) {
|
||||||
if (unit === "km") return v;
|
if (unit === "km") return v;
|
||||||
|
|
@ -110,8 +114,12 @@ function editWorld() {
|
||||||
const tPole = +document.getElementById("temperaturePoleOutput").value;
|
const tPole = +document.getElementById("temperaturePoleOutput").value;
|
||||||
document.getElementById("temperaturePoleF").innerHTML = rn((tPole * 9) / 5 + 32);
|
document.getElementById("temperaturePoleF").innerHTML = rn((tPole * 9) / 5 + 32);
|
||||||
globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin)));
|
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
|
||||||
globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin)));
|
.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)));
|
globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ function editZones() {
|
||||||
updateFilters();
|
updateFilters();
|
||||||
zonesEditorAddLines();
|
zonesEditorAddLines();
|
||||||
|
|
||||||
if (modules.editZones) return;
|
if (fmg.modules.editZones) return;
|
||||||
modules.editZones = true;
|
fmg.modules.editZones = true;
|
||||||
|
|
||||||
$("#zonesEditor").dialog({
|
$("#zonesEditor").dialog({
|
||||||
title: "Zones Editor",
|
title: "Zones Editor",
|
||||||
|
|
@ -61,7 +61,8 @@ function editZones() {
|
||||||
const filterSelect = document.getElementById("zonesFilterType");
|
const filterSelect = document.getElementById("zonesFilterType");
|
||||||
const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
|
const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
|
||||||
|
|
||||||
filterSelect.innerHTML = "<option value='all'>all</option>" + types.map(type => `<option value="${type}">${type}</option>`).join("");
|
filterSelect.innerHTML =
|
||||||
|
"<option value='all'>all</option>" + types.map(type => `<option value="${type}">${type}</option>`).join("");
|
||||||
filterSelect.value = typeToFilterBy;
|
filterSelect.value = typeToFilterBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,9 +81,12 @@ function editZones() {
|
||||||
const fill = zoneEl.getAttribute("fill");
|
const fill = zoneEl.getAttribute("fill");
|
||||||
const area = getArea(d3.sum(c.map(i => pack.cells.area[i])));
|
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 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 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 inactive = zoneEl.style.display === "none";
|
||||||
const focused = defs.select("#fog #focus" + zoneEl.id).size();
|
const focused = defs.select("#fog #focus" + zoneEl.id).size();
|
||||||
|
|
||||||
|
|
@ -98,8 +102,12 @@ function editZones() {
|
||||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||||
<span data-tip="Drag to raise or lower the zone" class="icon-resize-vertical hide"></span>
|
<span data-tip="Drag to raise or lower the zone" class="icon-resize-vertical hide"></span>
|
||||||
<span data-tip="Toggle zone focus" class="icon-pin ${focused ? "" : " inactive"} hide ${c.length ? "" : " placeholder"}"></span>
|
<span data-tip="Toggle zone focus" class="icon-pin ${focused ? "" : " inactive"} hide ${
|
||||||
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive ? " inactive" : ""} hide ${c.length ? "" : " placeholder"}"></span>
|
c.length ? "" : " placeholder"
|
||||||
|
}"></span>
|
||||||
|
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive ? " inactive" : ""} hide ${
|
||||||
|
c.length ? "" : " placeholder"
|
||||||
|
}"></span>
|
||||||
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
|
|
@ -109,7 +117,9 @@ function editZones() {
|
||||||
// update footer
|
// update footer
|
||||||
const totalArea = getArea(graphWidth * graphHeight);
|
const totalArea = getArea(graphWidth * graphHeight);
|
||||||
zonesFooterArea.dataset.area = totalArea;
|
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;
|
zonesFooterPopulation.dataset.population = totalPop;
|
||||||
zonesFooterNumber.innerHTML = /* html */ `${filteredZones.length} of ${zones.length}`;
|
zonesFooterNumber.innerHTML = /* html */ `${filteredZones.length} of ${zones.length}`;
|
||||||
zonesFooterCells.innerHTML = pack.cells.i.length;
|
zonesFooterCells.innerHTML = pack.cells.i.length;
|
||||||
|
|
@ -150,7 +160,13 @@ function editZones() {
|
||||||
zonesEditorAddLines();
|
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) {
|
function movezone(ev, ui) {
|
||||||
const zone = $("#" + ui.item.attr("data-id"));
|
const zone = $("#" + ui.item.attr("data-id"));
|
||||||
const prev = $("#" + ui.item.prev().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"}});
|
$("#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);
|
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");
|
body.querySelector("div").classList.add("selected");
|
||||||
zones.selectAll("g").each(function () {
|
zones.selectAll("g").each(function () {
|
||||||
|
|
@ -285,7 +305,8 @@ function editZones() {
|
||||||
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
|
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
|
||||||
zonesFooter.style.display = "block";
|
zonesFooter.style.display = "block";
|
||||||
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "all"));
|
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();
|
restoreDefaultEvents();
|
||||||
clearMainTip();
|
clearMainTip();
|
||||||
|
|
@ -356,7 +377,8 @@ function editZones() {
|
||||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||||
el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + "%";
|
el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + "%";
|
||||||
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 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 {
|
} else {
|
||||||
body.dataset.type = "absolute";
|
body.dataset.type = "absolute";
|
||||||
|
|
@ -369,7 +391,13 @@ function editZones() {
|
||||||
const description = "Unknown zone";
|
const description = "Unknown zone";
|
||||||
const type = "Unknown";
|
const type = "Unknown";
|
||||||
const fill = "url(#hatch" + (id.slice(4) % 42) + ")";
|
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();
|
zonesEditorAddLines();
|
||||||
}
|
}
|
||||||
|
|
@ -411,13 +439,19 @@ function editZones() {
|
||||||
const burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell));
|
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 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 total = rural + urban;
|
||||||
const l = n => Number(n).toLocaleString();
|
const l = n => Number(n).toLocaleString();
|
||||||
|
|
||||||
alertMessage.innerHTML = /* html */ `Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
|
alertMessage.innerHTML = /* html */ `Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
|
||||||
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"} />
|
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${
|
||||||
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
burgs.length ? "" : "disabled"
|
||||||
|
} />
|
||||||
|
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(
|
||||||
|
total
|
||||||
|
)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
||||||
|
|
||||||
const update = function () {
|
const update = function () {
|
||||||
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
|
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
|
||||||
|
|
|
||||||
52
modules/zoom.js
Normal file
52
modules/zoom.js
Normal file
|
|
@ -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};
|
||||||
|
})();
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^2.9.12",
|
"typescript": "^4.7.4",
|
||||||
"typescript": "^4.7.4"
|
"vite": "^2.9.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
src/config/logging.ts
Normal file
7
src/config/logging.ts
Normal file
|
|
@ -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;
|
||||||
9
src/constants/index.ts
Normal file
9
src/constants/index.ts
Normal file
|
|
@ -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;
|
||||||
|
|
@ -1,26 +1,22 @@
|
||||||
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License
|
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License
|
||||||
// https://github.com/Azgaar/Fantasy-Map-Generator
|
// https://github.com/Azgaar/Fantasy-Map-Generator
|
||||||
|
|
||||||
"use strict";
|
import {PRODUCTION, UINT16_MAX} from "./constants";
|
||||||
// set debug options
|
import {INFO, TIME, WARN, ERROR} from "./config/logging";
|
||||||
const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
|
import {createTypedArray} from "./utils";
|
||||||
const DEBUG = localStorage.getItem("debug");
|
import {shouldRegenerateGrid, generateGrid, calculateVoronoi, getPackPolygon, isLand} from "./utils/graphUtils";
|
||||||
const INFO = DEBUG || !PRODUCTION;
|
import {drawRivers, drawStates, drawBorders} from "../modules/ui/layers";
|
||||||
const TIME = DEBUG || !PRODUCTION;
|
import {invokeActiveZooming} from "../modules/activeZooming";
|
||||||
const WARN = true;
|
import {applyStoredOptions, applyMapSize, randomizeOptions} from "../modules/ui/options";
|
||||||
const ERROR = true;
|
import {locked} from "../modules/ui/general";
|
||||||
|
|
||||||
// detect device
|
globalThis.fmg = {
|
||||||
const MOBILE = window.innerWidth < 600 || navigator.userAgentData?.mobile;
|
modules: {}
|
||||||
|
};
|
||||||
// typed arrays max values
|
|
||||||
const UINT8_MAX = 255;
|
|
||||||
const UINT16_MAX = 65535;
|
|
||||||
const UINT32_MAX = 4294967295;
|
|
||||||
|
|
||||||
if (PRODUCTION && "serviceWorker" in navigator) {
|
if (PRODUCTION && "serviceWorker" in navigator) {
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
navigator.serviceWorker.register("./sw.js").catch(err => {
|
navigator.serviceWorker.register("../sw.js").catch(err => {
|
||||||
console.error("ServiceWorker registration failed: ", err);
|
console.error("ServiceWorker registration failed: ", err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -29,170 +25,47 @@ if (PRODUCTION && "serviceWorker" in navigator) {
|
||||||
"beforeinstallprompt",
|
"beforeinstallprompt",
|
||||||
async event => {
|
async event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const Installation = await import("./modules/dynamic/installation.js");
|
const Installation = await import("../modules/dynamic/installation.js");
|
||||||
Installation.init(event);
|
Installation.init(event);
|
||||||
},
|
},
|
||||||
{once: true}
|
{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
|
// default options
|
||||||
let options = {
|
options = {
|
||||||
pinNotes: false,
|
pinNotes: false,
|
||||||
showMFCGMap: true,
|
showMFCGMap: true,
|
||||||
winds: [225, 45, 225, 315, 135, 315],
|
winds: [225, 45, 225, 315, 135, 315],
|
||||||
stateLabelsMode: "auto"
|
stateLabelsMode: "auto"
|
||||||
};
|
};
|
||||||
let mapCoordinates = {}; // map coordinates on globe
|
mapCoordinates = {}; // map coordinates on globe
|
||||||
let populationRate = +document.getElementById("populationRateInput").value;
|
populationRate = +byId("populationRateInput").value;
|
||||||
let distanceScale = +document.getElementById("distanceScaleInput").value;
|
distanceScale = +byId("distanceScaleInput").value;
|
||||||
let urbanization = +document.getElementById("urbanizationInput").value;
|
urbanization = +byId("urbanizationInput").value;
|
||||||
let urbanDensity = +document.getElementById("urbanDensityInput").value;
|
urbanDensity = +byId("urbanDensityInput").value;
|
||||||
let statesNeutral = 1; // statesEditor growth parameter
|
statesNeutral = 1; // statesEditor growth parameter
|
||||||
|
|
||||||
applyStoredOptions();
|
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
|
// voronoi graph extension, cannot be changed after generation
|
||||||
let graphWidth = +mapWidthInput.value;
|
graphWidth = +byId("mapWidthInput").value;
|
||||||
let graphHeight = +mapHeightInput.value;
|
graphHeight = +byId("mapHeightInput").value;
|
||||||
|
|
||||||
// svg canvas resolution, can be changed
|
// svg canvas resolution, can be changed
|
||||||
let svgWidth = graphWidth;
|
svgWidth = graphWidth;
|
||||||
let svgHeight = graphHeight;
|
svgHeight = graphHeight;
|
||||||
|
|
||||||
landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
defineSvg(graphWidth, 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);
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.on("DOMContentLoaded", async () => {
|
||||||
if (!location.hostname) {
|
if (!location.hostname) {
|
||||||
const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
|
const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
|
||||||
alertMessage.innerHTML = /* html */ `Fantasy Map Generator cannot run serverless. Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can
|
alertMessage.innerHTML = /* html */ `Fantasy Map Generator cannot run serverless. Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can
|
||||||
|
|
@ -321,7 +194,7 @@ function focusOn() {
|
||||||
if (cellParam) {
|
if (cellParam) {
|
||||||
const cell = +params.get("cell");
|
const cell = +params.get("cell");
|
||||||
const [x, y] = pack.cells.p[cell];
|
const [x, y] = pack.cells.p[cell];
|
||||||
zoomTo(x, y, scale, 1600);
|
Zoom.to(x, y, scale, 1600);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,13 +203,13 @@ function focusOn() {
|
||||||
if (!burg) return;
|
if (!burg) return;
|
||||||
|
|
||||||
const {x, y} = burg;
|
const {x, y} = burg;
|
||||||
zoomTo(x, y, scale, 1600);
|
Zoom.to(x, y, scale, 1600);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = +params.get("x") || graphWidth / 2;
|
const x = +params.get("x") || graphWidth / 2;
|
||||||
const y = +params.get("y") || graphHeight / 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();
|
invokeActiveZooming();
|
||||||
tip("Here stands the glorious city of " + b.name, true, "success", 15000);
|
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) {
|
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) {
|
for (let use of g.children) {
|
||||||
const i = +use.dataset.i;
|
const i = +use.dataset.i;
|
||||||
const id = type + "COA" + i;
|
const id = type + "COA" + i;
|
||||||
|
|
@ -1547,7 +1255,9 @@ function addZones(number = 1) {
|
||||||
const invader = ra(atWar);
|
const invader = ra(atWar);
|
||||||
const target = invader.diplomacy.findIndex(d => d === "Enemy");
|
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;
|
if (!cell) return;
|
||||||
|
|
||||||
const cellsArray = [],
|
const cellsArray = [],
|
||||||
|
|
@ -1589,7 +1299,9 @@ function addZones(number = 1) {
|
||||||
|
|
||||||
const neib = ra(state.neighbors.filter(n => n && !states[n].removed));
|
const neib = ra(state.neighbors.filter(n => n && !states[n].removed));
|
||||||
if (!neib) return;
|
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 cellsArray = [];
|
||||||
const queue = [];
|
const queue = [];
|
||||||
if (cell) queue.push(cell);
|
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;
|
const name = getAdjective(states[neib].name) + " " + rebels;
|
||||||
zonesData.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"});
|
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"));
|
const organized = ra(pack.religions.filter(r => r.type === "Organized"));
|
||||||
if (!organized) return;
|
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;
|
if (!cell) return;
|
||||||
const target = cells.religion[cell];
|
const target = cells.religion[cell];
|
||||||
const cellsArray = [],
|
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 adjective = () =>
|
||||||
const animal = () => ra(["Ape", "Bear", "Boar", "Cat", "Cow", "Dog", "Pig", "Fox", "Bird", "Horse", "Rat", "Raven", "Sheep", "Spider", "Wolf"]);
|
ra(["Great", "Silent", "Severe", "Blind", "Unknown", "Loud", "Deadly", "Burning", "Bloody", "Brutal", "Fatal"]);
|
||||||
const color = () => ra(["Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]);
|
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;
|
const name = rw({[color()]: 4, [animal()]: 2, [adjective()]: 1}) + " " + type;
|
||||||
zonesData.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
|
zonesData.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
|
||||||
}
|
}
|
||||||
|
|
@ -1812,7 +1584,9 @@ function addZones(number = 1) {
|
||||||
meanFlux = d3.mean(fl),
|
meanFlux = d3.mean(fl),
|
||||||
maxFlux = d3.max(fl),
|
maxFlux = d3.max(fl),
|
||||||
flux = (maxFlux - meanFlux) / 2 + meanFlux;
|
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;
|
if (!rivers.length) return;
|
||||||
|
|
||||||
const cell = +ra(rivers),
|
const cell = +ra(rivers),
|
||||||
|
|
@ -1923,7 +1697,7 @@ const regenerateMap = debounce(async function (options) {
|
||||||
|
|
||||||
closeDialogs("#worldConfigurator, #options3d");
|
closeDialogs("#worldConfigurator, #options3d");
|
||||||
customization = 0;
|
customization = 0;
|
||||||
resetZoom(1000);
|
Zoom.reset(1000);
|
||||||
undraw();
|
undraw();
|
||||||
await generate(options);
|
await generate(options);
|
||||||
restoreLayers();
|
restoreLayers();
|
||||||
7
src/types/global.d.ts
vendored
Normal file
7
src/types/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
interface Navigator {
|
||||||
|
userAgentData?: {
|
||||||
|
mobile: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnknownObject = {[key: string]: unknown};
|
||||||
33
src/utils/arrayUtils.ts
Normal file
33
src/utils/arrayUtils.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {UINT16_MAX, UINT32_MAX, UINT8_MAX} from "../constants";
|
||||||
|
|
||||||
|
export function last<T>(array: T[]) {
|
||||||
|
return array[array.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unique<T>(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<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTypedArray({maxValue, length, from}: ICreateTypedArray) {
|
||||||
|
const typedArray = getTypedArray(maxValue);
|
||||||
|
if (!from) return new typedArray(length);
|
||||||
|
return typedArray.from(from);
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
"use strict";
|
import {TIME} from "../config/logging";
|
||||||
// FMG utils related to graph
|
import {createTypedArray} from ".";
|
||||||
|
|
||||||
// check if new grid graph should be generated or we can use the existing one
|
// 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;
|
const cellsDesired = +byId("pointsInput").dataset.cells;
|
||||||
if (cellsDesired !== grid.cellsDesired) return true;
|
if (cellsDesired !== grid.cellsDesired) return true;
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ function shouldRegenerateGrid(grid) {
|
||||||
return grid.spacing !== newSpacing || grid.cellsX !== newCellsX || grid.cellsY !== newCellsY;
|
return grid.spacing !== newSpacing || grid.cellsX !== newCellsX || grid.cellsY !== newCellsY;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateGrid() {
|
export function generateGrid() {
|
||||||
Math.random = aleaPRNG(seed); // reset PRNG
|
Math.random = aleaPRNG(seed); // reset PRNG
|
||||||
const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints();
|
const {spacing, cellsDesired, boundary, points, cellsX, cellsY} = placePoints();
|
||||||
const {cells, vertices} = calculateVoronoi(points, boundary);
|
const {cells, vertices} = calculateVoronoi(points, boundary);
|
||||||
|
|
@ -36,7 +36,7 @@ function placePoints() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate Delaunay and then Voronoi diagram
|
// calculate Delaunay and then Voronoi diagram
|
||||||
function calculateVoronoi(points, boundary) {
|
export function calculateVoronoi(points, boundary) {
|
||||||
TIME && console.time("calculateDelaunay");
|
TIME && console.time("calculateDelaunay");
|
||||||
const allPoints = points.concat(boundary);
|
const allPoints = points.concat(boundary);
|
||||||
const delaunay = Delaunator.from(allPoints);
|
const delaunay = Delaunator.from(allPoints);
|
||||||
|
|
@ -95,8 +95,11 @@ function getJitteredGrid(width, height, spacing) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// return cell index on a regular square grid
|
// return cell index on a regular square grid
|
||||||
function findGridCell(x, y, grid) {
|
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 (
|
||||||
|
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
|
// 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 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
|
// 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);
|
const found = pack.cells.q.findAll(x, y, radius);
|
||||||
return found.map(r => r[2]);
|
return found.map(r => r[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get polygon points for packed cells knowing cell id
|
// 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 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
|
// get polygon points for initial cells knowing cell id
|
||||||
function getGridPolygon(i) {
|
function getGridPolygon(i) {
|
||||||
return grid.cells.v[i].map(v => grid.vertices.p[v]);
|
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
|
// filter land cells
|
||||||
function isLand(i) {
|
export function isLand(i) {
|
||||||
return pack.cells.h[i] >= 20;
|
return pack.cells.h[i] >= 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter water cells
|
// filter water cells
|
||||||
function isWater(i) {
|
export function isWater(i) {
|
||||||
return pack.cells.h[i] < 20;
|
return pack.cells.h[i] < 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,7 +249,14 @@ void (function addFindAll() {
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
// Stop searching if this quadrant can’t contain a closer node.
|
// 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.
|
// Bisect the current quadrant.
|
||||||
if (t.node.length) {
|
if (t.node.length) {
|
||||||
1
src/utils/index.ts
Normal file
1
src/utils/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export {last, unique, createTypedArray} from "./arrayUtils";
|
||||||
|
|
@ -16,5 +16,5 @@
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src", "utils", "modules"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,54 +7,3 @@ function last(array) {
|
||||||
function unique(array) {
|
function unique(array) {
|
||||||
return [...new Set(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);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
3
vite.config.js
Normal file
3
vite.config.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import {defineConfig} from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue