From 8579ae7bff022305122f7579e7332e4e04cb0416 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 7 Aug 2023 00:39:59 +0400 Subject: [PATCH 001/244] feat: add slow marker to the Smooth geometry option --- index.html | 4 +++- modules/coa-generator.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 616f731e..167fe89c 100644 --- a/index.html +++ b/index.html @@ -5731,7 +5731,9 @@ style="margin: 0.6em 0 0.3em -0.2em" > - +
diff --git a/modules/coa-generator.js b/modules/coa-generator.js index 5ecfae60..24b27cfa 100644 --- a/modules/coa-generator.js +++ b/modules/coa-generator.js @@ -726,7 +726,7 @@ window.COA = (function () { // charges specific to culture or burg type (FMG-only config, not coming from Armoria) const typeMapping = { - Naval: {anchor: 3, boat: 1, lymphad: 2, armillarySphere: 1, escallop: 1, dolphin: 1, plaice: 1, cavavel: 1}, + Naval: {anchor: 3, boat: 1, lymphad: 2, armillarySphere: 1, escallop: 1, dolphin: 1, plaice: 1, caravel: 1}, Highland: {tower: 1, raven: 1, wolfHeadErased: 1, wolfPassant: 1, goat: 1, axe: 1}, River: { tower: 1, From c72c998eaf57d1cb07fe111b1c34b5fb760804f9 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 8 Aug 2023 01:34:36 +0400 Subject: [PATCH 002/244] bug: style to have texture info --- index.html | 2 +- main.js | 7 ++++ modules/dynamic/auto-update.js | 14 +++++++ modules/io/export.js | 18 ++++++++- modules/ui/layers.js | 21 ++-------- modules/ui/style.js | 71 ++++++++++++++++++++++++++-------- modules/ui/stylePresets.js | 6 ++- styles/ancient.json | 3 +- styles/atlas.json | 7 +++- styles/clean.json | 8 +++- styles/cyberpunk.json | 7 +++- styles/default.json | 5 +++ styles/gloom.json | 5 ++- styles/light.json | 7 +++- styles/monochrome.json | 8 +++- styles/pale.json | 5 ++- styles/watercolor.json | 5 +++ versioning.js | 2 +- 18 files changed, 150 insertions(+), 51 deletions(-) diff --git a/index.html b/index.html index 167fe89c..0ff9b35a 100644 --- a/index.html +++ b/index.html @@ -865,7 +865,7 @@ Image + %
${si(population)}
- ${i > 12 && !b.cells[i] ? '' : ""} + ${ + i > 12 && !b.cells[i] + ? '' + : "" + }
`; } @@ -403,7 +411,14 @@ function editBiomes() { // change of append new element if (exists.size()) exists.attr("data-biome", biomeNew).attr("fill", color).attr("stroke", color); - else temp.append("polygon").attr("data-cell", i).attr("data-biome", biomeNew).attr("points", getPackPolygon(i)).attr("fill", color).attr("stroke", color); + else + temp + .append("polygon") + .attr("data-cell", i) + .attr("data-biome", biomeNew) + .attr("points", getPackPolygon(i)) + .attr("fill", color) + .attr("stroke", color); }); } @@ -449,8 +464,8 @@ function editBiomes() { } function restoreInitialBiomes() { - biomesData = applyDefaultBiomesSystem(); - defineBiomes(); + biomesData = Biomes.getDefault(); + Biomes.define(); drawBiomes(); recalculatePopulation(); refreshBiomesEditor(); diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 44be3fa0..bde67df7 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -239,7 +239,7 @@ function editHeightmap(options) { drawRivers(); Lakes.defineGroup(); - defineBiomes(); + Biomes.define(); rankCells(); Cultures.generate(); @@ -373,10 +373,6 @@ function editHeightmap(options) { const g = pack.cells.g[i]; const isLand = pack.cells.h[i] >= 20; - // check biome - pack.cells.biome[i] = - isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]); - // rivers data if (!erosionAllowed) { pack.cells.r[i] = r[g]; @@ -384,6 +380,12 @@ function editHeightmap(options) { pack.cells.fl[i] = fl[g]; } + // check biome + pack.cells.biome[i] = + isLand && biome[g] + ? biome[g] + : Biomes.getId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i], Boolean(pack.cells.r[i])); + if (!isLand) continue; pack.cells.culture[i] = culture[g]; pack.cells.pop[i] = pop[g]; diff --git a/modules/ui/world-configurator.js b/modules/ui/world-configurator.js index ae222a59..c47beaca 100644 --- a/modules/ui/world-configurator.js +++ b/modules/ui/world-configurator.js @@ -85,7 +85,7 @@ function editWorld() { Lakes.defineGroup(); Rivers.specify(); pack.cells.h = new Float32Array(heights); - defineBiomes(); + Biomes.define(); if (layerIsOn("toggleTemp")) drawTemp(); if (layerIsOn("togglePrec")) drawPrec(); diff --git a/versioning.js b/versioning.js index d6b17ab3..ab4fb4e1 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.91.04"; // generator version, update each time +const version = "1.91.05"; // generator version, update each time { document.title += " v" + version; From 87599d153009cfd5fb2505c2ea94f0e84614edc7 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 11 Aug 2023 18:56:42 +0400 Subject: [PATCH 013/244] State labels: new label placing algorithm (#977) * feat: draw state labels start * feat: update old .map files * chore: update version hash * fear: add change to the user's changelog --- index.css | 2 +- index.html | 21 +- main.js | 2 +- modules/burgs-and-states.js | 218 ------------------ modules/dynamic/auto-update.js | 7 + modules/dynamic/editors/states-editor.js | 8 +- modules/io/load.js | 2 +- modules/renderers/drawStatelabels.js | 280 +++++++++++++++++++++++ modules/submap.js | 2 +- modules/ui/editors.js | 2 +- modules/ui/heightmap-editor.js | 4 +- modules/ui/labels-editor.js | 23 +- modules/ui/provinces-editor.js | 89 +++++-- modules/ui/tools.js | 11 +- versioning.js | 5 +- 15 files changed, 395 insertions(+), 281 deletions(-) create mode 100644 modules/renderers/drawStatelabels.js diff --git a/index.css b/index.css index 4bd1a328..505b0716 100644 --- a/index.css +++ b/index.css @@ -263,7 +263,7 @@ i.icon-lock { } #labels { - text-anchor: start; + text-anchor: middle; dominant-baseline: central; cursor: pointer; } diff --git a/index.html b/index.html index ed6d14a4..19dc0d0a 100644 --- a/index.html +++ b/index.html @@ -138,7 +138,7 @@ } - + @@ -7946,7 +7946,8 @@ - + + @@ -7963,15 +7964,15 @@ - + - - + + - - + + @@ -7980,7 +7981,7 @@ - + @@ -7999,14 +8000,14 @@ - + - + diff --git a/main.js b/main.js index 6967242d..4c40de43 100644 --- a/main.js +++ b/main.js @@ -648,7 +648,7 @@ async function generate(options) { drawStates(); drawBorders(); - BurgsAndStates.drawStateLabels(); + drawStateLabels(); Rivers.specify(); Lakes.generateName(); diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index 6f2bf831..ff433f46 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -502,223 +502,6 @@ window.BurgsAndStates = (function () { TIME && console.timeEnd("updateCulturesForBurgsAndStates"); }; - // calculate and draw curved state labels for a list of states - const drawStateLabels = function (list) { - TIME && console.time("drawStateLabels"); - const {cells, features, states} = pack; - const paths = []; // text paths - lineGen.curve(d3.curveBundle.beta(1)); - const mode = options.stateLabelsMode || "auto"; - - for (const s of states) { - if (!s.i || s.removed || s.lock || !s.cells || (list && !list.includes(s.i))) continue; - - const used = []; - const visualCenter = findCell(s.pole[0], s.pole[1]); - const start = cells.state[visualCenter] === s.i ? visualCenter : s.center; - const hull = getHull(start, s.i, s.cells / 10); - const points = [...hull].map(v => pack.vertices.p[v]); - const delaunay = Delaunator.from(points); - const voronoi = new Voronoi(delaunay, points, points.length); - const chain = connectCenters(voronoi.vertices, s.pole[1]); - const relaxed = chain.map(i => voronoi.vertices.p[i]).filter((p, i) => i % 15 === 0 || i + 1 === chain.length); - paths.push([s.i, relaxed]); - - function getHull(start, state, maxLake) { - const queue = [start]; - const hull = new Set(); - - while (queue.length) { - const q = queue.pop(); - const sameStateNeibs = cells.c[q].filter(c => cells.state[c] === state); - - cells.c[q].forEach(function (c, d) { - const passableLake = features[cells.f[c]].type === "lake" && features[cells.f[c]].cells < maxLake; - if (cells.b[c] || (cells.state[c] !== state && !passableLake)) return hull.add(cells.v[q][d]); - - const hasCoadjacentSameStateCells = sameStateNeibs.some(neib => cells.c[c].includes(neib)); - if (hull.size > 20 && !hasCoadjacentSameStateCells && !passableLake) return hull.add(cells.v[q][d]); - - if (used[c]) return; - used[c] = 1; - queue.push(c); - }); - } - - return hull; - } - - function connectCenters(c, y) { - // check if vertex is inside the area - const inside = c.p.map(function (p) { - if (p[0] <= 0 || p[1] <= 0 || p[0] >= graphWidth || p[1] >= graphHeight) return false; // out of the screen - return used[findCell(p[0], p[1])]; - }); - - const pointsInside = d3.range(c.p.length).filter(i => inside[i]); - if (!pointsInside.length) return [0]; - const h = c.p.length < 200 ? 0 : c.p.length < 600 ? 0.5 : 1; // power of horyzontality shift - const end = - pointsInside[ - d3.scan( - pointsInside, - (a, b) => c.p[a][0] - c.p[b][0] + (Math.abs(c.p[a][1] - y) - Math.abs(c.p[b][1] - y)) * h - ) - ]; // left point - const start = - pointsInside[ - d3.scan( - pointsInside, - (a, b) => c.p[b][0] - c.p[a][0] - (Math.abs(c.p[b][1] - y) - Math.abs(c.p[a][1] - y)) * h - ) - ]; // right point - - // connect leftmost and rightmost points with shortest path - const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); - const cost = [], - from = []; - queue.queue({e: start, p: 0}); - - while (queue.length) { - const next = queue.dequeue(), - n = next.e, - p = next.p; - if (n === end) break; - - for (const v of c.v[n]) { - if (v === -1) continue; - const totalCost = p + (inside[v] ? 1 : 100); - if (from[v] || totalCost >= cost[v]) continue; - cost[v] = totalCost; - from[v] = n; - queue.queue({e: v, p: totalCost}); - } - } - - // restore path - const chain = [end]; - let cur = end; - while (cur !== start) { - cur = from[cur]; - if (inside[cur]) chain.push(cur); - } - return chain; - } - } - - void (function drawLabels() { - const g = labels.select("#states"); - const t = defs.select("#textPaths"); - const displayed = layerIsOn("toggleLabels"); - if (!displayed) toggleLabels(); - - // remove state labels to be redrawn - for (const state of pack.states) { - if (!state.i || state.removed || state.lock) continue; - if (list && !list.includes(state.i)) continue; - - byId(`stateLabel${state.i}`)?.remove(); - byId(`textPath_stateLabel${state.i}`)?.remove(); - } - - const example = g.append("text").attr("x", 0).attr("x", 0).text("Average"); - const letterLength = example.node().getComputedTextLength() / 7; // average length of 1 letter - - paths.forEach(p => { - const id = p[0]; - const state = states[p[0]]; - const {name, fullName} = state; - - const path = p[1].length > 1 ? round(lineGen(p[1])) : `M${p[1][0][0] - 50},${p[1][0][1]}h${100}`; - const textPath = t - .append("path") - .attr("d", path) - .attr("id", "textPath_stateLabel" + id); - const pathLength = p[1].length > 1 ? textPath.node().getTotalLength() / letterLength : 0; // path length in letters - - const [lines, ratio] = getLines(mode, name, fullName, pathLength); - - // prolongate path if it's too short - if (pathLength && pathLength < lines[0].length) { - const points = p[1]; - const f = points[0]; - const l = points[points.length - 1]; - const [dx, dy] = [l[0] - f[0], l[1] - f[1]]; - const mod = Math.abs((letterLength * lines[0].length) / dx) / 2; - points[0] = [rn(f[0] - dx * mod), rn(f[1] - dy * mod)]; - points[points.length - 1] = [rn(l[0] + dx * mod), rn(l[1] + dy * mod)]; - textPath.attr("d", round(lineGen(points))); - } - - example.attr("font-size", ratio + "%"); - const top = (lines.length - 1) / -2; // y offset - const spans = lines.map((l, d) => { - example.text(l); - const left = example.node().getBBox().width / -2; // x offset - return `${l}`; - }); - - const el = g - .append("text") - .attr("id", "stateLabel" + id) - .append("textPath") - .attr("xlink:href", "#textPath_stateLabel" + id) - .attr("startOffset", "50%") - .attr("font-size", ratio + "%") - .node(); - - el.insertAdjacentHTML("afterbegin", spans.join("")); - if (mode === "full" || lines.length === 1) return; - - // check whether multilined label is generally inside the state. If no, replace with short name label - const cs = pack.cells.state; - const b = el.parentNode.getBBox(); - const c1 = () => +cs[findCell(b.x, b.y)] === id; - const c2 = () => +cs[findCell(b.x + b.width / 2, b.y)] === id; - const c3 = () => +cs[findCell(b.x + b.width, b.y)] === id; - const c4 = () => +cs[findCell(b.x + b.width, b.y + b.height)] === id; - const c5 = () => +cs[findCell(b.x + b.width / 2, b.y + b.height)] === id; - const c6 = () => +cs[findCell(b.x, b.y + b.height)] === id; - if (c1() + c2() + c3() + c4() + c5() + c6() > 3) return; // generally inside => exit - - // move to one-line name - const text = pathLength > fullName.length * 1.8 ? fullName : name; - example.text(text); - const left = example.node().getBBox().width / -2; // x offset - el.innerHTML = `${text}`; - - const correctedRatio = minmax(rn((pathLength / text.length) * 60), 40, 130); - el.setAttribute("font-size", correctedRatio + "%"); - }); - - example.remove(); - if (!displayed) toggleLabels(); - })(); - - function getLines(mode, name, fullName, pathLength) { - // short name - if (mode === "short" || (mode === "auto" && pathLength < name.length)) { - const lines = splitInTwo(name); - const ratio = pathLength / lines[0].length; - return [lines, minmax(rn(ratio * 60), 50, 150)]; - } - - // full name: one line - if (pathLength > fullName.length * 2.5) { - 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 ratio = pathLength / lines[0].length; - return [lines, minmax(rn(ratio * 60), 70, 150)]; - } - - TIME && console.timeEnd("drawStateLabels"); - }; - // calculate states data like area, population etc. const collectStatistics = function () { TIME && console.time("collectStatistics"); @@ -1405,7 +1188,6 @@ window.BurgsAndStates = (function () { specifyBurgs, defineBurgFeatures, getType, - drawStateLabels, collectStatistics, generateCampaign, generateCampaigns, diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index 5bcb2c2b..fcd12273 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -698,4 +698,11 @@ export function resolveVersionConflicts(version) { } }); } + + if (version < 1.92) { + // v1.92 change labels text-anchor from 'start' to 'middle' + labels.selectAll("tspan").each(function () { + this.setAttribute("x", 0); + }); + } } diff --git a/modules/dynamic/editors/states-editor.js b/modules/dynamic/editors/states-editor.js index 11d9581d..a6128c57 100644 --- a/modules/dynamic/editors/states-editor.js +++ b/modules/dynamic/editors/states-editor.js @@ -494,7 +494,7 @@ function editStateName(state) { s.name = nameInput.value; s.formName = formSelect.value; s.fullName = fullNameInput.value; - if (changed && stateNameEditorUpdateLabel.checked) BurgsAndStates.drawStateLabels([s.i]); + if (changed && stateNameEditorUpdateLabel.checked) drawStateLabels([s.i]); refreshStatesEditor(); } } @@ -877,7 +877,7 @@ function recalculateStates(must) { if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); if (layerIsOn("toggleProvinces")) drawProvinces(); - if (adjustLabels.checked) BurgsAndStates.drawStateLabels(); + if (adjustLabels.checked) drawStateLabels(); refreshStatesEditor(); } @@ -1022,7 +1022,7 @@ function applyStatesManualAssignent() { if (affectedStates.length) { refreshStatesEditor(); layerIsOn("toggleStates") ? drawStates() : toggleStates(); - if (adjustLabels.checked) BurgsAndStates.drawStateLabels([...new Set(affectedStates)]); + if (adjustLabels.checked) drawStateLabels([...new Set(affectedStates)]); adjustProvinces([...new Set(affectedProvinces)]); layerIsOn("toggleBorders") ? drawBorders() : toggleBorders(); if (layerIsOn("toggleProvinces")) drawProvinces(); @@ -1459,7 +1459,7 @@ function openStateMergeDialog() { layerIsOn("toggleStates") ? drawStates() : toggleStates(); layerIsOn("toggleBorders") ? drawBorders() : toggleBorders(); layerIsOn("toggleProvinces") && drawProvinces(); - BurgsAndStates.drawStateLabels([rulingStateId]); + drawStateLabels([rulingStateId]); refreshStatesEditor(); } diff --git a/modules/io/load.js b/modules/io/load.js index ff4792ad..7b39fd4e 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -435,7 +435,7 @@ async function parseLoadedData(data) { { // dynamically import and run auto-udpdate script const versionNumber = parseFloat(params[0]); - const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.91.00"); + const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.00"); resolveVersionConflicts(versionNumber); } diff --git a/modules/renderers/drawStatelabels.js b/modules/renderers/drawStatelabels.js new file mode 100644 index 00000000..0c82a4cd --- /dev/null +++ b/modules/renderers/drawStatelabels.js @@ -0,0 +1,280 @@ +"use strict"; + +// list - an optional array of stateIds to regenerate +function drawStateLabels(list) { + console.time("drawStateLabels"); + + const {cells, states, features} = pack; + const stateIds = cells.state; + + // 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 INITIAL_DISTANCE = 10; + const DISTANCE_STEP = 15; + const MAX_ITERATIONS = 100; + + const labelPaths = getLabelPaths(); + drawLabelPath(); + + function getLabelPaths() { + const labelPaths = []; + + for (const state of states) { + if (!state.i || state.removed || state.lock) continue; + if (list && !list.includes(state.i)) continue; + + const offset = getOffsetWidth(state.cells); + const maxLakeSize = state.cells / 50; + 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 { + 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 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} + ); + + 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 drawLabelPath() { + const mode = options.stateLabelsMode || "auto"; + const lineGen = d3.line().curve(d3.curveBundle.beta(1)); + + const textGroup = d3.select("g#labels > g#states"); + const pathGroup = d3.select("defs > g#deftemp > g#textPaths"); + + const testLabel = textGroup.append("text").attr("x", 0).attr("y", 0).text("Example"); + const letterLength = testLabel.node().getComputedTextLength() / 7; // approximate length of 1 letter + testLabel.remove(); + + for (const [stateId, pathPoints] of labelPaths) { + const state = states[stateId]; + if (!state.i || state.removed) throw new Error("State must not be neutral or removed"); + if (pathPoints.length < 2) throw new Error("Label path must have at least 2 points"); + + textGroup.select("#stateLabel" + stateId).remove(); + pathGroup.select("#textPath_stateLabel" + stateId).remove(); + + const textPath = pathGroup + .append("path") + .attr("d", round(lineGen(pathPoints))) + .attr("id", "textPath_stateLabel" + stateId); + + const pathLength = textPath.node().getTotalLength() / letterLength; // path length in letters + const [lines, ratio] = getLinesAndRatio(mode, state.name, state.fullName, pathLength); + + // prolongate path if it's too short + const longestLineLength = d3.max(lines.map(({length}) => length)); + if (pathLength && pathLength < longestLineLength) { + const [x1, y1] = pathPoints.at(0); + const [x2, y2] = pathPoints.at(-1); + const [dx, dy] = [(x2 - x1) / 2, (y2 - y1) / 2]; + + const mod = longestLineLength / pathLength; + pathPoints[0] = [x1 + dx - dx * mod, y1 + dy - dy * mod]; + pathPoints[pathPoints.length - 1] = [x2 - dx + dx * mod, y2 - dy + dy * mod]; + + textPath.attr("d", round(lineGen(pathPoints))); + } + + const textElement = textGroup + .append("text") + .attr("id", "stateLabel" + stateId) + .append("textPath") + .attr("startOffset", "50%") + .attr("font-size", ratio + "%") + .node(); + + const top = (lines.length - 1) / -2; // y offset + const spans = lines.map((line, index) => `${line}`); + textElement.insertAdjacentHTML("afterbegin", spans.join("")); + + const {width, height} = textElement.getBBox(); + textElement.setAttribute("href", "#textPath_stateLabel" + stateId); + + if (mode === "full" || lines.length === 1) continue; + + // check if label fits state boundaries. If no, replace it with short name + const [[x1, y1], [x2, y2]] = [pathPoints.at(0), pathPoints.at(-1)]; + const angleRad = Math.atan2(y2 - y1, x2 - x1); + + const isInsideState = checkIfInsideState(textElement, angleRad, width / 2, height / 2, stateIds, stateId); + if (isInsideState) continue; + + // replace name to one-liner + 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); + 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 + } + + function precalculateAngles(step) { + const angles = []; + 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}); + } + + return angles; + } + + function getLinesAndRatio(mode, name, fullName, pathLength) { + // short name + if (mode === "short" || (mode === "auto" && pathLength <= name.length)) { + const lines = splitInTwo(name); + const longestLineLength = d3.max(lines.map(({length}) => length)); + const ratio = pathLength / longestLineLength; + return [lines, minmax(rn(ratio * 60), 50, 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 + function checkIfInsideState(textElement, angleRad, halfwidth, halfheight, stateIds, stateId) { + const bbox = textElement.getBBox(); + const [cx, cy] = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2]; + + const points = [ + [-halfwidth, -halfheight], + [+halfwidth, -halfheight], + [+halfwidth, halfheight], + [-halfwidth, halfheight], + [0, halfheight], + [0, -halfheight] + ]; + + const sin = Math.sin(angleRad); + const cos = Math.cos(angleRad); + const rotatedPoints = points.map(([x, y]) => [cx + x * cos - y * sin, cy + x * sin + y * cos]); + + let pointsInside = 0; + for (const [x, y] of rotatedPoints) { + const isInside = stateIds[findCell(x, y)] === stateId; + if (isInside) pointsInside++; + if (pointsInside > 4) return true; + } + + return false; + } + + console.timeEnd("drawStateLabels"); +} diff --git a/modules/submap.js b/modules/submap.js index 0544ba71..0918d715 100644 --- a/modules/submap.js +++ b/modules/submap.js @@ -276,7 +276,7 @@ window.Submap = (function () { drawStates(); drawBorders(); - BurgsAndStates.drawStateLabels(); + drawStateLabels(); Rivers.specify(); Lakes.generateName(); diff --git a/modules/ui/editors.js b/modules/ui/editors.js index ab1bcc66..d28abe30 100644 --- a/modules/ui/editors.js +++ b/modules/ui/editors.js @@ -1176,7 +1176,7 @@ function refreshAllEditors() { // dynamically loaded editors async function editStates() { if (customization) return; - const Editor = await import("../dynamic/editors/states-editor.js?v=1.89.35"); + const Editor = await import("../dynamic/editors/states-editor.js?v=1.92.00"); Editor.open(); } diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index bde67df7..41031786 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -253,7 +253,7 @@ function editHeightmap(options) { drawStates(); drawBorders(); - BurgsAndStates.drawStateLabels(); + drawStateLabels(); Rivers.specify(); Lakes.generateName(); @@ -442,7 +442,7 @@ function editHeightmap(options) { c.center = findCell(c.x, c.y); } - BurgsAndStates.drawStateLabels(); + drawStateLabels(); drawStates(); drawBorders(); diff --git a/modules/ui/labels-editor.js b/modules/ui/labels-editor.js index 8bd04cdd..d19de7ae 100644 --- a/modules/ui/labels-editor.js +++ b/modules/ui/labels-editor.js @@ -78,7 +78,9 @@ function editLabel() { } function updateValues(textPath) { - document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")].map(tspan => tspan.textContent).join("|"); + document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")] + .map(tspan => tspan.textContent) + .join("|"); document.getElementById("labelStartOffset").value = parseFloat(textPath.getAttribute("startOffset")); document.getElementById("labelRelativeSize").value = parseFloat(textPath.getAttribute("font-size")); } @@ -298,22 +300,15 @@ function editLabel() { function changeText() { const input = document.getElementById("labelText").value; const el = elSelected.select("textPath").node(); - const example = d3.select(elSelected.node().parentNode).append("text").attr("x", 0).attr("x", 0).attr("font-size", el.getAttribute("font-size")).node(); const lines = input.split("|"); - const top = (lines.length - 1) / -2; // y offset - const inner = lines - .map((l, d) => { - example.innerHTML = l; - const left = example.getBBox().width / -2; // x offset - return `${l}`; - }) - .join(""); + if (lines.length > 1) { + const top = (lines.length - 1) / -2; // y offset + el.innerHTML = lines.map((line, index) => `${line}`).join(""); + } else el.innerHTML = `${lines}`; - el.innerHTML = inner; - example.remove(); - - if (elSelected.attr("id").slice(0, 10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning"); + if (elSelected.attr("id").slice(0, 10) === "stateLabel") + tip("Use States Editor to change an actual state name, not just a label", false, "warning"); } function generateRandomName() { diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js index f3c7660f..07ee522d 100644 --- a/modules/ui/provinces-editor.js +++ b/modules/ui/provinces-editor.js @@ -124,7 +124,9 @@ function editProvinces() { const rural = p.rural * populationRate; const urban = p.urban * populationRate * urbanization; const population = rn(rural + urban); - const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`; + const populationTip = `Total population: ${si(population)}; Rural population: ${si( + rural + )}; Urban population: ${si(urban)}`; totalPopulation += population; const stateName = pack.states[p.state].name; @@ -145,9 +147,15 @@ function editProvinces() { > - - - + + + Urban: - -

Total population: ${l(total)} ⇒ ${l(total)} (100%)

`; + +

Total population: ${l(total)} ⇒ ${l( + total + )} (100%)

`; const update = function () { const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; @@ -694,7 +715,13 @@ function editProvinces() { function updateChart() { const value = - this.value === "area" ? d => d.area : this.value === "rural" ? d => d.rural : this.value === "urban" ? d => d.urban : d => d.rural + d.urban; + this.value === "area" + ? d => d.area + : this.value === "rural" + ? d => d.rural + : this.value === "urban" + ? d => d.urban + : d => d.rural + d.urban; root.sum(value); node.data(treeLayout(root).leaves()); @@ -776,7 +803,13 @@ function editProvinces() { customization = 11; provs.select("g#provincesBody").append("g").attr("id", "temp"); - provs.select("g#provincesBody").append("g").attr("id", "centers").attr("fill", "none").attr("stroke", "#ff0000").attr("stroke-width", 1); + provs + .select("g#provincesBody") + .append("g") + .attr("id", "centers") + .attr("fill", "none") + .attr("stroke", "#ff0000") + .attr("stroke-width", 1); document.querySelectorAll("#provincesBottom > *").forEach(el => (el.style.display = "none")); document.getElementById("provincesManuallyButtons").style.display = "inline-block"; @@ -788,7 +821,11 @@ function editProvinces() { $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); tip("Click on a province to select, drag the circle to change province", true); - viewbox.style("cursor", "crosshair").on("click", selectProvinceOnMapClick).call(d3.drag().on("start", dragBrush)).on("touchmove mousemove", moveBrush); + viewbox + .style("cursor", "crosshair") + .on("click", selectProvinceOnMapClick) + .call(d3.drag().on("start", dragBrush)) + .on("touchmove mousemove", moveBrush); body.querySelector("div").classList.add("selected"); selectProvince(+body.querySelector("div").dataset.id); @@ -859,7 +896,11 @@ function editProvinces() { if (i === pack.provinces[provinceOld].center) { const center = centers.select("polygon[data-center='" + i + "']"); if (!center.size()) centers.append("polygon").attr("data-center", i).attr("points", getPackPolygon(i)); - tip("Province center cannot be assigned to a different region. Please remove the province first", false, "error"); + tip( + "Province center cannot be assigned to a different region. Please remove the province first", + false, + "error" + ); return; } @@ -921,7 +962,8 @@ function editProvinces() { provincesHeader.querySelector("div[data-sortby='state']").style.left = "22em"; provincesFooter.style.display = "block"; body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); - if (!close) $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); + if (!close) + $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); restoreDefaultEvents(); clearMainTip(); @@ -943,14 +985,20 @@ function editProvinces() { const {cells, provinces} = pack; const point = d3.mouse(this); const center = findCell(point[0], point[1]); - if (cells.h[center] < 20) return tip("You cannot place province into the water. Please click on a land cell", false, "error"); + if (cells.h[center] < 20) + return tip("You cannot place province into the water. Please click on a land cell", false, "error"); const oldProvince = cells.province[center]; if (oldProvince && provinces[oldProvince].center === center) return tip("The cell is already a center of a different province. Select other cell", false, "error"); const state = cells.state[center]; - if (!state) return tip("You cannot create a province in neutral lands. Please assign this land to a state first", false, "error"); + if (!state) + return tip( + "You cannot create a province in neutral lands. Please assign this land to a state first", + false, + "error" + ); if (d3.event.shiftKey === false) exitAddProvinceMode(); @@ -1016,7 +1064,10 @@ function editProvinces() { function downloadProvincesData() { const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; - let data = "Id,Province,Full Name,Form,State,Color,Capital,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers + let data = + "Id,Province,Full Name,Form,State,Color,Capital,Area " + + unit + + ",Total Population,Rural Population,Urban Population\n"; // headers body.querySelectorAll(":scope > div").forEach(function (el) { const key = parseInt(el.dataset.id); diff --git a/modules/ui/tools.js b/modules/ui/tools.js index af04f4f2..e1d554da 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -74,7 +74,7 @@ toolsContent.addEventListener("click", function (event) { }); function processFeatureRegeneration(event, button) { - if (button === "regenerateStateLabels") BurgsAndStates.drawStateLabels(); + if (button === "regenerateStateLabels") drawStateLabels(); else if (button === "regenerateReliefIcons") { ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief(); @@ -154,7 +154,7 @@ function regenerateStates() { layerIsOn("toggleBorders") ? drawBorders() : toggleBorders(); if (layerIsOn("toggleProvinces")) drawProvinces(); - BurgsAndStates.drawStateLabels(); + drawStateLabels(); Military.generate(); if (layerIsOn("toggleEmblems")) drawEmblems(); @@ -570,9 +570,8 @@ function addLabelOnClick() { .attr("data-size", 18) .attr("filter", null); - const example = group.append("text").attr("x", 0).attr("x", 0).text(name); + const example = group.append("text").attr("x", 0).attr("y", 0).text(name); const width = example.node().getBBox().width; - const x = width / -2; // x offset; example.remove(); group.classed("hidden", false); @@ -584,7 +583,7 @@ function addLabelOnClick() { .attr("startOffset", "50%") .attr("font-size", "100%") .append("tspan") - .attr("x", x) + .attr("x", 0) .text(name); defs @@ -836,7 +835,7 @@ function addMarkerOnClick() { const marker = Markers.add({...baseMarker, x, y, cell}); if (selectedConfig && selectedConfig.add) { - selectedConfig.add("marker"+marker.i, cell); + selectedConfig.add("marker" + marker.i, cell); } const markersElement = document.getElementById("markers"); diff --git a/versioning.js b/versioning.js index ab4fb4e1..55be68cb 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.91.05"; // generator version, update each time +const version = "1.92.00"; // generator version, update each time { document.title += " v" + version; @@ -28,6 +28,7 @@ const version = "1.91.05"; // generator version, update each time

Join our Discord server and Reddit community to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.

From 95503ebf38aeb4d8b6ef3415d8308262c3d419e6 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 11 Aug 2023 19:03:21 +0400 Subject: [PATCH 014/244] hotfix: filename conflict --- index.html | 2 +- modules/renderers/{drawStatelabels.js => state-labels.js} | 0 versioning.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename modules/renderers/{drawStatelabels.js => state-labels.js} (100%) diff --git a/index.html b/index.html index 19dc0d0a..36778d12 100644 --- a/index.html +++ b/index.html @@ -7946,7 +7946,7 @@ - + diff --git a/modules/renderers/drawStatelabels.js b/modules/renderers/state-labels.js similarity index 100% rename from modules/renderers/drawStatelabels.js rename to modules/renderers/state-labels.js diff --git a/versioning.js b/versioning.js index 55be68cb..2ae9c5d2 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.92.00"; // generator version, update each time +const version = "1.92.01"; // generator version, update each time { document.title += " v" + version; From 454fc9ca3efd73b6fa3fb2ee09c6cfea317a69af Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 11 Aug 2023 19:09:00 +0400 Subject: [PATCH 015/244] fix: remove texture hiding style --- index.html | 2 +- modules/dynamic/auto-update.js | 5 +++-- modules/io/load.js | 2 +- versioning.js | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 36778d12..9ab2fc63 100644 --- a/index.html +++ b/index.html @@ -8007,7 +8007,7 @@ - + diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index fcd12273..d3b8607f 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -640,8 +640,6 @@ export function resolveVersionConflicts(version) { if (version < 1.91) { // from v1.90.02 texture image is always there if (!texture.selectAll("*").size()) { - texture.style("display", "none"); - texture .append("image") .attr("id", "textureImage") @@ -704,5 +702,8 @@ export function resolveVersionConflicts(version) { labels.selectAll("tspan").each(function () { this.setAttribute("x", 0); }); + + // leftover from v1.90.02 + texture.style("display", null); } } diff --git a/modules/io/load.js b/modules/io/load.js index 7b39fd4e..1f8366d2 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -435,7 +435,7 @@ async function parseLoadedData(data) { { // dynamically import and run auto-udpdate script const versionNumber = parseFloat(params[0]); - const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.00"); + const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.02"); resolveVersionConflicts(versionNumber); } diff --git a/versioning.js b/versioning.js index 2ae9c5d2..ed883322 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.92.01"; // generator version, update each time +const version = "1.92.02"; // generator version, update each time { document.title += " v" + version; From c3a385b2c90b153f058dc311e4085458a5f870c0 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Fri, 11 Aug 2023 19:16:30 +0400 Subject: [PATCH 016/244] hotfix: texture on version update --- modules/dynamic/auto-update.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index d3b8607f..2319219c 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -705,5 +705,16 @@ export function resolveVersionConflicts(version) { // leftover from v1.90.02 texture.style("display", null); + const textureImage = texture.select("#textureImage"); + if (textureImage) { + const xlink = textureImage.attr("xlink:href"); + const href = textureImage.attr("href"); + const src = xlink || href; + + if (src) { + textureImage.attr("src", src); + textureImage.attr("xlink:href", null); + } + } } } From 20c46e181a9f5c8feddf0bb779d83c35d5769881 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 14 Aug 2023 14:39:11 +0400 Subject: [PATCH 017/244] fix: check for selection size --- index.html | 2 +- modules/dynamic/auto-update.js | 2 +- modules/io/load.js | 2 +- versioning.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 9ab2fc63..090b9078 100644 --- a/index.html +++ b/index.html @@ -8007,7 +8007,7 @@ - + diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index 2319219c..47dc5aa2 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -706,7 +706,7 @@ export function resolveVersionConflicts(version) { // leftover from v1.90.02 texture.style("display", null); const textureImage = texture.select("#textureImage"); - if (textureImage) { + if (textureImage.size()) { const xlink = textureImage.attr("xlink:href"); const href = textureImage.attr("href"); const src = xlink || href; diff --git a/modules/io/load.js b/modules/io/load.js index 1f8366d2..328f56a0 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -435,7 +435,7 @@ async function parseLoadedData(data) { { // dynamically import and run auto-udpdate script const versionNumber = parseFloat(params[0]); - const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.02"); + const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.03"); resolveVersionConflicts(versionNumber); } diff --git a/versioning.js b/versioning.js index ed883322..a384b243 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.92.02"; // generator version, update each time +const version = "1.92.03"; // generator version, update each time { document.title += " v" + version; From 1da3f9d3ad55f634516e4a11982765dd1f074ee5 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 14 Aug 2023 15:24:59 +0400 Subject: [PATCH 018/244] fix: re-add textureImage if it has no id --- index.html | 2 +- modules/dynamic/auto-update.js | 4 +++- modules/io/load.js | 2 +- versioning.js | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 090b9078..8c2e5da1 100644 --- a/index.html +++ b/index.html @@ -8007,7 +8007,7 @@ - + diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index 47dc5aa2..588a8f69 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -639,7 +639,9 @@ export function resolveVersionConflicts(version) { if (version < 1.91) { // from v1.90.02 texture image is always there - if (!texture.selectAll("*").size()) { + if (!texture.select("#textureImage").size()) { + // cleanup old texture if it has no id and add new one + texture.selectAll("*").remove(); texture .append("image") .attr("id", "textureImage") diff --git a/modules/io/load.js b/modules/io/load.js index 328f56a0..1a2fa03c 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -435,7 +435,7 @@ async function parseLoadedData(data) { { // dynamically import and run auto-udpdate script const versionNumber = parseFloat(params[0]); - const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.03"); + const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.04"); resolveVersionConflicts(versionNumber); } diff --git a/versioning.js b/versioning.js index a384b243..6c8d1c94 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.92.03"; // generator version, update each time +const version = "1.92.04"; // generator version, update each time { document.title += " v" + version; From 5fba7d60f4e0cca91aee1d86883e31e74abb5fd3 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 15 Aug 2023 12:02:39 +0400 Subject: [PATCH 019/244] fix: check if coa is defined on auto-update --- index.html | 2 +- modules/dynamic/auto-update.js | 6 +++--- modules/io/load.js | 2 +- versioning.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 8c2e5da1..373bb7b0 100644 --- a/index.html +++ b/index.html @@ -8007,7 +8007,7 @@ - + diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js index 588a8f69..44f84283 100644 --- a/modules/dynamic/auto-update.js +++ b/modules/dynamic/auto-update.js @@ -678,21 +678,21 @@ export function resolveVersionConflicts(version) { // from 1.91.00 coaSize is moved to coa object pack.states.forEach(state => { - if (state.coaSize) { + if (state.coaSize && state.coa) { state.coa.size = state.coaSize; delete state.coaSize; } }); pack.provinces.forEach(province => { - if (province.coaSize) { + if (province.coaSize && province.coa) { province.coa.size = province.coaSize; delete province.coaSize; } }); pack.burgs.forEach(burg => { - if (burg.coaSize) { + if (burg.coaSize && burg.coa) { burg.coa.size = burg.coaSize; delete burg.coaSize; } diff --git a/modules/io/load.js b/modules/io/load.js index 1a2fa03c..33d7eea4 100644 --- a/modules/io/load.js +++ b/modules/io/load.js @@ -435,7 +435,7 @@ async function parseLoadedData(data) { { // dynamically import and run auto-udpdate script const versionNumber = parseFloat(params[0]); - const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.04"); + const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.92.05"); resolveVersionConflicts(versionNumber); } diff --git a/versioning.js b/versioning.js index 6c8d1c94..67c0b12c 100644 --- a/versioning.js +++ b/versioning.js @@ -1,7 +1,7 @@ "use strict"; // version and caching control -const version = "1.92.04"; // generator version, update each time +const version = "1.92.05"; // generator version, update each time { document.title += " v" + version; From 26f48a48fd816e5534308d36e78e64949c9b47f0 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 15 Aug 2023 16:50:28 +0400 Subject: [PATCH 020/244] Compress save file (#986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding gzip compression for improving storage use and backward compatibility. (#984) * Basic gzip an gunzip on load and save. * refactor file save type to .gz and update the data in ui. --------- Co-authored-by: Azgaar * refactor: cleanup, change wording * feat: streamline saving options * fix: fixes --------- Co-authored-by: Efruz Yıldırır <30903352+yldrefruz@users.noreply.github.com> --- index.html | 70 +++++++++++-------- main.js | 30 +++++---- modules/dynamic/auto-update.js | 2 +- modules/io/load.js | 43 +++++++++--- modules/io/save.js | 120 +++++++++++++++++++++------------ modules/ui/heightmap-editor.js | 2 +- modules/ui/hotkeys.js | 6 +- modules/ui/options.js | 6 +- utils/polyfills.js | 16 +++++ versioning.js | 6 +- 10 files changed, 197 insertions(+), 104 deletions(-) diff --git a/index.html b/index.html index 373bb7b0..7a1f782c 100644 --- a/index.html +++ b/index.html @@ -1775,6 +1775,18 @@ + + + Onload behavior + + + + + + Speaker voice @@ -2329,8 +2341,8 @@
- - + +
@@ -5884,37 +5896,42 @@ @@ -5919,7 +5919,7 @@

- Maps are saved in .gz format, that can be loaded back via the Load in menu. There is no way to + Maps are saved in .map format, that can be loaded back via the Load in menu. There is no way to restore the progress if file is lost. Please keep old save files on your machine or cloud storage as backups.

@@ -5927,12 +5927,12 @@ + +
+ Presets: + + + + +