From 80da2f0cda35c6af125adde95ba574b4aebf9f3a Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 29 Sep 2024 15:09:29 +0200 Subject: [PATCH 01/26] fix: gap path to not omit the M path sign --- index.html | 2 +- utils/pathUtils.js | 3 +-- versioning.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 1d9727ba..e2be2515 100644 --- a/index.html +++ b/index.html @@ -8039,7 +8039,7 @@ - + diff --git a/utils/pathUtils.js b/utils/pathUtils.js index 4f023595..a81720a4 100644 --- a/utils/pathUtils.js +++ b/utils/pathUtils.js @@ -78,11 +78,10 @@ function getBorderPath(vertices, vertexChain, discontinue) { } const operation = discontinued ? "M" : "L"; - const command = operation === lastOperation ? "" : operation; - discontinued = false; lastOperation = operation; + const command = operation === "L" && operation === lastOperation ? "" : operation; return ` ${command}${vertices.p[vertexId]}`; }); diff --git a/versioning.js b/versioning.js index 5a416d6d..3b86e7d1 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.7"; +const VERSION = "1.105.8"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 2d0030e3d46436253e9bf77024cf54aaf0e2841e Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 1 Oct 2024 21:20:29 +0200 Subject: [PATCH 02/26] feat: allow to crean data in case of load error --- index.html | 4 ++-- main.js | 2 +- modules/io/load.js | 3 +++ versioning.js | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index e2be2515..a556d5c7 100644 --- a/index.html +++ b/index.html @@ -8072,7 +8072,7 @@ - + @@ -8118,7 +8118,7 @@ - + diff --git a/main.js b/main.js index 7afe4cf6..48afa810 100644 --- a/main.js +++ b/main.js @@ -691,7 +691,7 @@ async function generate(options) { title: "Generation error", width: "32em", buttons: { - "Cleanup data": cleanupData, + "Clear cache": () => cleanupData(), Regenerate: function () { regenerateMap("generation error"); $(this).dialog("close"); diff --git a/modules/io/load.js b/modules/io/load.js index a3f39913..9d3592f9 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -96,6 +96,7 @@ function showUploadErrorMessage(error, URL, random) { title: "Loading error", width: "32em", buttons: { + "Clear cache": () => cleanupData(), OK: function () { $(this).dialog("close"); } @@ -195,6 +196,7 @@ function showUploadMessage(type, mapData, mapVersion) { $("#alert").dialog({ title, buttons: { + "Clear cache": () => cleanupData(), OK: function () { $(this).dialog("close"); } @@ -735,6 +737,7 @@ async function parseLoadedData(data, mapVersion) { title: "Loading error", maxWidth: "50em", buttons: { + "Clear cache": () => cleanupData(), "Select file": function () { $(this).dialog("close"); mapToLoad.click(); diff --git a/versioning.js b/versioning.js index 3b86e7d1..a4eb0a72 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.8"; +const VERSION = "1.105.9"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { @@ -58,7 +58,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o width: "28em", position: {my: "center center-4em", at: "center", of: "svg"}, buttons: { - "Cleanup data": () => cleanupData(), + "Clear cache": () => cleanupData(), "Don't show again": function () { $(this).dialog("close"); localStorage.setItem("version", VERSION); From 56597d961d58ed421995fc54a33474f0dcc82686 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 3 Oct 2024 13:05:10 +0200 Subject: [PATCH 03/26] fix: remove unwanted states styling --- index.html | 2 +- modules/dynamic/auto-update.js | 8 ++++++++ modules/io/load.js | 2 +- versioning.js | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index a556d5c7..c88f1274 100644 --- a/index.html +++ b/index.html @@ -8118,7 +8118,7 @@ - + diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index e14e594f..50e6ed92 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -964,5 +964,13 @@ export function resolveVersionConflicts(mapVersion) { defs.select("#water").selectAll("path, use").remove(); viewbox.select("#coastline").selectAll("path, use").remove(); drawFeatures(); + + // v1.104.0 introduced bugs with state borders + regions + .attr("opacity", null) + .attr("stroke-width", null) + .attr("letter-spacing", null) + .attr("fill", null) + .attr("stroke", null); } } diff --git a/modules/io/load.js b/modules/io/load.js index 9d3592f9..bc9781fe 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -461,7 +461,7 @@ async function parseLoadedData(data, mapVersion) { { // dynamically import and run auto-update script - const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.105.5"); + const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.105.10"); resolveVersionConflicts(mapVersion); } diff --git a/versioning.js b/versioning.js index a4eb0a72..04c9faf8 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.9"; +const VERSION = "1.105.10"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From c795ac6c3079e16f8da9d663a9f7abf083e768d6 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 9 Oct 2024 01:08:47 +0200 Subject: [PATCH 04/26] fix: allow to load smaller namesbase without issues on regeneration --- index.html | 9 +++--- modules/dynamic/editors/cultures-editor.js | 1 + modules/names-generator.js | 36 ++++++++++++---------- modules/ui/editors.js | 2 +- modules/ui/namesbase-editor.js | 6 ++-- versioning.js | 2 +- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/index.html b/index.html index c88f1274..c1ef6c90 100644 --- a/index.html +++ b/index.html @@ -4770,7 +4770,7 @@ Names data: -
+
@@ -8051,7 +8052,7 @@ - + @@ -8076,13 +8077,13 @@ - + - + diff --git a/modules/dynamic/editors/cultures-editor.js b/modules/dynamic/editors/cultures-editor.js index c6143dc2..b8e43608 100644 --- a/modules/dynamic/editors/cultures-editor.js +++ b/modules/dynamic/editors/cultures-editor.js @@ -266,6 +266,7 @@ function getTypeOptions(type) { function getBaseOptions(base) { let options = ""; nameBases.forEach((n, i) => (options += ``)); + if (!nameBases[base]) options += ``; // in case namesbase was removed return options; } diff --git a/modules/names-generator.js b/modules/names-generator.js index 4e243f3c..c35afedc 100644 --- a/modules/names-generator.js +++ b/modules/names-generator.js @@ -48,18 +48,28 @@ window.Names = (function () { return chain; }; - // update chain for specific base - const updateChain = i => (chains[i] = nameBases[i] || nameBases[i].b ? calculateChain(nameBases[i].b) : null); + const updateChain = i => { + chains[i] = nameBases[i]?.b ? calculateChain(nameBases[i].b) : null; + }; - // update chains for all used bases - const clearChains = () => (chains = []); + const clearChains = () => { + chains = []; + }; // generate name using Markov's chain const getBase = function (base, min, max, dupl) { - if (base === undefined) { - ERROR && console.error("Please define a base"); - return; + if (base === undefined) return ERROR && console.error("Please define a base"); + + if (nameBases[base] === undefined) { + if (nameBases[0]) { + WARN && console.warn("Namebase " + base + " is not found. First available namebase will be used"); + base = 0; + } else { + ERROR && console.error("Namebase " + base + " is not found"); + return "ERROR"; + } } + if (!chains[base]) updateChain(base); const data = chains[base]; @@ -141,16 +151,8 @@ window.Names = (function () { // generate short name for base const getBaseShort = function (base) { - if (nameBases[base] === undefined) { - tip( - `Namebase ${base} does not exist. Please upload custom namebases of change the base in Cultures Editor`, - false, - "error" - ); - base = 1; - } - const min = nameBases[base].min - 1; - const max = Math.max(nameBases[base].max - 2, min); + const min = nameBases[base] ? nameBases[base].min - 1 : null; + const max = min ? Math.max(nameBases[base].max - 2, min) : null; return getBase(base, min, max, "", 0); }; diff --git a/modules/ui/editors.js b/modules/ui/editors.js index dd742aed..13380155 100644 --- a/modules/ui/editors.js +++ b/modules/ui/editors.js @@ -1255,7 +1255,7 @@ async function editStates() { async function editCultures() { if (customization) return; - const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.104.0"); + const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.105.11"); Editor.open(); } diff --git a/modules/ui/namesbase-editor.js b/modules/ui/namesbase-editor.js index edf472eb..ae3fc730 100644 --- a/modules/ui/namesbase-editor.js +++ b/modules/ui/namesbase-editor.js @@ -41,7 +41,7 @@ function editNamesbase() { $("#namesbaseEditor").dialog({ title: "Namesbase Editor", - width: "auto", + width: "60vw", position: {my: "center", at: "center", of: "svg"} }); @@ -66,7 +66,7 @@ function editNamesbase() { function updateExamples() { const base = +document.getElementById("namesbaseSelect").value; let examples = ""; - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 7; i++) { const example = Names.getBase(base); if (example === undefined) { examples = "Cannot generate examples. Please verify the data"; @@ -250,7 +250,7 @@ function editNamesbase() { const [rawName, min, max, d, m, rawNames] = base.split("|"); const name = rawName.replace(unsafe, ""); const names = rawNames.replace(unsafe, ""); - nameBases.push({name, min, max, d, m, b: names}); + nameBases.push({name, min: +min, max: +max, d, m: +m, b: names}); }); createBasesList(); diff --git a/versioning.js b/versioning.js index 04c9faf8..44eaf676 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.10"; +const VERSION = "1.105.11"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 26b659a59ec72d7930d6e886a84805c8bb6faace Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 11 Oct 2024 12:17:33 +0200 Subject: [PATCH 05/26] fix #1152 --- index.html | 10 +++++----- modules/cultures-generator.js | 5 ++++- modules/ui/options.js | 8 ++++---- modules/ui/tools.js | 8 ++++---- versioning.js | 2 +- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/index.html b/index.html index c1ef6c90..eb3c2ef5 100644 --- a/index.html +++ b/index.html @@ -1631,10 +1631,10 @@ Cultures number - + - + @@ -8053,7 +8053,7 @@ - + @@ -8072,13 +8072,13 @@ - + - + diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js index 65c4f6ba..3096f96c 100644 --- a/modules/cultures-generator.js +++ b/modules/cultures-generator.js @@ -8,7 +8,10 @@ window.Cultures = (function () { cells = pack.cells; const cultureIds = new Uint16Array(cells.i.length); // cell cultures - let count = Math.min(+culturesInput.value, +culturesSet.selectedOptions[0].dataset.max); + + const culturesInputNumber = +byId("culturesInput").value; + const culturesInSetNumber = +byId("culturesSet").selectedOptions[0].dataset.max; + let count = Math.min(culturesInputNumber, culturesInSetNumber); const populated = cells.i.filter(i => cells.s[i]); // populated cells if (populated.length < count * 25) { diff --git a/modules/ui/options.js b/modules/ui/options.js index 1ac4c8ad..88bf53db 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -558,10 +558,10 @@ function applyStoredOptions() { if (key.slice(0, 5) === "style") applyOption(stylePreset, key, key.slice(5)); } - if (stored("winds")) options.winds = localStorage.getItem("winds").split(",").map(Number); - if (stored("temperatureEquator")) options.temperatureEquator = +localStorage.getItem("temperatureEquator"); - if (stored("temperatureNorthPole")) options.temperatureNorthPole = +localStorage.getItem("temperatureNorthPole"); - if (stored("temperatureSouthPole")) options.temperatureSouthPole = +localStorage.getItem("temperatureSouthPole"); + if (stored("winds")) options.winds = stored("winds").split(",").map(Number); + if (stored("temperatureEquator")) options.temperatureEquator = +stored("temperatureEquator"); + if (stored("temperatureNorthPole")) options.temperatureNorthPole = +stored("temperatureNorthPole"); + if (stored("temperatureSouthPole")) options.temperatureSouthPole = +stored("temperatureSouthPole"); if (stored("military")) options.military = JSON.parse(stored("military")); if (stored("tooltipSize")) changeTooltipSize(stored("tooltipSize")); diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 2ab57530..9d26e9dd 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -513,8 +513,8 @@ function regenerateEmblems() { function regenerateReligions() { Religions.generate(); - if (layerIsOn("toggleReligions")) drawReligions(); - else toggleReligions(); + + layerIsOn("toggleReligions") ? drawReligions() : toggleReligions(); refreshAllEditors(); } @@ -523,8 +523,8 @@ function regenerateCultures() { Cultures.expand(); BurgsAndStates.updateCultures(); Religions.updateCultures(); - if (!layerIsOn("toggleCultures")) toggleCultures(); - else drawCultures(); + + layerIsOn("toggleCultures") ? drawCultures() : toggleCultures(); refreshAllEditors(); } diff --git a/versioning.js b/versioning.js index 44eaf676..ade2a04c 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.11"; +const VERSION = "1.105.12"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From f0ff23a119fcb216155ebd277cd5bc21ef0117e6 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 13 Oct 2024 15:08:40 +0200 Subject: [PATCH 06/26] fix: #1152, don't keep removed locked cultures on regenerate --- modules/cultures-generator.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js index 3096f96c..f2203146 100644 --- a/modules/cultures-generator.js +++ b/modules/cultures-generator.js @@ -123,26 +123,26 @@ window.Cultures = (function () { cultures.forEach(c => (c.base = c.base % nameBases.length)); function selectCultures(culturesNumber) { - let def = getDefault(culturesNumber); + let defaultCultures = getDefault(culturesNumber); const cultures = []; pack.cultures?.forEach(function (culture) { - if (culture.lock) cultures.push(culture); + if (culture.lock && !culture.removed) cultures.push(culture); }); if (!cultures.length) { - if (culturesNumber === def.length) return def; - if (def.every(d => d.odd === 1)) return def.splice(0, culturesNumber); + if (culturesNumber === defaultCultures.length) return defaultCultures; + if (defaultCultures.every(d => d.odd === 1)) return defaultCultures.splice(0, culturesNumber); } - for (let culture, rnd, i = 0; cultures.length < culturesNumber && def.length > 0; ) { + for (let culture, rnd, i = 0; cultures.length < culturesNumber && defaultCultures.length > 0; ) { do { - rnd = rand(def.length - 1); - culture = def[rnd]; + rnd = rand(defaultCultures.length - 1); + culture = defaultCultures[rnd]; i++; } while (i < 200 && !P(culture.odd)); cultures.push(culture); - def.splice(rnd, 1); + defaultCultures.splice(rnd, 1); } return cultures; } From 6c37c7babf7b1ded5ddfee65749b681da27e7ca6 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 13 Oct 2024 15:10:31 +0200 Subject: [PATCH 07/26] fix: #1152, don't keep removed locked cultures on regenerate --- index.html | 2 +- versioning.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index eb3c2ef5..2671ad1d 100644 --- a/index.html +++ b/index.html @@ -8053,7 +8053,7 @@ - + diff --git a/versioning.js b/versioning.js index ade2a04c..a4cfb23c 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.12"; +const VERSION = "1.105.13"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From c447afb829c6d0e35a6fc0aedaff74c691237577 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 13 Oct 2024 20:32:37 +0200 Subject: [PATCH 08/26] fix: features rendering - close the ring --- index.html | 4 ++-- modules/renderers/draw-features.js | 2 +- modules/ui/lakes-editor.js | 10 +++++----- versioning.js | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index 2671ad1d..a54ede5b 100644 --- a/index.html +++ b/index.html @@ -8090,7 +8090,7 @@ - + @@ -8123,7 +8123,7 @@ - + diff --git a/modules/renderers/draw-features.js b/modules/renderers/draw-features.js index 0221a1c1..ac2a395e 100644 --- a/modules/renderers/draw-features.js +++ b/modules/renderers/draw-features.js @@ -55,7 +55,7 @@ function getFeaturePath(feature) { const clippedPoints = clipPoly(simplifiedPoints, 1); const lineGen = d3.line().curve(d3.curveBasisClosed); - const path = round(lineGen(clippedPoints)); + const path = round(lineGen(clippedPoints)) + "Z"; return path; } diff --git a/modules/ui/lakes-editor.js b/modules/ui/lakes-editor.js index a6ce80d1..55f3fb5b 100644 --- a/modules/ui/lakes-editor.js +++ b/modules/ui/lakes-editor.js @@ -15,7 +15,7 @@ function editLake() { debug.append("g").attr("id", "vertices"); elSelected = d3.select(node); updateLakeValues(); - selectLakeGroup(node); + selectLakeGroup(); drawLakeVertices(); viewbox.on("touchmove mousemove", null); @@ -140,13 +140,13 @@ function editLake() { lake.name = lakeName.value = Names.getBase(rand(nameBases.length - 1)); } - function selectLakeGroup(node) { - const group = node.parentNode.id; + function selectLakeGroup() { + const lake = getLake(); + const select = byId("lakeGroup"); select.options.length = 0; // remove all options - lakes.selectAll("g").each(function () { - select.options.add(new Option(this.id, this.id, false, this.id === group)); + select.options.add(new Option(this.id, this.id, false, this.id === lake.group)); }); } diff --git a/versioning.js b/versioning.js index a4cfb23c..ef1bf42f 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.13"; +const VERSION = "1.105.14"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From efbe0373b04d29c6d40109982fc72783b468ef9d Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 16 Oct 2024 15:20:44 +0200 Subject: [PATCH 09/26] fix: lock all burgs --- index.html | 2 +- modules/ui/burgs-overview.js | 2 +- versioning.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index a54ede5b..96a8c1aa 100644 --- a/index.html +++ b/index.html @@ -8102,7 +8102,7 @@ - + diff --git a/modules/ui/burgs-overview.js b/modules/ui/burgs-overview.js index 1170b5f1..2a487b67 100644 --- a/modules/ui/burgs-overview.js +++ b/modules/ui/burgs-overview.js @@ -608,7 +608,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) { const activeBurgs = pack.burgs.filter(b => b.i && !b.removed); const allLocked = activeBurgs.every(burg => burg.lock); - pack.burgs.forEach(burg => { + activeBurgs.forEach(burg => { burg.lock = !allLocked; }); diff --git a/versioning.js b/versioning.js index ef1bf42f..1aa1326c 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.14"; +const VERSION = "1.105.15"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 87e1dc2c5de5f7dcb1e510f32a43d46681747586 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 19 Oct 2024 13:25:34 +0200 Subject: [PATCH 10/26] Draw state labels improvement (#1155) * chore: render debug elements * feat: redo draw state labels algo --------- Co-authored-by: Azgaar --- index.html | 6 +- main.js | 2 +- modules/renderers/draw-state-labels.js | 258 +++++++++++++------------ modules/ui/labels-editor.js | 2 +- utils/debugUtils.js | 14 ++ versioning.js | 2 +- 6 files changed, 156 insertions(+), 128 deletions(-) diff --git a/index.html b/index.html index 96a8c1aa..1e2c4356 100644 --- a/index.html +++ b/index.html @@ -8073,7 +8073,7 @@ - + @@ -8092,7 +8092,7 @@ - + @@ -8131,7 +8131,7 @@ - + diff --git a/main.js b/main.js index 48afa810..7beab61f 100644 --- a/main.js +++ b/main.js @@ -106,10 +106,10 @@ coastline.append("g").attr("id", "lake_island"); terrs.append("g").attr("id", "oceanHeights"); terrs.append("g").attr("id", "landHeights"); +let burgLabels = labels.append("g").attr("id", "burgLabels"); 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"); diff --git a/modules/renderers/draw-state-labels.js b/modules/renderers/draw-state-labels.js index 15bb7ea5..ab49437d 100644 --- a/modules/renderers/draw-state-labels.js +++ b/modules/renderers/draw-state-labels.js @@ -14,11 +14,11 @@ function drawStateLabels(list) { // increase step to 15 or 30 to make it faster and more horyzontal // decrease step to 5 to improve accuracy const ANGLE_STEP = 9; - const raycast = precalculateAngles(ANGLE_STEP); + const angles = precalculateAngles(ANGLE_STEP); - const INITIAL_DISTANCE = 10; - const DISTANCE_STEP = 15; - const MAX_ITERATIONS = 100; + const LENGTH_START = 5; + const LENGTH_STEP = 5; + const LENGTH_MAX = 300; const labelPaths = getLabelPaths(); const letterLength = checkExampleLetterLength(); @@ -35,87 +35,25 @@ function drawStateLabels(list) { if (list && !list.includes(state.i)) continue; const offset = getOffsetWidth(state.cells); - const maxLakeSize = state.cells / 50; + const maxLakeSize = state.cells / 20; const [x0, y0] = state.pole; - const offsetPoints = new Map( - (offset ? raycast : []).map(({angle, x: x1, y: y1}) => { - const [x, y] = [x0 + offset * x1, y0 + offset * y1]; - return [angle, {x, y}]; - }) - ); - - const distances = raycast.map(({angle, x: dx, y: dy, modifier}) => { - let distanceMin; - const distance1 = getMaxDistance(state.i, {x: x0, y: y0}, dx, dy, maxLakeSize); - - if (offset) { - const point2 = offsetPoints.get(angle - 90 < 0 ? angle + 270 : angle - 90); - const distance2 = getMaxDistance(state.i, point2, dx, dy, maxLakeSize); - - const point3 = offsetPoints.get(angle + 90 >= 360 ? angle - 270 : angle + 90); - const distance3 = getMaxDistance(state.i, point3, dx, dy, maxLakeSize); - - distanceMin = Math.min(distance1, distance2, distance3); - } else { - distanceMin = distance1; - } - - const [x, y] = [x0 + distanceMin * dx, y0 + distanceMin * dy]; - return {angle, distance: distanceMin * modifier, x, y}; + const rays = angles.map(({angle, dx, dy}) => { + const {length, x, y} = raycast({stateId: state.i, x0, y0, dx, dy, maxLakeSize, offset}); + return {angle, length, x, y}; }); + const [ray1, ray2] = findBestRayPair(rays); - const { - angle, - x: x1, - y: y1 - } = distances.reduce( - (acc, {angle, distance, x, y}) => { - if (distance > acc.distance) return {angle, distance, x, y}; - return acc; - }, - {angle: 0, distance: 0, x: 0, y: 0} - ); + const pathPoints = [[ray1.x, ray1.y], state.pole, [ray2.x, ray2.y]]; + if (ray1.x > ray2.x) pathPoints.reverse(); - const oppositeAngle = angle >= 180 ? angle - 180 : angle + 180; - const {x: x2, y: y2} = distances.reduce( - (acc, {angle, distance, x, y}) => { - const angleDif = getAnglesDif(angle, oppositeAngle); - const score = distance * getAngleModifier(angleDif); - if (score > acc.score) return {angle, score, x, y}; - return acc; - }, - {angle: 0, score: 0, x: 0, y: 0} - ); + DEBUG && drawPoint(state.pole, {color: "black", radius: 1}); + DEBUG && drawPath(pathPoints, {color: "black", width: 0.2}); - const pathPoints = [[x1, y1], state.pole, [x2, y2]]; - if (x1 > x2) pathPoints.reverse(); labelPaths.push([state.i, pathPoints]); } return labelPaths; - - function getMaxDistance(stateId, point, dx, dy, maxLakeSize) { - let distance = INITIAL_DISTANCE; - - for (let i = 0; i < MAX_ITERATIONS; i++) { - const [x, y] = [point.x + distance * dx, point.y + distance * dy]; - const cellId = findCell(x, y, DISTANCE_STEP); - - // drawPoint([x, y], {color: cellId && isPassable(cellId) ? "blue" : "red", radius: 0.8}); - - if (!cellId || !isPassable(cellId)) break; - distance += DISTANCE_STEP; - } - - return distance; - - function isPassable(cellId) { - const feature = features[cells.f[cellId]]; - if (feature.type === "lake") return feature.cells <= maxLakeSize; - return stateIds[cellId] === stateId; - } - } } function checkExampleLetterLength() { @@ -129,7 +67,7 @@ function drawStateLabels(list) { function drawLabelPath(letterLength) { const mode = options.stateLabelsMode || "auto"; - const lineGen = d3.line().curve(d3.curveBundle.beta(1)); + const lineGen = d3.line().curve(d3.curveNatural); const textGroup = d3.select("g#labels > g#states"); const pathGroup = d3.select("defs > g#deftemp > g#textPaths"); @@ -192,35 +130,15 @@ function drawStateLabels(list) { const text = pathLength > state.fullName.length * 1.8 ? state.fullName : state.name; textElement.innerHTML = `${text}`; - const correctedRatio = minmax(rn((pathLength / text.length) * 50), 40, 130); + const correctedRatio = minmax(rn((pathLength / text.length) * 50), 50, 130); textElement.setAttribute("font-size", correctedRatio + "%"); } } - // point offset to reduce label overlap with state borders function getOffsetWidth(cellsNumber) { - if (cellsNumber < 80) return 0; - if (cellsNumber < 140) return 5; - if (cellsNumber < 200) return 15; - if (cellsNumber < 300) return 20; - if (cellsNumber < 500) return 25; - return 30; - } - - // difference between two angles in range [0, 180] - function getAnglesDif(angle1, angle2) { - return 180 - Math.abs(Math.abs(angle1 - angle2) - 180); - } - - // score multiplier based on angle difference betwee left and right sides - function getAngleModifier(angleDif) { - if (angleDif === 0) return 1; - if (angleDif <= 15) return 0.95; - if (angleDif <= 30) return 0.9; - if (angleDif <= 45) return 0.6; - if (angleDif <= 60) return 0.3; - if (angleDif <= 90) return 0.1; - return 0; // >90 + if (cellsNumber < 40) return 0; + if (cellsNumber < 200) return 5; + return 10; } function precalculateAngles(step) { @@ -228,37 +146,133 @@ function drawStateLabels(list) { const RAD = Math.PI / 180; for (let angle = 0; angle < 360; angle += step) { - const x = Math.cos(angle * RAD); - const y = Math.sin(angle * RAD); - const angleDif = 90 - Math.abs((angle % 180) - 90); - const modifier = 1 - angleDif / 120; // [0.25, 1] - angles.push({angle, modifier, x, y}); + const dx = Math.cos(angle * RAD); + const dy = Math.sin(angle * RAD); + angles.push({angle, dx, dy}); } return angles; } + function raycast({stateId, x0, y0, dx, dy, maxLakeSize, offset}) { + let ray = {length: 0, x: x0, y: y0}; + + for (let length = LENGTH_START; length < LENGTH_MAX; length += LENGTH_STEP) { + const [x, y] = [x0 + length * dx, y0 + length * dy]; + // offset points are perpendicular to the ray + const offset1 = [x + -dy * offset, y + dx * offset]; + const offset2 = [x + dy * offset, y + -dx * offset]; + + DEBUG && drawPoint([x, y], {color: isInsideState(x, y) ? "blue" : "red", radius: 0.8}); + DEBUG && drawPoint(offset1, {color: isInsideState(...offset1) ? "blue" : "red", radius: 0.4}); + DEBUG && drawPoint(offset2, {color: isInsideState(...offset2) ? "blue" : "red", radius: 0.4}); + + const inState = isInsideState(x, y) && isInsideState(...offset1) && isInsideState(...offset2); + if (!inState) break; + ray = {length, x, y}; + } + + return ray; + + function isInsideState(x, y) { + if (x < 0 || x > graphWidth || y < 0 || y > graphHeight) return false; + const cellId = findCell(x, y); + + const feature = features[cells.f[cellId]]; + if (feature.type === "lake") return isInnerLake(feature) || isSmallLake(feature); + + return stateIds[cellId] === stateId; + } + + function isInnerLake(feature) { + return feature.shoreline.every(cellId => stateIds[cellId] === stateId); + } + + function isSmallLake(feature) { + return feature.cells <= maxLakeSize; + } + } + + function findBestRayPair(rays) { + let bestPair = null; + let bestScore = -Infinity; + + for (let i = 0; i < rays.length; i++) { + const score1 = rays[i].length * scoreRayAngle(rays[i].angle); + + for (let j = i + 1; j < rays.length; j++) { + const score2 = rays[j].length * scoreRayAngle(rays[j].angle); + const pairScore = (score1 + score2) * scoreCurvature(rays[i].angle, rays[j].angle); + + if (pairScore > bestScore) { + bestScore = pairScore; + bestPair = [rays[i], rays[j]]; + } + } + } + + return bestPair; + } + + function scoreRayAngle(angle) { + const normalizedAngle = Math.abs(angle % 180); // [0, 180] + const horizontality = Math.abs(normalizedAngle - 90) / 90; // [0, 1] + + if (horizontality === 1) return 1; // Best: horizontal + if (horizontality >= 0.75) return 0.9; // Very good: slightly slanted + if (horizontality >= 0.5) return 0.6; // Good: moderate slant + if (horizontality >= 0.25) return 0.5; // Acceptable: more slanted + if (horizontality >= 0.15) return 0.2; // Poor: almost vertical + return 0.1; // Very poor: almost vertical + } + + function scoreCurvature(angle1, angle2) { + const delta = getAngleDelta(angle1, angle2); + const similarity = evaluateArc(angle1, angle2); + + if (delta === 180) return 1; // straight line: best + if (delta < 90) return 0; // acute: not allowed + if (delta < 120) return 0.6 * similarity; + if (delta < 140) return 0.7 * similarity; + if (delta < 160) return 0.8 * similarity; + + return similarity; + } + + function getAngleDelta(angle1, angle2) { + let delta = Math.abs(angle1 - angle2) % 360; + if (delta > 180) delta = 360 - delta; // [0, 180] + return delta; + } + + // compute arc similarity towards x-axis + function evaluateArc(angle1, angle2) { + const proximity1 = Math.abs((angle1 % 180) - 90); + const proximity2 = Math.abs((angle2 % 180) - 90); + return 1 - Math.abs(proximity1 - proximity2) / 90; + } + function getLinesAndRatio(mode, name, fullName, pathLength) { - // short name - if (mode === "short" || (mode === "auto" && pathLength <= name.length)) { - const lines = splitInTwo(name); + if (mode === "short") return getShortOneLine(); + if (pathLength > fullName.length * 2) return getFullOneLine(); + return getFullTwoLines(); + + function getShortOneLine() { + const ratio = pathLength / name.length; + return [[name], minmax(rn(ratio * 60), 50, 150)]; + } + + function getFullOneLine() { + const ratio = pathLength / fullName.length; + return [[fullName], minmax(rn(ratio * 70), 70, 170)]; + } + + function getFullTwoLines() { + const lines = splitInTwo(fullName); const longestLineLength = d3.max(lines.map(({length}) => length)); const ratio = pathLength / longestLineLength; - return [lines, minmax(rn(ratio * 60), 50, 150)]; + return [lines, minmax(rn(ratio * 60), 70, 150)]; } - - // full name: one line - if (pathLength > fullName.length * 2) { - const lines = [fullName]; - const ratio = pathLength / lines[0].length; - return [lines, minmax(rn(ratio * 70), 70, 170)]; - } - - // full name: two lines - const lines = splitInTwo(fullName); - const longestLineLength = d3.max(lines.map(({length}) => length)); - const ratio = pathLength / longestLineLength; - return [lines, minmax(rn(ratio * 60), 70, 150)]; } // check whether multi-lined label is mostly inside the state. If no, replace it with short name label diff --git a/modules/ui/labels-editor.js b/modules/ui/labels-editor.js index 809be210..8c47ec99 100644 --- a/modules/ui/labels-editor.js +++ b/modules/ui/labels-editor.js @@ -122,7 +122,7 @@ function editLabel() { function redrawLabelPath() { const path = byId("textPath_" + elSelected.attr("id")); - lineGen.curve(d3.curveBundle.beta(1)); + lineGen.curve(d3.curveNatural); const points = []; debug .select("#controlPoints") diff --git a/utils/debugUtils.js b/utils/debugUtils.js index 6fd46860..1859f3ae 100644 --- a/utils/debugUtils.js +++ b/utils/debugUtils.js @@ -56,3 +56,17 @@ function drawRouteConnections() { } } } + +function drawPoint([x, y], {color = "red", radius = 0.5}) { + debug.append("circle").attr("cx", x).attr("cy", y).attr("r", radius).attr("fill", color); +} + +function drawPath(points, {color = "red", width = 0.5}) { + const lineGen = d3.line().curve(d3.curveBundle); + debug + .append("path") + .attr("d", round(lineGen(points))) + .attr("stroke", color) + .attr("stroke-width", width) + .attr("fill", "none"); +} diff --git a/versioning.js b/versioning.js index 1aa1326c..025c183a 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.15"; +const VERSION = "1.105.16"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 8a4f28b32105cb7790c52dd69f26df130a7cf51e Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 19 Oct 2024 13:32:59 +0200 Subject: [PATCH 11/26] fix: CRLF issue --- index.html | 2 +- modules/io/load.js | 14 ++++++++++++-- versioning.js | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 1e2c4356..790a4d54 100644 --- a/index.html +++ b/index.html @@ -8119,7 +8119,7 @@ - + diff --git a/modules/io/load.js b/modules/io/load.js index bc9781fe..cd00b28c 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -153,11 +153,21 @@ async function uncompress(compressedData) { async function parseLoadedResult(result) { try { const resultAsString = new TextDecoder().decode(result); + // data can be in FMG internal format or base64 encoded const isDelimited = resultAsString.substring(0, 10).includes("|"); - const decoded = isDelimited ? resultAsString : decodeURIComponent(atob(resultAsString)); + let content = isDelimited ? resultAsString : decodeURIComponent(atob(resultAsString)); - const mapData = decoded.split("\r\n"); // split by CRLF + // fix if svg part has CRLF line endings instead of LF + const svgMatch = content.match(/]*id="map"[\s\S]*?<\/svg>/); + const svgContent = svgMatch[0]; + const hasCrlfEndings = svgContent.includes("\r\n"); + if (hasCrlfEndings) { + const correctedSvgContent = svgContent.replace(/\r\n/g, "\n"); + content = content.replace(svgContent, correctedSvgContent); + } + + const mapData = content.split("\r\n"); // split by CRLF const mapVersion = parseMapVersion(mapData[0].split("|")[0] || mapData[0] || ""); return {mapData, mapVersion}; diff --git a/versioning.js b/versioning.js index 025c183a..24c2f84a 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.16"; +const VERSION = "1.105.17"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 6d69eb855fd11ba3899409b319806512b4177aab Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 19 Oct 2024 14:02:04 +0200 Subject: [PATCH 12/26] fix: zones editor - legend to be toggable --- index.html | 2 +- modules/ui/zones-editor.js | 2 ++ versioning.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 790a4d54..d14c9381 100644 --- a/index.html +++ b/index.html @@ -8101,7 +8101,7 @@ - + diff --git a/modules/ui/zones-editor.js b/modules/ui/zones-editor.js index 0b5d069a..d575a544 100644 --- a/modules/ui/zones-editor.js +++ b/modules/ui/zones-editor.js @@ -341,6 +341,8 @@ function editZones() { } function toggleLegend() { + if (legend.selectAll("*").size()) return clearLegend(); // hide legend + const filterBy = byId("zonesFilterType").value; const isFiltered = filterBy && filterBy !== "all"; const visibleZones = pack.zones.filter(zone => !zone.hidden && (!isFiltered || zone.type === filterBy)); diff --git a/versioning.js b/versioning.js index 24c2f84a..acb67e2b 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.17"; +const VERSION = "1.105.18"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 5ac99d180de7a2348fc44b0bbc9647c8794f39ba Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 22 Oct 2024 14:45:25 +0200 Subject: [PATCH 13/26] chore: parse DEBUG setting as an object --- index.html | 6 +++--- main.js | 4 ++-- modules/io/cloud.js | 6 +++--- modules/io/load.js | 2 +- modules/renderers/draw-state-labels.js | 14 +++++++++----- modules/submap.js | 3 +-- utils/stringUtils.js | 8 ++++++++ versioning.js | 2 +- 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/index.html b/index.html index d14c9381..8f2705fb 100644 --- a/index.html +++ b/index.html @@ -8073,7 +8073,7 @@ - + @@ -8120,7 +8120,7 @@ - + @@ -8131,7 +8131,7 @@ - + diff --git a/main.js b/main.js index 7beab61f..c4279296 100644 --- a/main.js +++ b/main.js @@ -4,7 +4,7 @@ // set debug options const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1"; -const DEBUG = localStorage.getItem("debug"); +const DEBUG = JSON.safeParse(localStorage.getItem("debug")) || {}; const INFO = true; const TIME = true; const WARN = true; @@ -922,7 +922,7 @@ function calculateTemperatures() { const [, y] = grid.points[rowCellId]; const rowLatitude = mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT; // [90; -90] const tempSeaLevel = calculateSeaLevelTemp(rowLatitude); - DEBUG && console.info(`${rn(rowLatitude)}° sea temperature: ${rn(tempSeaLevel)}°C`); + DEBUG.temperature && console.info(`${rn(rowLatitude)}° sea temperature: ${rn(tempSeaLevel)}°C`); for (let cellId = rowCellId; cellId < rowCellId + grid.cellsX; cellId++) { const tempAltitudeDrop = getAltitudeTemperatureDrop(cells.h[cellId]); diff --git a/modules/io/cloud.js b/modules/io/cloud.js index b25b6a90..17ca92db 100644 --- a/modules/io/cloud.js +++ b/modules/io/cloud.js @@ -60,7 +60,7 @@ window.Cloud = (function () { async save(fileName, contents) { const resp = await this.call("filesUpload", {path: "/" + fileName, contents}); - DEBUG && console.info("Dropbox response:", resp); + DEBUG.cloud && console.info("Dropbox response:", resp); return true; }, @@ -104,7 +104,7 @@ window.Cloud = (function () { // Callback function for auth window async setDropBoxToken(token) { - DEBUG && console.info("Access token:", token); + DEBUG.cloud && console.info("Access token:", token); setToken(this.name, token); await this.connect(token); this.authWindow.close(); @@ -131,7 +131,7 @@ window.Cloud = (function () { allow_download: true }; const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings}); - DEBUG && console.info("Dropbox link object:", resp.result); + DEBUG.cloud && console.info("Dropbox link object:", resp.result); return resp.result.url; } }; diff --git a/modules/io/load.js b/modules/io/load.js index cd00b28c..05819ce7 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -13,7 +13,7 @@ async function quickLoad() { async function loadFromDropbox() { const mapPath = byId("loadFromDropboxSelect")?.value; - DEBUG && console.info("Loading map from Dropbox:", mapPath); + console.info("Loading map from Dropbox:", mapPath); const blob = await Cloud.providers.dropbox.load(mapPath); uploadMap(blob); } diff --git a/modules/renderers/draw-state-labels.js b/modules/renderers/draw-state-labels.js index ab49437d..d30d185c 100644 --- a/modules/renderers/draw-state-labels.js +++ b/modules/renderers/draw-state-labels.js @@ -47,8 +47,10 @@ function drawStateLabels(list) { const pathPoints = [[ray1.x, ray1.y], state.pole, [ray2.x, ray2.y]]; if (ray1.x > ray2.x) pathPoints.reverse(); - DEBUG && drawPoint(state.pole, {color: "black", radius: 1}); - DEBUG && drawPath(pathPoints, {color: "black", width: 0.2}); + if (DEBUG.stateLabels) { + drawPoint(state.pole, {color: "black", radius: 1}); + drawPath(pathPoints, {color: "black", width: 0.2}); + } labelPaths.push([state.i, pathPoints]); } @@ -163,9 +165,11 @@ function drawStateLabels(list) { const offset1 = [x + -dy * offset, y + dx * offset]; const offset2 = [x + dy * offset, y + -dx * offset]; - DEBUG && drawPoint([x, y], {color: isInsideState(x, y) ? "blue" : "red", radius: 0.8}); - DEBUG && drawPoint(offset1, {color: isInsideState(...offset1) ? "blue" : "red", radius: 0.4}); - DEBUG && drawPoint(offset2, {color: isInsideState(...offset2) ? "blue" : "red", radius: 0.4}); + if (DEBUG.stateLabels) { + drawPoint([x, y], {color: isInsideState(x, y) ? "blue" : "red", radius: 0.8}); + drawPoint(offset1, {color: isInsideState(...offset1) ? "blue" : "red", radius: 0.4}); + drawPoint(offset2, {color: isInsideState(...offset2) ? "blue" : "red", radius: 0.4}); + } const inState = isInsideState(x, y) && isInsideState(...offset1) && isInsideState(...offset2); if (!inState) break; diff --git a/modules/submap.js b/modules/submap.js index 2fb4582f..3c783fb0 100644 --- a/modules/submap.js +++ b/modules/submap.js @@ -31,7 +31,6 @@ window.Submap = (function () { seed = parentMap.seed; Math.random = aleaPRNG(seed); INFO && console.group("SubMap with seed: " + seed); - DEBUG && console.info("Using Options:", options); applyGraphSize(); grid = generateGrid(); @@ -373,7 +372,7 @@ window.Submap = (function () { b.removed = true; return; } - DEBUG && console.info(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`); + [b.x, b.y] = b.port ? getCloseToEdgePoint(newCell, neighbor) : cells.p[newCell]; if (b.port) b.port = cells.f[neighbor]; // copy feature number b.cell = newCell; diff --git a/utils/stringUtils.js b/utils/stringUtils.js index 6325d278..1027ee8f 100644 --- a/utils/stringUtils.js +++ b/utils/stringUtils.js @@ -56,3 +56,11 @@ JSON.isValid = str => { } return true; }; + +JSON.safeParse = str => { + try { + return JSON.parse(str); + } catch (e) { + return null; + } +}; diff --git a/versioning.js b/versioning.js index acb67e2b..076de37c 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.18"; +const VERSION = "1.105.19"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 2ec2c9f77368554bbe53b9198974c783e4f0c81d Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 22 Oct 2024 15:10:33 +0200 Subject: [PATCH 14/26] chore: update 1.105.19 hash --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 8f2705fb..41afe3b6 100644 --- a/index.html +++ b/index.html @@ -8037,7 +8037,7 @@ - + From 54491cfd099bcd3d094b658cce1945aa51114769 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 22 Oct 2024 23:04:57 +0200 Subject: [PATCH 15/26] feat: zones editor - don't close other editors on open --- index.html | 2 +- modules/ui/zones-editor.js | 2 +- versioning.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 41afe3b6..96b9374e 100644 --- a/index.html +++ b/index.html @@ -8101,7 +8101,7 @@ - + diff --git a/modules/ui/zones-editor.js b/modules/ui/zones-editor.js index d575a544..85888c4e 100644 --- a/modules/ui/zones-editor.js +++ b/modules/ui/zones-editor.js @@ -1,7 +1,7 @@ "use strict"; function editZones() { - closeDialogs(); + closeDialogs("#zonesEditor, .stable"); if (!layerIsOn("toggleZones")) toggleZones(); const body = byId("zonesBodySection"); diff --git a/versioning.js b/versioning.js index 076de37c..fcd5504b 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,7 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.19"; +const VERSION = "1.105.20"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From d7f5cae2298cef6748f90d92d6a2161b39e62b74 Mon Sep 17 00:00:00 2001 From: "Ryan D. Guild" Date: Sat, 26 Oct 2024 08:26:59 -0400 Subject: [PATCH 16/26] Removed priority queue in favor of FlatQueue (#1157) * removed priority queue in favor of simple array extension as it will be easier to migrate to esm * patch: bump version * spacing * moved references to globalThis * demonstrate module interop * added version to priority-queue and moved to utils to follow dom loading pattern * removed PriorityQueue in favor of FlatQueue * update index.html * never mind that force push I don't know how to amend commits right * missing capitalization * priority set to 0 on 541 --------- Co-authored-by: RyanGuild --- index.html | 11 +++++------ libs/priority-queue.min.js | 1 - modules/burgs-and-states.js | 10 ++++++---- modules/cultures-generator.js | 8 ++++---- modules/provinces-generator.js | 22 +++++++++++----------- modules/religions-generator.js | 8 ++++---- modules/zones-generator.js | 16 ++++++++-------- versioning.js | 3 ++- 8 files changed, 40 insertions(+), 39 deletions(-) delete mode 100644 libs/priority-queue.min.js diff --git a/index.html b/index.html index 96b9374e..3b2d7203 100644 --- a/index.html +++ b/index.html @@ -8022,7 +8022,6 @@ - @@ -8053,14 +8052,14 @@ - - - + + + - + - + diff --git a/libs/priority-queue.min.js b/libs/priority-queue.min.js deleted file mode 100644 index acf2506b..00000000 --- a/libs/priority-queue.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).PriorityQueue=t()}}(function(){return function t(e,i,r){function o(n,s){if(!i[n]){if(!e[n]){var h="function"==typeof require&&require;if(!s&&h)return h(n,!0);if(a)return a(n,!0);var u=new Error("Cannot find module '"+n+"'");throw u.code="MODULE_NOT_FOUND",u}var p=i[n]={exports:{}};e[n][0].call(p.exports,function(t){var i=e[n][1][t];return o(i||t)},p,p.exports,t,e,i,r)}return i[n].exports}for(var a="function"==typeof require&&require,n=0;n>>1],e)>=0?o=a+1:r=a;return o},e.exports=function(){function t(t){var e;this.options=t,this.comparator=this.options.comparator,this.data=(null!=(e=this.options.initialValues)?e.slice(0):void 0)||[],this.data.sort(this.comparator).reverse()}return t.prototype.queue=function(t){var e;e=r(this.data,t,this.comparator),this.data.splice(e,0,t)},t.prototype.dequeue=function(){return this.data.pop()},t.prototype.peek=function(){return this.data[this.data.length-1]},t.prototype.clear=function(){this.data.length=0},t}()},{}],4:[function(t,e,i){e.exports=function(){function t(t){var e,i,r,o,a,n,s,h;for(this.comparator=(null!=t?t.comparator:void 0)||function(t,e){return t-e},this.pageSize=(null!=t?t.pageSize:void 0)||512,this.length=0,s=0;1<a;0<=a?++i:--i)e.push(null);if(this._memory=[],this._mask=this.pageSize-1,t.initialValues)for(r=0,o=(n=t.initialValues).length;r0&&(this._write(1,e),this._bubbleDown(1,e)),t},t.prototype.peek=function(){return this._read(1)},t.prototype.clear=function(){this.length=0,this._memory.length=0},t.prototype._write=function(t,e){var i;for(i=t>>this._shift;i>=this._memory.length;)this._memory.push(this._emptyMemoryPageTemplate.slice(0));return this._memory[i][t&this._mask]=e},t.prototype._read=function(t){return this._memory[t>>this._shift][t&this._mask]},t.prototype._bubbleUp=function(t,e){var i,r,o,a;for(i=this.comparator;t>1&&(r=t&this._mask,t3?o=t&~this._mask|r>>1:r<2?(o=t-this.pageSize>>this._shift,o+=o&~(this._mask>>1),o|=this.pageSize>>1):o=t-2,!(i(a=this._read(o),e)<0));)this._write(o,e),this._write(t,a),t=o},t.prototype._bubbleDown=function(t,e){var i,r,o,a,n;for(n=this.comparator;tthis._mask&&!(t&this._mask-1)?i=r=t+2:t&this.pageSize>>1?(i=(t&~this._mask)>>1,r=(i=(i|=t&this._mask>>1)+1<0)for(t=e=1,i=this.data.length;1<=i?ei;t=1<=i?++e:--e)this._bubbleUp(t)},t.prototype.queue=function(t){this.data.push(t),this._bubbleUp(this.data.length-1)},t.prototype.dequeue=function(){var t,e;return e=this.data[0],t=this.data.pop(),this.data.length>0&&(this.data[0]=t,this._bubbleDown(0)),e},t.prototype.peek=function(){return this.data[0]},t.prototype.clear=function(){this.length=0,this.data.length=0},t.prototype._bubbleUp=function(t){for(var e,i;t>0&&(e=t-1>>>1,this.comparator(this.data[t],this.data[e])<0);)i=this.data[e],this.data[e]=this.data[t],this.data[t]=i,t=e},t.prototype._bubbleDown=function(t){var e,i,r,o,a;for(e=this.data.length-1;o=(i=1+(t<<1))+1,r=t,i<=e&&this.comparator(this.data[i],this.data[r])<0&&(r=i),o<=e&&this.comparator(this.data[o],this.data[r])<0&&(r=o),r!==t;)a=this.data[r],this.data[r]=this.data[t],this.data[t]=a,t=r},t}()},{}]},{},[1])(1)}); \ No newline at end of file diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index 90ab2bf2..4b73276b 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -286,7 +286,8 @@ window.BurgsAndStates = (() => { const {cells, states, cultures, burgs} = pack; cells.state = cells.state || new Uint16Array(cells.i.length); - const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); + + const queue = new FlatQueue(); const cost = []; const globalGrowthRate = byId("growthRate").valueAsNumber || 1; @@ -307,12 +308,13 @@ window.BurgsAndStates = (() => { cells.state[capitalCell] = state.i; const cultureCenter = cultures[state.culture].center; const b = cells.biome[cultureCenter]; // state native biome - queue.queue({e: state.center, p: 0, s: state.i, b}); + queue.push({e: state.center, p: 0, s: state.i, b}, 0); cost[state.center] = 1; } while (queue.length) { - const next = queue.dequeue(); + const next = queue.pop(); + const {e, p, s, b} = next; const {type, culture} = states[s]; @@ -335,7 +337,7 @@ window.BurgsAndStates = (() => { if (!cost[e] || totalCost < cost[e]) { if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell cost[e] = totalCost; - queue.queue({e, p: totalCost, s, b}); + queue.push({e, p: totalCost, s, b}, totalCost); } }); } diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js index f2203146..34dc5edd 100644 --- a/modules/cultures-generator.js +++ b/modules/cultures-generator.js @@ -518,7 +518,7 @@ window.Cultures = (function () { TIME && console.time("expandCultures"); const {cells, cultures} = pack; - const queue = new PriorityQueue({comparator: (a, b) => a.priority - b.priority}); + const queue = new FlatQueue(); const cost = []; const neutralRate = byId("neutralRate")?.valueAsNumber || 1; @@ -538,11 +538,11 @@ window.Cultures = (function () { for (const culture of cultures) { if (!culture.i || culture.removed || culture.lock) continue; - queue.queue({cellId: culture.center, cultureId: culture.i, priority: 0}); + queue.push({cellId: culture.center, cultureId: culture.i, priority: 0}, 0); } while (queue.length) { - const {cellId, priority, cultureId} = queue.dequeue(); + const {cellId, priority, cultureId} = queue.pop(); const {type, expansionism} = cultures[cultureId]; cells.c[cellId].forEach(neibCellId => { @@ -566,7 +566,7 @@ window.Cultures = (function () { if (!cost[neibCellId] || totalCost < cost[neibCellId]) { if (cells.pop[neibCellId] > 0) cells.culture[neibCellId] = cultureId; // assign culture to populated cell cost[neibCellId] = totalCost; - queue.queue({cellId: neibCellId, cultureId, priority: totalCost}); + queue.push({cellId: neibCellId, cultureId, priority: totalCost}, totalCost); } }); } diff --git a/modules/provinces-generator.js b/modules/provinces-generator.js index 8563f682..c0f8e8f7 100644 --- a/modules/provinces-generator.js +++ b/modules/provinces-generator.js @@ -77,18 +77,18 @@ window.Provinces = (function () { }); // expand generated provinces - const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); + const queue = new FlatQueue(); const cost = []; provinces.forEach(p => { if (!p.i || p.removed || isProvinceLocked(p)) return; provinceIds[p.center] = p.i; - queue.queue({e: p.center, p: 0, province: p.i, state: p.state}); + queue.push({e: p.center, province: p.i, state: p.state, p: 0}, 0); cost[p.center] = 1; }); while (queue.length) { - const {e, p, province, state} = queue.dequeue(); + const {e, p, province, state} = queue.pop(); cells.c[e].forEach(e => { if (isProvinceCellLocked(e)) return; // do not overwrite cell of locked provinces @@ -103,7 +103,7 @@ window.Provinces = (function () { if (!cost[e] || totalCost < cost[e]) { if (land) provinceIds[e] = province; // assign province to a cell cost[e] = totalCost; - queue.queue({e, p: totalCost, province, state}); + queue.push({e, province, state, p: totalCost}, totalCost); } }); } @@ -158,9 +158,9 @@ window.Provinces = (function () { // expand province const cost = []; cost[center] = 1; - queue.queue({e: center, p: 0}); + queue.push({e: center, p: 0}, 0); while (queue.length) { - const {e, p} = queue.dequeue(); + const {e, p} = queue.pop(); cells.c[e].forEach(nextCellId => { if (provinceIds[nextCellId]) return; @@ -173,7 +173,7 @@ window.Provinces = (function () { if (!cost[nextCellId] || totalCost < cost[nextCellId]) { if (land && cells.state[nextCellId] === s.i) provinceIds[nextCellId] = provinceId; // assign province to a cell cost[nextCellId] = totalCost; - queue.queue({e: nextCellId, p: totalCost}); + queue.push({e: nextCellId, p: totalCost}, totalCost); } }); } @@ -216,15 +216,15 @@ window.Provinces = (function () { // check if there is a land way within the same state between two cells function isPassable(from, to) { if (cells.f[from] !== cells.f[to]) return false; // on different islands - const queue = [from], + const passableQueue = [from], used = new Uint8Array(cells.i.length), state = cells.state[from]; - while (queue.length) { - const current = queue.pop(); + while (passableQueue.length) { + const current = passableQueue.pop(); if (current === to) return true; // way is found cells.c[current].forEach(c => { if (used[c] || cells.h[c] < 20 || cells.state[c] !== state) return; - queue.push(c); + passableQueue.push(c); used[c] = 1; }); } diff --git a/modules/religions-generator.js b/modules/religions-generator.js index 5e5e08f6..2bb972db 100644 --- a/modules/religions-generator.js +++ b/modules/religions-generator.js @@ -695,7 +695,7 @@ window.Religions = (function () { const {cells, routes} = pack; const religionIds = spreadFolkReligions(religions); - const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); + const queue = new FlatQueue(); const cost = []; // limit cost for organized religions growth @@ -705,14 +705,14 @@ window.Religions = (function () { .filter(r => r.i && !r.lock && r.type !== "Folk" && !r.removed) .forEach(r => { religionIds[r.center] = r.i; - queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center]}); + queue.push({e: r.center, p: 0, r: r.i, s: cells.state[r.center]}, 0); cost[r.center] = 1; }); const religionsMap = new Map(religions.map(r => [r.i, r])); while (queue.length) { - const {e: cellId, p, r, s: state} = queue.dequeue(); + const {e: cellId, p, r, s: state} = queue.pop(); const {culture, expansion, expansionism} = religionsMap.get(r); cells.c[cellId].forEach(nextCell => { @@ -732,7 +732,7 @@ window.Religions = (function () { if (cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell cost[nextCell] = totalCost; - queue.queue({e: nextCell, p: totalCost, r, s: state}); + queue.push({e: nextCell, p: totalCost, r, s: state}, totalCost); } }); } diff --git a/modules/zones-generator.js b/modules/zones-generator.js index 7e8ec94b..0fdab688 100644 --- a/modules/zones-generator.js +++ b/modules/zones-generator.js @@ -209,11 +209,11 @@ window.Zones = (function () { const cost = []; const maxCells = rand(20, 40); - const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); - queue.queue({e: burg.cell, p: 0}); + const queue = new FlatQueue(); + queue.push({e: burg.cell, p: 0}, 0); while (queue.length) { - const next = queue.dequeue(); + const next = queue.pop(); if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e); usedCells[next.e] = 1; @@ -224,7 +224,7 @@ window.Zones = (function () { if (!cost[nextCellId] || p < cost[nextCellId]) { cost[nextCellId] = p; - queue.queue({e: nextCellId, p}); + queue.push({e: nextCellId, p}, p); } }); } @@ -251,11 +251,11 @@ window.Zones = (function () { const cost = []; const maxCells = rand(5, 25); - const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); - queue.queue({e: burg.cell, p: 0}); + const queue = new FlatQueue(); + queue.push({e: burg.cell, p: 0}, 0); while (queue.length) { - const next = queue.dequeue(); + const next = queue.pop(); if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e); usedCells[next.e] = 1; @@ -266,7 +266,7 @@ window.Zones = (function () { if (!cost[e] || p < cost[e]) { cost[e] = p; - queue.queue({e, p}); + queue.push({e, p}), p; } }); } diff --git a/versioning.js b/versioning.js index fcd5504b..c5bf2fa4 100644 --- a/versioning.js +++ b/versioning.js @@ -12,7 +12,8 @@ * * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.20"; + +const VERSION = "1.105.21"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 1706fa0981edf0dd825cd1ee3c604b721dc0f1fd Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 26 Oct 2024 14:29:52 +0200 Subject: [PATCH 17/26] fix: add p to priority queue --- modules/zones-generator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/zones-generator.js b/modules/zones-generator.js index 0fdab688..641a0784 100644 --- a/modules/zones-generator.js +++ b/modules/zones-generator.js @@ -266,7 +266,7 @@ window.Zones = (function () { if (!cost[e] || p < cost[e]) { cost[e] = p; - queue.push({e, p}), p; + queue.push({e, p}, p); } }); } From 91dc16878eade53217a29f710f4b3603157157b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81ngel=20Montero=20Lamas?= Date: Sat, 26 Oct 2024 14:40:43 +0200 Subject: [PATCH 18/26] Stroke dash to cells (#1159) * style.js sorted items alphabetically * style.js added strokeDash to "cells" --- modules/ui/style.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/ui/style.js b/modules/ui/style.js index 49d1d8fb..3df79026 100644 --- a/modules/ui/style.js +++ b/modules/ui/style.js @@ -116,20 +116,20 @@ function selectStyleElement() { if ( [ "armies", - "routes", - "lakes", "biomes", "borders", - "cults", - "relig", "cells", "coastline", - "prec", + "coordinates", + "cults", + "gridOverlay", "ice", "icons", - "coordinates", - "zones", - "gridOverlay" + "lakes", + "prec", + "relig", + "routes", + "zones" ].includes(styleElement) ) { styleStroke.style.display = "block"; @@ -140,7 +140,7 @@ function selectStyleElement() { // stroke dash if ( - ["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes( + ["borders", "cells", "coordinates", "gridOverlay", "legend", "population", "routes", "temperature", "zones"].includes( styleElement ) ) { @@ -788,7 +788,7 @@ styleShadowInput.on("input", function () { styleFontAdd.on("click", function () { addFontNameInput.value = ""; addFontURLInput.value = ""; - + $("#addFontDialog").dialog({ title: "Add custom font", width: "26em", From ca8e7230067d8569472c4ba4beac4e5e79a885a7 Mon Sep 17 00:00:00 2001 From: Dyxang <36600710+dyxang@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:23:38 +0800 Subject: [PATCH 19/26] Temperature parameters can be customized (#1162) * Temperature parameters can be customized * fix typo * update to 1.105.22 * Update index.html --- index.html | 15 +++++++++++++-- modules/ui/ai-generator.js | 15 +++++++++++---- modules/ui/notes-editor.js | 2 +- versioning.js | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 3b2d7203..99c9f849 100644 --- a/index.html +++ b/index.html @@ -4951,7 +4951,18 @@ >Model: - + -
diff --git a/modules/ui/ai-generator.js b/modules/ui/ai-generator.js index daa8cde6..734f1246 100644 --- a/modules/ui/ai-generator.js +++ b/modules/ui/ai-generator.js @@ -1,8 +1,114 @@ "use strict"; -const GPT_MODELS = ["gpt-4o-mini", "chatgpt-4o-latest", "gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"]; +const PROVIDERS = { + openai: { + keyLink: "https://platform.openai.com/account/api-keys", + generate: generateWithOpenAI + }, + anthropic: { + keyLink: "https://console.anthropic.com/account/keys", + generate: generateWithAnthropic + } +}; + +const DEFAULT_MODEL = "gpt-4o-mini"; + +const MODELS = { + "gpt-4o-mini": "openai", + "chatgpt-4o-latest": "openai", + "gpt-4o": "openai", + "gpt-4-turbo": "openai", + "o1-preview": "openai", + "o1-mini": "openai", + "claude-3-5-haiku-latest": "anthropic", + "claude-3-5-sonnet-latest": "anthropic", + "claude-3-opus-latest": "anthropic" +}; + const SYSTEM_MESSAGE = "I'm working on my fantasy map."; +async function generateWithOpenAI({key, model, prompt, temperature, onContent}) { + const headers = { + "Content-Type": "application/json", + Authorization: `Bearer ${key}` + }; + + const messages = [ + {role: "system", content: SYSTEM_MESSAGE}, + {role: "user", content: prompt} + ]; + + const response = await fetch("https://api.openai.com/v1/chat/completions", { + method: "POST", + headers, + body: JSON.stringify({model, messages, temperature, stream: true}) + }); + + const getContent = json => { + const content = json.choices?.[0]?.delta?.content; + if (content) onContent(content); + }; + + await handleStream(response, getContent); +} + +async function generateWithAnthropic({key, model, prompt, temperature, onContent}) { + const headers = { + "Content-Type": "application/json", + "x-api-key": key, + "anthropic-version": "2023-06-01", + "anthropic-dangerous-direct-browser-access": "true" + }; + + const messages = [{role: "user", content: prompt}]; + + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers, + body: JSON.stringify({model, system: SYSTEM_MESSAGE, messages, temperature, max_tokens: 4096, stream: true}) + }); + + const getContent = json => { + const content = json.delta?.text; + if (content) onContent(content); + }; + + await handleStream(response, getContent); +} + +async function handleStream(response, getContent) { + if (!response.ok) { + const json = await response.json(); + throw new Error(json?.error?.message || "Failed to generate"); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + let buffer = ""; + + while (true) { + const {done, value} = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, {stream: true}); + const lines = buffer.split("\n"); + + for (let i = 0; i < lines.length - 1; i++) { + const line = lines[i].trim(); + if (line.startsWith("data: ") && line !== "data: [DONE]") { + try { + const json = JSON.parse(line.slice(6)); + getContent(json); + } catch (jsonError) { + ERROR && console.error(`Failed to parse JSON:`, jsonError, `Line: ${line}`); + } + } + } + + buffer = lines.at(-1); + } +} + function generateWithAi(defaultPrompt, onApply) { updateValues(); @@ -29,90 +135,53 @@ function generateWithAi(defaultPrompt, onApply) { if (modules.generateWithAi) return; modules.generateWithAi = true; + byId("aiGeneratorKeyHelp").on("click", function (e) { + const model = byId("aiGeneratorModel").value; + const provider = MODELS[model]; + openURL(PROVIDERS[provider].keyLink); + }); + function updateValues() { byId("aiGeneratorResult").value = ""; byId("aiGeneratorPrompt").value = defaultPrompt; - byId("aiGeneratorKey").value = localStorage.getItem("fmg-ai-kl") || ""; - byId("aiGeneratorTemperature").value = localStorage.getItem("fmg-ai-temperature") || "1.2"; + byId("aiGeneratorTemperature").value = localStorage.getItem("fmg-ai-temperature") || "1"; const select = byId("aiGeneratorModel"); select.options.length = 0; - GPT_MODELS.forEach(model => select.options.add(new Option(model, model))); - select.value = localStorage.getItem("fmg-ai-model") || GPT_MODELS[0]; + Object.keys(MODELS).forEach(model => select.options.add(new Option(model, model))); + select.value = localStorage.getItem("fmg-ai-model"); + if (!select.value || !MODELS[select.value]) select.value = DEFAULT_MODEL; + + const provider = MODELS[select.value]; + byId("aiGeneratorKey").value = localStorage.getItem(`fmg-ai-kl-${provider}`) || ""; } async function generate(button) { const key = byId("aiGeneratorKey").value; - if (!key) return tip("Please enter an OpenAI API key", true, "error", 4000); - localStorage.setItem("fmg-ai-kl", key); + if (!key) return tip("Please enter an API key", true, "error", 4000); const model = byId("aiGeneratorModel").value; if (!model) return tip("Please select a model", true, "error", 4000); localStorage.setItem("fmg-ai-model", model); + const provider = MODELS[model]; + localStorage.setItem(`fmg-ai-kl-${provider}`, key); + const prompt = byId("aiGeneratorPrompt").value; if (!prompt) return tip("Please enter a prompt", true, "error", 4000); - const temperature = parseFloat(byId("aiGeneratorTemperature").value); - if (isNaN(temperature) || temperature < 0 || temperature > 2) { - return tip("Temperature must be a number between 0 and 2", true, "error", 4000); - } - localStorage.setItem("fmg-ai-temperature", temperature.toString()); + const temperature = byId("aiGeneratorTemperature").valueAsNumber; + if (isNaN(temperature)) return tip("Temperature must be a number", true, "error", 4000); + localStorage.setItem("fmg-ai-temperature", temperature); try { button.disabled = true; const resultArea = byId("aiGeneratorResult"); - resultArea.value = ""; resultArea.disabled = true; + resultArea.value = ""; + const onContent = content => (resultArea.value += content); - const response = await fetch("https://api.openai.com/v1/chat/completions", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${key}` - }, - body: JSON.stringify({ - model, - messages: [ - {role: "system", content: SYSTEM_MESSAGE}, - {role: "user", content: prompt} - ], - temperature: temperature, - stream: true // Enable streaming - }) - }); - - if (!response.ok) { - const json = await response.json(); - throw new Error(json?.error?.message || "Failed to generate"); - } - - const reader = response.body.getReader(); - const decoder = new TextDecoder("utf-8"); - let buffer = ""; - - while (true) { - const {done, value} = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, {stream: true}); - const lines = buffer.split("\n"); - - for (let i = 0; i < lines.length - 1; i++) { - const line = lines[i].trim(); - if (line.startsWith("data: ") && line !== "data: [DONE]") { - try { - const jsonData = JSON.parse(line.slice(6)); - const content = jsonData.choices[0].delta.content; - if (content) resultArea.value += content; - } catch (jsonError) { - console.warn("Failed to parse JSON:", jsonError, "Line:", line); - } - } - } - - buffer = lines[lines.length - 1]; - } + await PROVIDERS[provider].generate({key, model, prompt, temperature, onContent}); } catch (error) { return tip(error.message, true, "error", 4000); } finally { From 8db9dc9bed09e9e7f977974358151603414024e1 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 15 Nov 2024 15:53:41 +0100 Subject: [PATCH 21/26] chore: supporters update --- modules/dynamic/supporters.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/dynamic/supporters.js b/modules/dynamic/supporters.js index 3ca64da0..cf5c9cc7 100644 --- a/modules/dynamic/supporters.js +++ b/modules/dynamic/supporters.js @@ -583,4 +583,9 @@ James Benware FortunesFaded breadsticks Murderbits -Ben Jones`; +Ben Jones +Marco Faltracco +L +silentArtifact +Keith Potter +Morgan Gilbert`; From fa03b2d705532f8c03316c13a06909d45d3aabb4 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 24 Nov 2024 15:02:56 +0100 Subject: [PATCH 22/26] fix: #1170 --- index.html | 2 +- modules/dynamic/editors/cultures-editor.js | 35 ++++++++++++---------- modules/ui/editors.js | 2 +- versioning.js | 2 +- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index d5a3b000..913f4b29 100644 --- a/index.html +++ b/index.html @@ -8076,7 +8076,7 @@ - + diff --git a/modules/dynamic/editors/cultures-editor.js b/modules/dynamic/editors/cultures-editor.js index b8e43608..dee807db 100644 --- a/modules/dynamic/editors/cultures-editor.js +++ b/modules/dynamic/editors/cultures-editor.js @@ -345,10 +345,13 @@ function cultureChangeName() { } function cultureRegenerateName() { - const culture = +this.parentNode.dataset.id; - const name = Names.getCultureShort(culture); + const cultureId = +this.parentNode.dataset.id; + const base = pack.cultures[cultureId].base; + if (!nameBases[base]) return tip("Namesbase is not defined, please select a valid namesbase", false, "error", 5000); + + const name = Names.getCultureShort(cultureId); this.parentNode.querySelector("input.cultureName").value = name; - pack.cultures[culture].name = name; + pack.cultures[cultureId].name = name; } function cultureChangeExpansionism() { @@ -494,12 +497,15 @@ function cultureRegenerateBurgs() { if (customization === 4) return; const cultureId = +this.parentNode.dataset.id; - const cBurgs = pack.burgs.filter(b => b.culture === cultureId && !b.lock); - cBurgs.forEach(b => { + const base = pack.cultures[cultureId].base; + if (!nameBases[base]) return tip("Namesbase is not defined, please select a valid namesbase", false, "error", 5000); + + const cultureBurgs = pack.burgs.filter(b => b.culture === cultureId && !b.removed && !b.lock); + cultureBurgs.forEach(b => { b.name = Names.getCulture(cultureId); labels.select("[data-id='" + b.i + "']").text(b.name); }); - tip(`Names for ${cBurgs.length} burgs are regenerated`, false, "success"); + tip(`Names for ${cultureBurgs.length} burgs are regenerated`, false, "success"); } function removeCulture(cultureId) { @@ -849,14 +855,15 @@ async function uploadCulturesData() { this.value = ""; const csv = await file.text(); const data = d3.csvParse(csv, d => ({ - i: +d.Id, name: d.Name, + i: +d.Id, color: d.Color, expansionism: +d.Expansionism, type: d.Type, population: +d.Population, emblemsShape: d["Emblems Shape"], - origins: d.Origins + origins: d.Origins, + namesbase: d.Namesbase })); const {cultures, cells} = pack; @@ -883,7 +890,7 @@ async function uploadCulturesData() { culture.i ); } else { - current = {i: cultures.length, center: ra(populated), area: 0, cells: 0, origin: 0, rural: 0, urban: 0}; + current = {i: cultures.length, center: ra(populated), area: 0, cells: 0, origins: [0], rural: 0, urban: 0}; cultures.push(current); } @@ -903,6 +910,10 @@ async function uploadCulturesData() { else current.type = "Generic"; } + culture.origins = current.i ? restoreOrigins(culture.origins || "") : [null]; + current.shield = shapes.includes(culture.emblemsShape) ? culture.emblemsShape : "heater"; + current.base = nameBases.findIndex(n => n.name == culture.namesbase); // can be -1 if namesbase is not found + function restoreOrigins(originsString) { const originNames = originsString .replaceAll('"', "") @@ -918,12 +929,6 @@ async function uploadCulturesData() { current.origins = originIds.filter(id => id !== null); if (!current.origins.length) current.origins = [0]; } - - culture.origins = current.i ? restoreOrigins(culture.origins || "") : [null]; - current.shield = shapes.includes(culture.emblemsShape) ? culture.emblemsShape : "heater"; - - const nameBaseIndex = nameBases.findIndex(n => n.name == culture.namesbase); - current.base = nameBaseIndex === -1 ? 0 : nameBaseIndex; } cultures.filter(c => c.removed).forEach(c => removeCulture(c.i)); diff --git a/modules/ui/editors.js b/modules/ui/editors.js index 13380155..a7df5ca3 100644 --- a/modules/ui/editors.js +++ b/modules/ui/editors.js @@ -1255,7 +1255,7 @@ async function editStates() { async function editCultures() { if (customization) return; - const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.105.11"); + const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.105.23"); Editor.open(); } diff --git a/versioning.js b/versioning.js index fa8da7ec..e1d7b9f0 100644 --- a/versioning.js +++ b/versioning.js @@ -13,7 +13,7 @@ * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.22"; +const VERSION = "1.105.23"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From 03c7db32ef29c641d256793b9d72cf11c2621680 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 27 Nov 2024 12:21:36 +0100 Subject: [PATCH 23/26] fix: #1172 --- index.html | 2 +- modules/dynamic/auto-update.js | 4 ++++ modules/io/load.js | 2 +- versioning.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 913f4b29..304a757e 100644 --- a/index.html +++ b/index.html @@ -8118,7 +8118,7 @@ - + diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index 50e6ed92..10a9a375 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -972,5 +972,9 @@ export function resolveVersionConflicts(mapVersion) { .attr("letter-spacing", null) .attr("fill", null) .attr("stroke", null); + + // pole can be missing for some states/provinces + BurgsAndStates.getPoles(); + Provinces.getPoles(); } } diff --git a/modules/io/load.js b/modules/io/load.js index 05819ce7..8e05a798 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -471,7 +471,7 @@ async function parseLoadedData(data, mapVersion) { { // dynamically import and run auto-update script - const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.105.10"); + const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.105.24"); resolveVersionConflicts(mapVersion); } diff --git a/versioning.js b/versioning.js index e1d7b9f0..33bc1739 100644 --- a/versioning.js +++ b/versioning.js @@ -13,7 +13,7 @@ * Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2 */ -const VERSION = "1.105.23"; +const VERSION = "1.105.24"; if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function"); { From fd8fd28ab104f44a2d556aafdfbe15574ad505d9 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 28 Nov 2024 12:02:43 +0100 Subject: [PATCH 24/26] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index b027f814..f01a2390 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,3 @@ # These are supported funding model platforms - +github: Azgaar patreon: Azgaar From 610a2d9ae67ad49e8f0c6a6bc673d3e2b518e66d Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 28 Nov 2024 12:19:05 +0100 Subject: [PATCH 25/26] chore: update supporters --- modules/dynamic/supporters.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/dynamic/supporters.js b/modules/dynamic/supporters.js index cf5c9cc7..50a26d58 100644 --- a/modules/dynamic/supporters.js +++ b/modules/dynamic/supporters.js @@ -588,4 +588,5 @@ Marco Faltracco L silentArtifact Keith Potter -Morgan Gilbert`; +Morgan Gilbert +Alengork Gamer`; From 66d22f26c0b9d6e5bf6e2170ac5d15c41834d830 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 12 Dec 2024 13:11:54 +0100 Subject: [PATCH 26/26] [Draft] Submap refactoring (#1153) * refactor: submap - start * refactor: submap - continue * Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into submap-refactoring * refactor: submap - relocate burgs * refactor: submap - restore routes * refactor: submap - restore lake names * refactor: submap - UI update * refactor: submap - restore river and biome data * refactor: submap - simplify options * refactor: submap - restore rivers * refactor: submap - recalculateMapSize * refactor: submap - add middle points * refactor: submap - don't add middle points, unified findPath fn * chore: update version * feat: submap - relocate out of map regiments * feat: submap - fix route gen * feat: submap - allow custom number of cells * feat: submap - add checkbox submapRescaleBurgStyles * feat: submap - update version hash * chore: supporters update --------- Co-authored-by: Azgaar --- index.html | 298 +++++++++++++++------------- main.js | 19 +- modules/burgs-and-states.js | 3 +- modules/resample.js | 365 +++++++++++++++++++++++++++++++++++ modules/river-generator.js | 64 +++--- modules/routes-generator.js | 234 +++++++--------------- modules/ui/layers.js | 4 +- modules/ui/options.js | 8 +- modules/ui/rivers-creator.js | 28 +-- modules/ui/rivers-editor.js | 18 +- modules/ui/submap-tool.js | 95 +++++++++ modules/ui/submap.js | 332 ------------------------------- modules/ui/tools.js | 50 +++-- modules/ui/transform-tool.js | 201 +++++++++++++++++++ utils/graphUtils.js | 6 +- utils/pathUtils.js | 57 ++++++ utils/shorthands.js | 2 + versioning.js | 4 +- 18 files changed, 1043 insertions(+), 745 deletions(-) create mode 100644 modules/resample.js create mode 100644 modules/ui/submap-tool.js delete mode 100644 modules/ui/submap.js create mode 100644 modules/ui/transform-tool.js diff --git a/index.html b/index.html index 304a757e..8a268152 100644 --- a/index.html +++ b/index.html @@ -1081,8 +1081,14 @@ id="styleGridSizeFriendly" data-tip="Distance between grid cell centers (in map scale)" > - - + + @@ -1834,6 +1840,7 @@ @@ -2179,8 +2186,8 @@
Create
- - + +
@@ -2418,7 +2425,9 @@
- +
@@ -3512,7 +3521,11 @@ - +