From 79584ffface24b6a951fb1d07e6fd22848cad5f8 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 14 Feb 2021 19:22:54 +0300 Subject: [PATCH 01/18] v1.5.31 - let Armoria know the origin --- modules/ui/emblems-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui/emblems-editor.js b/modules/ui/emblems-editor.js index 6a8a945b..f6806449 100644 --- a/modules/ui/emblems-editor.js +++ b/modules/ui/emblems-editor.js @@ -180,7 +180,7 @@ function editEmblem(type, id, el) { function openInArmoria() { const coa = el.coa && el.coa !== "custom" ? el.coa : {t1: "sable"}; const json = JSON.stringify(coa).replaceAll("#", "%23"); - const url = `http://azgaar.github.io/Armoria/?coa=${json}`; + const url = `http://azgaar.github.io/Armoria/?coa=${json}&from=FMG`; openURL(url); } From 67ec8381600ec7ca2cafda5535878069de40d2eb Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 14 Feb 2021 19:59:37 +0300 Subject: [PATCH 02/18] v1.5.32 - resolve conflicts --- main.js | 32 +- modules/burgs-and-states.js | 2 +- modules/river-generator.js | 592 ++++++++++++++++++------------- modules/ui/heightmap-editor.js | 7 +- modules/ui/tools.js | 5 - modules/ui/world-configurator.js | 1 - modules/voronoi.js | 187 ++++++---- 7 files changed, 465 insertions(+), 361 deletions(-) diff --git a/main.js b/main.js index 85edae18..4e05d8d4 100644 --- a/main.js +++ b/main.js @@ -545,7 +545,6 @@ function generate() { reGraph(); drawCoastline(); - elevateLakes(); Rivers.generate(); defineBiomes(); @@ -626,7 +625,7 @@ function calculateVoronoi(graph, points) { TIME && console.timeEnd("calculateDelaunay"); TIME && console.time("calculateVoronoi"); - const voronoi = Voronoi(delaunay, allPoints, n); + const voronoi = new Voronoi(delaunay, allPoints, n); graph.cells = voronoi.cells; graph.cells.i = n < 65535 ? Uint16Array.from(d3.range(n)) : Uint32Array.from(d3.range(n)); // array of indexes graph.vertices = voronoi.vertices; @@ -1137,22 +1136,6 @@ function reMarkFeatures() { TIME && console.timeEnd("reMarkFeatures"); } -// temporary elevate some lakes to resolve depressions and flux the water to form an open (exorheic) lake -function elevateLakes() { - if (templateInput.value === "Atoll") return; // no need for Atolls - TIME && console.time('elevateLakes'); - const cells = pack.cells, features = pack.features; - const maxCells = cells.i.length / 100; // size limit; let big lakes be closed (endorheic) - cells.i.forEach(i => { - if (cells.h[i] >= 20) return; - if (features[cells.f[i]].group !== "freshwater" || features[cells.f[i]].cells > maxCells) return; - cells.h[i] = 20; - //debug.append("circle").attr("cx", cells.p[i][0]).attr("cy", cells.p[i][1]).attr("r", .5).attr("fill", "blue"); - }); - - TIME && console.timeEnd('elevateLakes'); -} - // assign biome id for each cell function defineBiomes() { TIME && console.time("defineBiomes"); @@ -1160,7 +1143,6 @@ function defineBiomes() { cells.biome = new Uint8Array(cells.i.length); // biomes array for (const i of cells.i) { - if (f[cells.f[i]].group === "freshwater") cells.h[i] = 19; // de-elevate lakes; here to save some resources const t = temp[cells.g[i]]; // cell temperature const h = cells.h[i]; // cell height const m = h < 20 ? 0 : calculateMoisture(i); // cell moisture @@ -1718,11 +1700,7 @@ function addZones(number = 1) { function showStatistics() { const template = templateInput.value; const templateRandom = locked("template") ? "" : "(random)"; - - mapId = Date.now(); // unique map id is it's creation date number - mapHistory.push({seed, width:graphWidth, height:graphHeight, template, created:mapId}); - console.log(` - Seed: ${seed} + const stats = ` Seed: ${seed} Canvas size: ${graphWidth}x${graphHeight} Template: ${template} ${templateRandom} Points: ${grid.points.length} @@ -1733,7 +1711,11 @@ function showStatistics() { Burgs: ${pack.burgs.length-1} Religions: ${pack.religions.length-1} Culture set: ${culturesSet.selectedOptions[0].innerText} - Cultures: ${pack.cultures.length-1}`); + Cultures: ${pack.cultures.length-1}`; + + mapId = Date.now(); // unique map id is it's creation date number + mapHistory.push({seed, width:graphWidth, height:graphHeight, template, created:mapId}); + INFO && console.log(stats); } const regenerateMap = debounce(function() { diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index 6120c27a..4270870c 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -419,7 +419,7 @@ 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 = Voronoi(delaunay, points, points.length); + 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]); diff --git a/modules/river-generator.js b/modules/river-generator.js index e0c5cdbe..2e6c5022 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -1,296 +1,376 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Rivers = factory()); + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.Rivers = factory()); }(this, (function () {'use strict'; - const generate = function(changeHeights = true) { - TIME && console.time('generateRivers'); - Math.random = aleaPRNG(seed); - const cells = pack.cells, p = cells.p, features = pack.features; +const generate = function(changeHeights = true) { + TIME && console.time('generateRivers'); + Math.random = aleaPRNG(seed); + const cells = pack.cells, p = cells.p, features = pack.features; - // build distance field in cells from water (cells.t) - void function markupLand() { - const q = t => cells.i.filter(i => cells.t[i] === t); - for (let t = 2, queue = q(t); queue.length; t++, queue = q(t)) { - queue.forEach(i => cells.c[i].forEach(c => { - if (!cells.t[c]) cells.t[c] = t+1; - })); + // build distance field in cells from water (cells.t) + void function markupLand() { + const q = t => cells.i.filter(i => cells.t[i] === t); + for (let t = 2, queue = q(t); queue.length; t++, queue = q(t)) { + queue.forEach(i => cells.c[i].forEach(c => { + if (!cells.t[c]) cells.t[c] = t+1; + })); + } + }() + + // height with added t value to make map less depressed + const h = Array.from(cells.h) + .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100) + .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000); + + resolveDepressions(h); + features.forEach(f => {delete f.river; delete f.flux; delete f.inlets}); + + const riversData = []; // rivers data + cells.fl = new Uint16Array(cells.i.length); // water flux array + cells.r = new Uint16Array(cells.i.length); // rivers array + cells.conf = new Uint8Array(cells.i.length); // confluences array + let riverNext = 1; // first river id is 1, not 0 + + void function drainWater() { + const land = cells.i.filter(i => h[i] >= 20).sort((a,b) => h[b] - h[a]); + const outlets = new Uint32Array(features.length); + // enumerate lake outlet positions + features.filter(f => f.type === "lake" && (f.group === "freshwater" || f.group === "frozen")).forEach(l => { + let outlet = 0; + if (l.shoreline) { + outlet = l.shoreline[d3.scan(l.shoreline, (a,b) => h[a] - h[b])]; + } else { // in case it got missed or deleted + WARN && console.warn('Re-scanning shoreline of a lake'); + const shallows = cells.i.filter(j => cells.t[j] === -1 && cells.f[j] === l.i); + let shoreline = []; + shallows.map(w => cells.c[w]).forEach(cList => cList.forEach(s => shoreline.push(s))); + outlet = shoreline[d3.scan(shoreline, (a,b) => h[a] - h[b])]; } - }() + outlets[l.i] = outlet; + delete l.shoreline // cleanup temp data once used + }); - // height with added t value to make map less depressed - const h = Array.from(cells.h) - .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100) - .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000); - - resolveDepressions(h); - features.forEach(f => {delete f.river; delete f.flux;}); - - const riversData = []; // rivers data - cells.fl = new Uint16Array(cells.i.length); // water flux array - cells.r = new Uint16Array(cells.i.length); // rivers array - cells.conf = new Uint8Array(cells.i.length); // confluences array - let riverNext = 1; // first river id is 1, not 0 - - void function drainWater() { - const land = cells.i.filter(i => h[i] >= 20).sort((a,b) => h[b] - h[a]); - land.forEach(function(i) { - cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation - const x = p[i][0], y = p[i][1]; - - // near-border cell: pour out of the screen - if (cells.b[i]) { - if (cells.r[i]) { - const to = []; - const min = Math.min(y, graphHeight - y, x, graphWidth - x); - if (min === y) {to[0] = x; to[1] = 0;} else - if (min === graphHeight - y) {to[0] = x; to[1] = graphHeight;} else - if (min === x) {to[0] = 0; to[1] = y;} else - if (min === graphWidth - x) {to[0] = graphWidth; to[1] = y;} - riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1]}); - } - return; - } - - //const min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell - let min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell - - // allow only one river can flow through a lake - const cf = features[cells.f[i]]; // current cell feature - if (cf.river && cf.river !== cells.r[i]) { - cells.fl[i] = 0; - } - - if (cells.fl[i] < 30) { - if (h[min] >= 20) cells.fl[min] += cells.fl[i]; - return; // flux is too small to operate as river - } - - // Proclaim a new river - if (!cells.r[i]) { - cells.r[i] = riverNext; - riversData.push({river: riverNext, cell: i, x, y}); - riverNext++; - } - - if (cells.r[min]) { // downhill cell already has river assigned - if (cells.fl[min] < cells.fl[i]) { - cells.conf[min] = cells.fl[min]; // mark confluence - if (h[min] >= 20) riversData.find(r => r.river === cells.r[min]).parent = cells.r[i]; // min river is a tributary of current river - cells.r[min] = cells.r[i]; // re-assign river if downhill part has less flux - } else { - cells.conf[min] += cells.fl[i]; // mark confluence - if (h[min] >= 20) riversData.find(r => r.river === cells.r[i]).parent = cells.r[min]; // current river is a tributary of min river - } - } else cells.r[min] = cells.r[i]; // assign the river to the downhill cell - - const nx = p[min][0], ny = p[min][1]; - if (h[min] < 20) { - // pour water to the sea haven - riversData.push({river: cells.r[i], cell: cells.haven[i], x: nx, y: ny}); + const flowDown = function(min, mFlux, iFlux, ri, i = 0){ + if (cells.r[min]) { // downhill cell already has river assigned + if (mFlux < iFlux) { + cells.conf[min] = cells.fl[min]; // mark confluence + if (h[min] >= 20) riversData.find(r => r.river === cells.r[min]).parent = ri; // min river is a tributary of current river + cells.r[min] = ri; // re-assign river if downhill part has less flux } else { - const mf = features[cells.f[min]]; // feature of min cell - if (mf.type === "lake") { - if (!mf.river || cells.fl[i] > mf.flux) { - mf.river = cells.r[i]; // pour water to temporaly elevated lake - mf.flux = cells.fl[i]; // entering flux + cells.conf[min] += iFlux; // mark confluence + if (h[min] >= 20) riversData.find(r => r.river === ri).parent = cells.r[min]; // current river is a tributary of min river + } + } else cells.r[min] = ri; // assign the river to the downhill cell + + if (h[min] < 20) { + // pour water to the sea haven + const oh = i ? cells.haven[i] : min; + riversData.push({river: ri, cell: oh, x: p[min][0], y: p[min][1]}); + const mf = features[cells.f[min]]; // feature of min cell + if (mf.type === "lake") { + if (!mf.river || iFlux > mf.flux) { + mf.river = ri; // pour water to temporaly elevated lake + mf.flux = iFlux; // entering flux + } + mf.totalFlux += iFlux; + if (mf.inlets) { + mf.inlets.push(ri); + } else { + mf.inlets = [ri]; + } + } + } else { + cells.fl[min] += iFlux; // propagate flux + riversData.push({river: ri, cell: min, x: p[min][0], y: p[min][1]}); // add next River segment + } + } + + land.forEach(function(i) { + cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation + const x = p[i][0], y = p[i][1]; + + // lake outlets draw from lake + let n = -1, out2 = 0; + while (outlets.includes(i, n+1)) { + n = outlets.indexOf(i, n+1); + const l = features[n]; + if ( ! l ) {continue;} + const j = cells.haven[i]; + // allow chain lakes to retain identity + if(cells.r[j] !== l.river) { + let touch = false; + for (const c of cells.c[j]){ + if (cells.r[c] === l.river) { + touch = true; + break; } } - cells.fl[min] += cells.fl[i]; // propagate flux - riversData.push({river: cells.r[i], cell: min, x: nx, y: ny}); // add next River segment - } - - }); - }() - - void function defineRivers() { - pack.rivers = []; // rivers data - const riverPaths = []; // temporary data for all rivers - - for (let r = 1; r <= riverNext; r++) { - const riverSegments = riversData.filter(d => d.river === r); - - if (riverSegments.length > 2) { - const riverEnhanced = addMeandring(riverSegments); - const width = rn(.8 + Math.random() * .4, 1); // river width modifier - const increment = rn(.8 + Math.random() * .6, 1); // river bed widening modifier - const [path, length] = getPath(riverEnhanced, width, increment); - riverPaths.push([r, path, width, increment]); - const source = riverSegments[0], mouth = riverSegments[riverSegments.length-2]; - const parent = source.parent || 0; - pack.rivers.push({i:r, parent, length, source:source.cell, mouth:mouth.cell}); - } else { - // remove too short rivers - riverSegments.filter(s => cells.r[s.cell] === r).forEach(s => cells.r[s.cell] = 0); + if (touch) { + cells.r[j] = l.river; + riversData.push({river: l.river, cell: j, x: p[j][0], y: p[j][1]}); + } else { + cells.r[j] = riverNext; + riversData.push({river: riverNext, cell: j, x: p[j][0], y: p[j][1]}); + riverNext++; + } } + cells.fl[j] = l.totalFlux; // signpost river size + flowDown(i, cells.fl[i], l.totalFlux, cells.r[j]); + // prevent dropping imediately back into the lake + out2 = cells.c[i].filter(c => (h[c] >= 20 || cells.f[c] !== cells.f[j])).sort((a,b) => h[a] - h[b])[0]; // downhill cell not in the source lake + // assign all to outlet basin + if (l.inlets) l.inlets.forEach(fork => riversData.find(r => r.river === fork).parent = cells.r[j]); } - const html = riverPaths.map(r =>``).join(""); - rivers.html(html); - }() - - // apply change heights as basic one - if (changeHeights) cells.h = Uint8Array.from(h); - - TIME && console.timeEnd('generateRivers'); - } - - // depression filling algorithm (for a correct water flux modeling) - const resolveDepressions = function(h) { - const cells = pack.cells; - const land = cells.i.filter(i => h[i] >= 20 && h[i] < 100 && !cells.b[i]); // exclude near-border cells - land.sort((a,b) => h[b] - h[a]); // highest cells go first - let depressed = false; - - for (let l = 0, depression = Infinity; depression && l < 100; l++) { - depression = 0; - for (const i of land) { - const minHeight = d3.min(cells.c[i].map(c => h[c])); - if (minHeight === 100) continue; // already max height - if (h[i] <= minHeight) { - h[i] = Math.min(minHeight + 1, 100); - depression++; - depressed = true; + // near-border cell: pour out of the screen + if (cells.b[i]) { + if (cells.r[i]) { + const to = []; + const min = Math.min(y, graphHeight - y, x, graphWidth - x); + if (min === y) {to[0] = x; to[1] = 0;} else + if (min === graphHeight - y) {to[0] = x; to[1] = graphHeight;} else + if (min === x) {to[0] = 0; to[1] = y;} else + if (min === graphWidth - x) {to[0] = graphWidth; to[1] = y;} + riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1]}); } + return; + } + + const min = out2 ? out2 : cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell + + if (cells.fl[i] < 30) { + if (h[min] >= 20) cells.fl[min] += cells.fl[i]; + return; // flux is too small to operate as river + } + + // Proclaim a new river + if (!cells.r[i]) { + cells.r[i] = riverNext; + riversData.push({river: riverNext, cell: i, x, y}); + riverNext++; + } + + flowDown(min, cells.fl[min], cells.fl[i], cells.r[i], i); + + }); + }() + + void function defineRivers() { + pack.rivers = []; // rivers data + const riverPaths = []; // temporary data for all rivers + + for (let r = 1; r <= riverNext; r++) { + const riverSegments = riversData.filter(d => d.river === r); + + if (riverSegments.length > 2) { + const source = riverSegments[0], mouth = riverSegments[riverSegments.length-2]; + const riverEnhanced = addMeandring(riverSegments); + let width = rn(.8 + Math.random() * .4, 1); // river width modifier [.2, 10] + let increment = rn(.8 + Math.random() * .6, 1); // river bed widening modifier [.01, 3] + const [path, length] = getPath(riverEnhanced, width, increment, cells.h[source.cell] >= 20 ? .1 : .6); + riverPaths.push([r, path, width, increment]); + const parent = source.parent || 0; + pack.rivers.push({i:r, parent, length, source:source.cell, mouth:mouth.cell}); + } else { + // remove too short rivers + riverSegments.filter(s => cells.r[s.cell] === r).forEach(s => cells.r[s.cell] = 0); } } - return depressed; - } + // drawRivers + rivers.selectAll("path").remove(); + rivers.selectAll("path").data(riverPaths).enter() + .append("path").attr("d", d => d[1]).attr("id", d => "river"+d[0]) + .attr("data-width", d => d[2]).attr("data-increment", d => d[3]); + }() - // add more river points on 1/3 and 2/3 of length - const addMeandring = function(segments, rndFactor = 0.3) { - const riverEnhanced = []; // to store enhanced segments - let side = 1; // to control meandring direction + // apply change heights as basic one + if (changeHeights) cells.h = Uint8Array.from(h); - for (let s = 0; s < segments.length; s++) { - const sX = segments[s].x, sY = segments[s].y; // segment start coordinates - const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence - riverEnhanced.push([sX, sY, c]); + TIME && console.timeEnd('generateRivers'); +} - if (s+1 === segments.length) break; // do not enhance last segment - - const eX = segments[s+1].x, eY = segments[s+1].y; // segment end coordinates - const angle = Math.atan2(eY - sY, eX - sX); - const sin = Math.sin(angle), cos = Math.cos(angle); - const serpentine = 1 / (s + 1) + 0.3; - const meandr = serpentine + Math.random() * rndFactor; - if (P(.5)) side *= -1; // change meandring direction in 50% - const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2; - // if dist2 is big or river is small add extra points at 1/3 and 2/3 of segment - if (dist2 > 64 || (dist2 > 16 && segments.length < 6)) { - const p1x = (sX * 2 + eX) / 3 + side * -sin * meandr; - const p1y = (sY * 2 + eY) / 3 + side * cos * meandr; - if (P(.2)) side *= -1; // change 2nd extra point meandring direction in 20% - const p2x = (sX + eX * 2) / 3 + side * sin * meandr; - const p2y = (sY + eY * 2) / 3 + side * cos * meandr; - riverEnhanced.push([p1x, p1y], [p2x, p2y]); - // if dist is medium or river is small add 1 extra middlepoint - } else if (dist2 > 16 || segments.length < 6) { - const p1x = (sX + eX) / 2 + side * -sin * meandr; - const p1y = (sY + eY) / 2 + side * cos * meandr; - riverEnhanced.push([p1x, p1y]); +// depression filling algorithm (for a correct water flux modeling) +const resolveDepressions = function(h) { + const cells = pack.cells; + const land = cells.i.filter(i => h[i] >= 20 && h[i] < 100 && !cells.b[i]); // exclude near-border cells + const lakes = pack.features.filter(f => f.type === "lake" && (f.group === "freshwater" || f.group === "frozen")); // to keep lakes flat + lakes.forEach(l => { + l.shoreline = []; + l.height = 21; + l.totalFlux = grid.cells.prec[cells.g[l.firstCell]]; + }); + for (let i of land.filter(i => cells.t[i] === 1)) { // select shoreline cells + cells.c[i].map(c => pack.features[cells.f[c]]).forEach(cf => { + if (lakes.includes(cf) && !cf.shoreline.includes(i)) { + cf.shoreline.push(i); } + }) + } + land.sort((a,b) => h[b] - h[a]); // highest cells go first + let depressed = false; + for (let l = 0, depression = Infinity; depression && l < 100; l++) { + depression = 0; + for (const l of lakes) { + const minHeight = d3.min(l.shoreline.map(s => h[s])); + if (minHeight === 100) continue; // already max height + if (l.height <= minHeight) { + l.height = Math.min(minHeight + 1, 100); + depression++; + depressed = true; + } + } + for (const i of land) { + const minHeight = d3.min(cells.c[i].map(c => cells.t[c] > 0 ? h[c] : + pack.features[cells.f[c]].height || h[c] // NB undefined is falsy (a || b is short for a ? a : b) + )); + if (minHeight === 100) continue; // already max height + if (h[i] <= minHeight) { + h[i] = Math.min(minHeight + 1, 100); + depression++; + depressed = true; + } } - return riverEnhanced; } - const getPath = function(points, width = 1, increment = 1) { - let offset, extraOffset = .1; // starting river width (to make river source visible) - const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i-1][0], v[1] - p[i-1][1]) : 0), 0); // summ of segments length - const widening = rn((1000 + (riverLength * 30)) * increment); - const riverPointsLeft = [], riverPointsRight = []; // store points on both sides to build a valid polygon - const last = points.length - 1; - const factor = riverLength / points.length; + return depressed; +} - // first point - let x = points[0][0], y = points[0][1], c; - let angle = Math.atan2(y - points[1][1], x - points[1][0]); - let sin = Math.sin(angle), cos = Math.cos(angle); - let xLeft = x + -sin * extraOffset, yLeft = y + cos * extraOffset; - riverPointsLeft.push([xLeft, yLeft]); - let xRight = x + sin * extraOffset, yRight = y + -cos * extraOffset; - riverPointsRight.unshift([xRight, yRight]); +// add more river points on 1/3 and 2/3 of length +const addMeandring = function(segments, rndFactor = 0.3) { + const riverEnhanced = []; // to store enhanced segments + let side = 1; // to control meandring direction - // middle points - for (let p = 1; p < last; p++) { - x = points[p][0], y = points[p][1], c = points[p][2] || 0; - const xPrev = points[p-1][0], yPrev = points[p - 1][1]; - const xNext = points[p+1][0], yNext = points[p + 1][1]; - angle = Math.atan2(yPrev - yNext, xPrev - xNext); - sin = Math.sin(angle), cos = Math.cos(angle); - offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2 * width) + extraOffset; - const confOffset = Math.atan(c * 5 / widening); - extraOffset += confOffset; - xLeft = x + -sin * offset, yLeft = y + cos * (offset + confOffset); - riverPointsLeft.push([xLeft, yLeft]); - xRight = x + sin * offset, yRight = y + -cos * offset; - riverPointsRight.unshift([xRight, yRight]); + for (let s = 0; s < segments.length; s++) { + const sX = segments[s].x, sY = segments[s].y; // segment start coordinates + const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence + riverEnhanced.push([sX, sY, c]); + + if (s+1 === segments.length) break; // do not enhance last segment + + const eX = segments[s+1].x, eY = segments[s+1].y; // segment end coordinates + const angle = Math.atan2(eY - sY, eX - sX); + const sin = Math.sin(angle), cos = Math.cos(angle); + const serpentine = 1 / (s + 1) + 0.3; + const meandr = serpentine + Math.random() * rndFactor; + if (P(.5)) side *= -1; // change meandring direction in 50% + const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2; + // if dist2 is big or river is small add extra points at 1/3 and 2/3 of segment + if (dist2 > 64 || (dist2 > 16 && segments.length < 6)) { + const p1x = (sX * 2 + eX) / 3 + side * -sin * meandr; + const p1y = (sY * 2 + eY) / 3 + side * cos * meandr; + if (P(.2)) side *= -1; // change 2nd extra point meandring direction in 20% + const p2x = (sX + eX * 2) / 3 + side * sin * meandr; + const p2y = (sY + eY * 2) / 3 + side * cos * meandr; + riverEnhanced.push([p1x, p1y], [p2x, p2y]); + // if dist is medium or river is small add 1 extra middlepoint + } else if (dist2 > 16 || segments.length < 6) { + const p1x = (sX + eX) / 2 + side * -sin * meandr; + const p1y = (sY + eY) / 2 + side * cos * meandr; + riverEnhanced.push([p1x, p1y]); } - // end point - x = points[last][0], y = points[last][1], c = points[last][2]; - if (c) extraOffset += Math.atan(c * 10 / widening); // add extra width on river confluence - angle = Math.atan2(points[last-1][1] - y, points[last-1][0] - x); + } + return riverEnhanced; +} + +const getPath = function(points, width = 1, increment = 1, starting = .1) { + let offset, extraOffset = starting; // starting river width (to make river source visible) + const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i-1][0], v[1] - p[i-1][1]) : 0), 0); // summ of segments length + const widening = rn((1000 + (riverLength * 30)) * increment); + const riverPointsLeft = [], riverPointsRight = []; // store points on both sides to build a valid polygon + const last = points.length - 1; + const factor = riverLength / points.length; + + // first point + let x = points[0][0], y = points[0][1], c; + let angle = Math.atan2(y - points[1][1], x - points[1][0]); + let sin = Math.sin(angle), cos = Math.cos(angle); + let xLeft = x + -sin * extraOffset, yLeft = y + cos * extraOffset; + riverPointsLeft.push([xLeft, yLeft]); + let xRight = x + sin * extraOffset, yRight = y + -cos * extraOffset; + riverPointsRight.unshift([xRight, yRight]); + + // middle points + for (let p = 1; p < last; p++) { + x = points[p][0], y = points[p][1], c = points[p][2] || 0; + const xPrev = points[p-1][0], yPrev = points[p - 1][1]; + const xNext = points[p+1][0], yNext = points[p + 1][1]; + angle = Math.atan2(yPrev - yNext, xPrev - xNext); sin = Math.sin(angle), cos = Math.cos(angle); - xLeft = x + -sin * offset, yLeft = y + cos * offset; + offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2 * width) + extraOffset; + const confOffset = Math.atan(c * 5 / widening); + extraOffset += confOffset; + xLeft = x + -sin * offset, yLeft = y + cos * (offset + confOffset); riverPointsLeft.push([xLeft, yLeft]); xRight = x + sin * offset, yRight = y + -cos * offset; riverPointsRight.unshift([xRight, yRight]); - - // generate polygon path and return - lineGen.curve(d3.curveCatmullRom.alpha(0.1)); - const right = lineGen(riverPointsRight); - let left = lineGen(riverPointsLeft); - left = left.substring(left.indexOf("C")); - return [round(right + left, 2), rn(riverLength, 2)]; } - const specify = function() { - if (!pack.rivers.length) return; - Math.random = aleaPRNG(seed); - const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)]; - const smallType = {"Creek":9, "River":3, "Brook":3, "Stream":1}; // weighted small river types + // end point + x = points[last][0], y = points[last][1], c = points[last][2]; + if (c) extraOffset += Math.atan(c * 10 / widening); // add extra width on river confluence + angle = Math.atan2(points[last-1][1] - y, points[last-1][0] - x); + sin = Math.sin(angle), cos = Math.cos(angle); + xLeft = x + -sin * offset, yLeft = y + cos * offset; + riverPointsLeft.push([xLeft, yLeft]); + xRight = x + sin * offset, yRight = y + -cos * offset; + riverPointsRight.unshift([xRight, yRight]); - for (const r of pack.rivers) { - r.basin = getBasin(r.i, r.parent); - r.name = getName(r.mouth); - //debug.append("circle").attr("cx", pack.cells.p[r.mouth][0]).attr("cy", pack.cells.p[r.mouth][1]).attr("r", 2); - const small = r.length < smallLength; - r.type = r.parent && !(r.i%6) ? small ? "Branch" : "Fork" : small ? rw(smallType) : "River"; - } + // generate polygon path and return + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); + const right = lineGen(riverPointsRight); + let left = lineGen(riverPointsLeft); + left = left.substring(left.indexOf("C")); + return [round(right + left, 2), rn(riverLength, 2)]; +} + +const specify = function() { + if (!pack.rivers.length) return; + Math.random = aleaPRNG(seed); + const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)]; + const smallType = {"Creek":9, "River":3, "Brook":3, "Stream":1}; // weighted small river types + + for (const r of pack.rivers) { + r.basin = getBasin(r.i, r.parent); + r.name = getName(r.mouth); + //debug.append("circle").attr("cx", pack.cells.p[r.mouth][0]).attr("cy", pack.cells.p[r.mouth][1]).attr("r", 2); + const small = r.length < smallLength; + r.type = r.parent && !(r.i%6) ? small ? "Branch" : "Fork" : small ? rw(smallType) : "River"; } +} - const getName = function(cell) { - return Names.getCulture(pack.cells.culture[cell]); +const getName = function(cell) { + return Names.getCulture(pack.cells.culture[cell]); +} + +// remove river and all its tributaries +const remove = function(id) { + const cells = pack.cells; + const riversToRemove = pack.rivers.filter(r => r.i === id || getBasin(r.i, r.parent, id) === id).map(r => r.i); + riversToRemove.forEach(r => rivers.select("#river"+r).remove()); + cells.r.forEach((r, i) => { + if (!r || !riversToRemove.includes(r)) return; + cells.r[i] = 0; + cells.fl[i] = grid.cells.prec[cells.g[i]]; + cells.conf[i] = 0; + }); + pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i)); +} + +const getBasin = function(r, p, e) { + while (p && r !== p && r !== e) { + const parent = pack.rivers.find(r => r.i === p); + if (!parent) return r; + r = parent.i; + p = parent.parent; } + return r; +} - // remove river and all its tributaries - const remove = function(id) { - const cells = pack.cells; - const riversToRemove = pack.rivers.filter(r => r.i === id || getBasin(r.i, r.parent, id) === id).map(r => r.i); - riversToRemove.forEach(r => rivers.select("#river"+r).remove()); - cells.r.forEach((r, i) => { - if (!r || !riversToRemove.includes(r)) return; - cells.r[i] = 0; - cells.fl[i] = grid.cells.prec[cells.g[i]]; - cells.conf[i] = 0; - }); - pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i)); - } +return {generate, resolveDepressions, addMeandring, getPath, specify, getName, getBasin, remove}; - const getBasin = function(r, p, e) { - while (p && r !== p && r !== e) { - const parent = pack.rivers.find(r => r.i === p); - if (!parent) return r; - r = parent.i; - p = parent.parent; - } - return r; - } - - return {generate, resolveDepressions, addMeandring, getPath, specify, getName, getBasin, remove}; - -}))); +}))); \ No newline at end of file diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 73a8006e..34a77e1d 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -176,7 +176,6 @@ function editHeightmap() { reGraph(); drawCoastline(); - elevateLakes(); Rivers.generate(change); if (!change) { @@ -288,10 +287,7 @@ function editHeightmap() { reGraph(); drawCoastline(); - if (changeHeights.checked) { - elevateLakes(); - Rivers.generate(changeHeights.checked); - } + if (changeHeights.checked) Rivers.generate(changeHeights.checked); // assign saved pack data from grid back to pack const n = pack.cells.i.length; @@ -314,7 +310,6 @@ function editHeightmap() { for (const i of pack.cells.i) { const g = pack.cells.g[i]; - if (pack.features[pack.cells.f[i]].group === "freshwater") pack.cells.h[i] = 19; // de-elevate lakes const land = pack.cells.h[i] >= 20; // check biome diff --git a/modules/ui/tools.js b/modules/ui/tools.js index d41fbc91..5269e185 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -73,12 +73,7 @@ function processFeatureRegeneration(event, button) { } function regenerateRivers() { - elevateLakes(); Rivers.generate(); - for (const i of pack.cells.i) { - const f = pack.features[pack.cells.f[i]]; // feature - if (f.group === "freshwater") pack.cells.h[i] = 19; // de-elevate lakes - } Rivers.specify(); if (!layerIsOn("toggleRivers")) toggleRivers(); } diff --git a/modules/ui/world-configurator.js b/modules/ui/world-configurator.js index 11eef897..913e4f77 100644 --- a/modules/ui/world-configurator.js +++ b/modules/ui/world-configurator.js @@ -45,7 +45,6 @@ function editWorld() { updateGlobePosition(); calculateTemperatures(); generatePrecipitation(); - elevateLakes(); const heights = new Uint8Array(pack.cells.h); Rivers.generate(); Rivers.specify(); diff --git a/modules/voronoi.js b/modules/voronoi.js index f7b82292..f5e4b45c 100644 --- a/modules/voronoi.js +++ b/modules/voronoi.js @@ -1,82 +1,135 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Voronoi = factory()); -}(this, (function () { 'use strict'; +class Voronoi { + /** + * Creates a Voronoi diagram from the given Delaunator, a list of points, and the number of points. The Voronoi diagram is constructed using (I think) the {@link https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm |Bowyer-Watson Algorithm} + * The {@link https://github.com/mapbox/delaunator/ |Delaunator} library uses {@link https://en.wikipedia.org/wiki/Doubly_connected_edge_list |half-edges} to represent the relationship between points and triangles. + * @param {{triangles: Uint32Array, halfedges: Int32Array}} delaunay A {@link https://github.com/mapbox/delaunator/blob/master/index.js |Delaunator} instance. + * @param {[number, number][]} points A list of coordinates. + * @param {number} pointsN The number of points. + */ + constructor(delaunay, points, pointsN) { + this.delaunay = delaunay; + this.points = points; + this.pointsN = pointsN; + this.cells = { v: [], c: [], b: [] }; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell + this.vertices = { p: [], v: [], c: [] }; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells - var Voronoi = function Voronoi(delaunay, points, pointsN) { - const cells = {v: [], c: [], b: []}; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell - const vertices = {p: [], v: [], c: []}; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells + // Half-edges are the indices into the delaunator outputs: + // delaunay.triangles[e] gives the point ID where the half-edge starts + // delaunay.triangles[e] returns either the opposite half-edge in the adjacent triangle, or -1 if there's not an adjacent triangle. + for (let e = 0; e < this.delaunay.triangles.length; e++) { - for (let e=0; e < delaunay.triangles.length; e++) { - - const p = delaunay.triangles[nextHalfedge(e)]; - if (p < pointsN && !cells.c[p]) { - const edges = edgesAroundPoint(e); - cells.v[p] = edges.map(e => triangleOfEdge(e)); // cell: adjacent vertex - cells.c[p] = edges.map(e => delaunay.triangles[e]).filter(c => c < pointsN); // cell: adjacent valid cells - cells.b[p] = edges.length > cells.c[p].length ? 1 : 0; // cell: is border + const p = this.delaunay.triangles[this.nextHalfedge(e)]; + if (p < this.pointsN && !this.cells.c[p]) { + const edges = this.edgesAroundPoint(e); + this.cells.v[p] = edges.map(e => this.triangleOfEdge(e)); // cell: adjacent vertex + this.cells.c[p] = edges.map(e => this.delaunay.triangles[e]).filter(c => c < this.pointsN); // cell: adjacent valid cells + this.cells.b[p] = edges.length > this.cells.c[p].length ? 1 : 0; // cell: is border } - const t = triangleOfEdge(e); - if (!vertices.p[t]) { - vertices.p[t] = triangleCenter(t); // vertex: coordinates - vertices.v[t] = trianglesAdjacentToTriangle(t); // vertex: adjacent vertices - vertices.c[t] = pointsOfTriangle(t); // vertex: adjacent cells + const t = this.triangleOfEdge(e); + if (!this.vertices.p[t]) { + this.vertices.p[t] = this.triangleCenter(t); // vertex: coordinates + this.vertices.v[t] = this.trianglesAdjacentToTriangle(t); // vertex: adjacent vertices + this.vertices.c[t] = this.pointsOfTriangle(t); // vertex: adjacent cells } } - - function pointsOfTriangle(t) { - return edgesOfTriangle(t).map(e => delaunay.triangles[e]); - } - - function trianglesAdjacentToTriangle(t) { - let triangles = []; - for (let e of edgesOfTriangle(t)) { - let opposite = delaunay.halfedges[e]; - triangles.push(triangleOfEdge(opposite)); - } - return triangles; - } - - function edgesAroundPoint(start) { - let result = [], incoming = start; - do { - result.push(incoming); - const outgoing = nextHalfedge(incoming); - incoming = delaunay.halfedges[outgoing]; - } while (incoming !== -1 && incoming !== start && result.length < 20); - return result; - } - - function triangleCenter(t) { - let vertices = pointsOfTriangle(t).map(p => points[p]); - return circumcenter(vertices[0], vertices[1], vertices[2]); - } - - return {cells, vertices} - } - function edgesOfTriangle(t) {return [3*t, 3*t+1, 3*t+2];} + /** + * Gets the IDs of the points comprising the given triangle. Taken from {@link https://mapbox.github.io/delaunator/#triangle-to-points| the Delaunator docs.} + * @param {number} t The index of the triangle + * @returns {[number, number, number]} The IDs of the points comprising the given triangle. + */ + pointsOfTriangle(t) { + return this.edgesOfTriangle(t).map(edge => this.delaunay.triangles[edge]); + } - function triangleOfEdge(e) {return Math.floor(e/3);} + /** + * Identifies what triangles are adjacent to the given triangle. Taken from {@link https://mapbox.github.io/delaunator/#triangle-to-triangles| the Delaunator docs.} + * @param {number} t The index of the triangle + * @returns {number[]} The indices of the triangles that share half-edges with this triangle. + */ + trianglesAdjacentToTriangle(t) { + let triangles = []; + for (let edge of this.edgesOfTriangle(t)) { + let opposite = this.delaunay.halfedges[edge]; + triangles.push(this.triangleOfEdge(opposite)); + } + return triangles; + } - function nextHalfedge(e) {return (e % 3 === 2) ? e-2 : e+1;} + /** + * Gets the indices of all the incoming and outgoing half-edges that touch the given point. Taken from {@link https://mapbox.github.io/delaunator/#point-to-edges| the Delaunator docs.} + * @param {number} start The index of an incoming half-edge that leads to the desired point + * @returns {number[]} The indices of all half-edges (incoming or outgoing) that touch the point. + */ + edgesAroundPoint(start) { + const result = []; + let incoming = start; + do { + result.push(incoming); + const outgoing = this.nextHalfedge(incoming); + incoming = this.delaunay.halfedges[outgoing]; + } while (incoming !== -1 && incoming !== start && result.length < 20); + return result; + } - function prevHalfedge(e) {return (e % 3 === 0) ? e+2 : e-1;} + /** + * Returns the center of the triangle located at the given index. + * @param {number} t The index of the triangle + * @returns {[number, number]} + */ + triangleCenter(t) { + let vertices = this.pointsOfTriangle(t).map(p => this.points[p]); + return this.circumcenter(vertices[0], vertices[1], vertices[2]); + } - function circumcenter(a, b, c) { - let ad = a[0]*a[0] + a[1]*a[1], - bd = b[0]*b[0] + b[1]*b[1], - cd = c[0]*c[0] + c[1]*c[1]; - let D = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1])); + /** + * Retrieves all of the half-edges for a specific triangle `t`. Taken from {@link https://mapbox.github.io/delaunator/#edge-and-triangle| the Delaunator docs.} + * @param {number} t The index of the triangle + * @returns {[number, number, number]} The edges of the triangle. + */ + edgesOfTriangle(t) { return [3 * t, 3 * t + 1, 3 * t + 2]; } + + /** + * Enables lookup of a triangle, given one of the half-edges of that triangle. Taken from {@link https://mapbox.github.io/delaunator/#edge-and-triangle| the Delaunator docs.} + * @param {number} e The index of the edge + * @returns {number} The index of the triangle + */ + triangleOfEdge(e) { return Math.floor(e / 3); } + + /** + * Moves to the next half-edge of a triangle, given the current half-edge's index. Taken from {@link https://mapbox.github.io/delaunator/#edge-to-edges| the Delaunator docs.} + * @param {number} e The index of the current half edge + * @returns {number} The index of the next half edge + */ + nextHalfedge(e) { return (e % 3 === 2) ? e - 2 : e + 1; } + + /** + * Moves to the previous half-edge of a triangle, given the current half-edge's index. Taken from {@link https://mapbox.github.io/delaunator/#edge-to-edges| the Delaunator docs.} + * @param {number} e The index of the current half edge + * @returns {number} The index of the previous half edge + */ + prevHalfedge(e) { return (e % 3 === 0) ? e + 2 : e - 1; } + + /** + * Finds the circumcenter of the triangle identified by points a, b, and c. Taken from {@link https://en.wikipedia.org/wiki/Circumscribed_circle#Circumcenter_coordinates| Wikipedia} + * @param {[number, number]} a The coordinates of the first point of the triangle + * @param {[number, number]} b The coordinates of the second point of the triangle + * @param {[number, number]} c The coordinates of the third point of the triangle + * @return {[number, number]} The coordinates of the circumcenter of the triangle. + */ + circumcenter(a, b, c) { + const [ax, ay] = a; + const [bx, by] = b; + const [cx, cy] = c; + const ad = ax * ax + ay * ay; + const bd = bx * bx + by * by; + const cd = cx * cx + cy * cy; + const D = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)); return [ - Math.floor(1/D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1]))), - Math.floor(1/D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0]))) + Math.floor(1 / D * (ad * (by - cy) + bd * (cy - ay) + cd * (ay - by))), + Math.floor(1 / D * (ad * (cx - bx) + bd * (ax - cx) + cd * (bx - ax))) ]; } - - return Voronoi; - -}))); \ No newline at end of file +} \ No newline at end of file From 380e0babb232691470cba34fe218eccbb9900aec Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 14 Feb 2021 23:14:23 +0300 Subject: [PATCH 03/18] v1.5.33 - emblem shape change takes immediate effect --- index.html | 120 +++++++++++++++++----------------- modules/coa-renderer.js | 2 +- modules/cultures-generator.js | 45 +++++++------ modules/ui/emblems-editor.js | 3 +- modules/ui/options.js | 44 ++++++++++--- 5 files changed, 125 insertions(+), 89 deletions(-) diff --git a/index.html b/index.html index e4e31e0a..b56bff5c 100644 --- a/index.html +++ b/index.html @@ -1107,8 +1107,67 @@ + - +

Generator settings:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1184,65 +1243,6 @@ -
Onload behavior + +
Interface size + + + +
Tooltip size + + + +
Transparency + + + +
Speaker voice + + + 🔊 +
- -

Generator settings:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/modules/coa-renderer.js b/modules/coa-renderer.js index e956f322..e1a1dab9 100644 --- a/modules/coa-renderer.js +++ b/modules/coa-renderer.js @@ -1051,6 +1051,6 @@ if (layerIsOn("toggleEmblems")) trigger(id, coa); } - return {trigger, add}; + return {trigger, add, shieldPaths}; }))); diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js index 3fcf8170..4e4d4e62 100644 --- a/modules/cultures-generator.js +++ b/modules/cultures-generator.js @@ -55,26 +55,10 @@ c.origin = 0; c.code = getCode(c.name); cells.culture[cell] = i+1; - if (emblemShape === "random") c.shield = getRandomShiled(); + if (emblemShape === "random") c.shield = getRandomShield(); else if (emblemShape !== "culture" && emblemShape !== "state") c.shield = emblemShape; }); - function getRandomShiled() { - const shields = { - types: {basic: 10, regional: 2, historical: 1, specific: 1, banner: 1, simple: 2, fantasy: 1, middleEarth: 0}, - basic: {heater: 12, spanish: 6, french: 1}, - regional: {horsehead: 1, horsehead2: 1, polish: 1, hessen: 1, swiss: 1}, - historical: {boeotian: 1, roman: 2, kite: 1, oldFrench: 5, renaissance: 2, baroque: 2}, - specific: {targe: 1, targe2: 0, pavise: 5, wedged: 10}, - banner: {flag: 1, pennon: 0, guidon: 0, banner: 0, dovetail: 1, gonfalon: 5, pennant: 0}, - simple: {round: 12, oval: 6, vesicaPiscis: 1, square: 1, diamond: 2, no: 0}, - fantasy: {fantasy1: 2, fantasy2: 2, fantasy3: 1, fantasy4: 1, fantasy5: 3}, - middleEarth: {noldor: 1, gondor: 1, easterling: 1, erebor: 1, ironHills: 1, urukHai: 1, moriaOrc: 1} - } - const type = rw(shields.types); - return rw(shields[type]); - } - function placeCenter(v) { let c, spacing = (graphWidth + graphHeight) / 2 / count; const sorted = [...populated].sort((a, b) => v(b) - v(a)), max = Math.floor(sorted.length / 2); @@ -161,7 +145,14 @@ const code = getCode(name); const i = pack.cultures.length; const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex(); - pack.cultures.push({name, color, base, center, i, expansionism:1, type:"Generic", cells:0, area:0, rural:0, urban:0, origin:0, code}); + + // define emblem shape + const emblemShape = document.getElementById("emblemShape").value; + let shield = culture.shield; + if (emblemShape === "random") shield = getRandomShield(); + else if (emblemShape !== "culture" && emblemShape !== "state") shield = emblemShape; + + pack.cultures.push({name, color, base, center, i, expansionism:1, type:"Generic", cells:0, area:0, rural:0, urban:0, origin:0, code, shield}); } const getDefault = function(count) { @@ -428,6 +419,22 @@ return 0; } - return {generate, add, expand, getDefault}; + const getRandomShield = function() { + const shields = { + types: {basic: 10, regional: 2, historical: 1, specific: 1, banner: 1, simple: 2, fantasy: 1, middleEarth: 0}, + basic: {heater: 12, spanish: 6, french: 1}, + regional: {horsehead: 1, horsehead2: 1, polish: 1, hessen: 1, swiss: 1}, + historical: {boeotian: 1, roman: 2, kite: 1, oldFrench: 5, renaissance: 2, baroque: 2}, + specific: {targe: 1, targe2: 0, pavise: 5, wedged: 10}, + banner: {flag: 1, pennon: 0, guidon: 0, banner: 0, dovetail: 1, gonfalon: 5, pennant: 0}, + simple: {round: 12, oval: 6, vesicaPiscis: 1, square: 1, diamond: 2, no: 0}, + fantasy: {fantasy1: 2, fantasy2: 2, fantasy3: 1, fantasy4: 1, fantasy5: 3}, + middleEarth: {noldor: 1, gondor: 1, easterling: 1, erebor: 1, ironHills: 1, urukHai: 1, moriaOrc: 1} + } + const type = rw(shields.types); + return rw(shields[type]); + } + + return {generate, add, expand, getDefault, getRandomShield}; }))); diff --git a/modules/ui/emblems-editor.js b/modules/ui/emblems-editor.js index f6806449..d39c74a1 100644 --- a/modules/ui/emblems-editor.js +++ b/modules/ui/emblems-editor.js @@ -150,7 +150,8 @@ function editEmblem(type, id, el) { function changeShape() { el.coa.shield = this.value; - document.getElementById(id).remove(); + const coaEl = document.getElementById(id); + if (coaEl) coaEl.remove(); COArenderer.trigger(id, el.coa); } diff --git a/modules/ui/options.js b/modules/ui/options.js index 50473d62..25b27ded 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -289,15 +289,44 @@ function changeCultureSet() { if (+culturesOutput.value > +max) culturesInput.value = culturesOutput.value = max; } -function changeEmblemShape(value) { +function changeEmblemShape(emblemShape) { const image = document.getElementById("emblemShapeImage"); - const shapeEl = document.getElementById(value); - if (shapeEl) { - const shape = shapeEl.querySelector("path").getAttribute("d"); - image.setAttribute("d", shape); - } else { - image.removeAttribute("d"); + const shapePath = window.COArenderer && COArenderer.shieldPaths[emblemShape]; + shapePath ? image.setAttribute("d", shapePath) : image.removeAttribute("d"); + + const specificShape = ["culture", "state", "random"].includes(emblemShape) ? null : emblemShape; + if (emblemShape === "random") pack.cultures.filter(c => !c.removed).forEach(c => c.shield = Cultures.getRandomShield()); + + const rerenderCOA = (id, coa) => { + const coaEl = document.getElementById(id); + if (coaEl) coaEl.remove(); + COArenderer.trigger(id, coa); } + + pack.states.forEach(state => { + if (!state.i || state.removed || !state.coa || state.coa === "custom") return; + const newShield = specificShape || COA.getShield(state.culture, null); + if (newShield === state.coa.shield) return; + state.coa.shield = newShield; + rerenderCOA("stateCOA" + state.i, state.coa); + }); + + pack.provinces.forEach(province => { + if (!province.i || province.removed || !province.coa || province.coa === "custom") return; + const culture = pack.cells.culture[province.center]; + const newShield = specificShape || COA.getShield(culture, province.state); + if (newShield === province.coa.shield) return; + province.coa.shield = newShield; + rerenderCOA("provinceCOA" + province.i, province.coa); + }); + + pack.burgs.forEach(burg => { + if (!burg.i || burg.removed || !burg.coa || burg.coa === "custom") return; + const newShield = specificShape || COA.getShield(burg.culture, burg.state); + if (newShield === burg.coa.shield) return; + burg.coa.shield = newShield + rerenderCOA("burgCOA" + burg.i, burg.coa); + }); } function changeStatesNumber(value) { @@ -419,7 +448,6 @@ function randomizeOptions() { // World settings generateEra(); - changeEmblemShape(emblemShape.value); // change emblem shape image } // select heightmap template pseudo-randomly From cd7b9549c00f2c089dee40634457290c1338ad59 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 15 Feb 2021 15:33:01 +0300 Subject: [PATCH 04/18] v1.5.34 - fixed #567 --- libs/jquery-ui.css | 3 +++ modules/ui/emblems-editor.js | 28 ++++++++++++---------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/libs/jquery-ui.css b/libs/jquery-ui.css index 927bfc9b..9361f041 100644 --- a/libs/jquery-ui.css +++ b/libs/jquery-ui.css @@ -434,6 +434,9 @@ body .ui-dialog { font-family: Arial,Helvetica,sans-serif; font-size: 1em; } +.ui-widget button { + padding: 1px 6px; +} .ui-widget.ui-widget-content { border: 1px solid #5e4fa2; color: #333333; diff --git a/modules/ui/emblems-editor.js b/modules/ui/emblems-editor.js index d39c74a1..78dede8a 100644 --- a/modules/ui/emblems-editor.js +++ b/modules/ui/emblems-editor.js @@ -13,7 +13,7 @@ function editEmblem(type, id, el) { updateElementSelectors(type, id, el); $("#emblemEditor").dialog({ - title: "Edit Emblem", resizable: true, width: "18em", height: "auto", + title: "Edit Emblem", resizable: true, width: "18.2em", height: "auto", position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}, close: closeEmblemEditor }); @@ -233,10 +233,10 @@ function editEmblem(type, id, el) { buttons.classList.toggle("hidden"); } - function download(format) { + async function download(format) { const coa = document.getElementById(id); const size = +emblemsDownloadSize.value; - const url = getURL(coa, el.coa, size); + const url = await getURL(coa, size); const link = document.createElement("a"); link.download = getFileName(`Emblem ${el.fullName || el.name}`) + "." + format; @@ -247,7 +247,6 @@ function editEmblem(type, id, el) { function downloadSVG(url, link) { link.href = url; link.click(); - window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000); } function downloadRaster(format, url, link, size) { @@ -264,25 +263,22 @@ function editEmblem(type, id, el) { ctx.fillRect(0, 0, canvas.width, canvas.height); } ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - const URL = canvas.toDataURL("image/" + format, .92); - link.href = URL; + const dataURL = canvas.toDataURL("image/" + format, .92); + link.href = dataURL; link.click(); - window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000); + window.setTimeout(() => window.URL.revokeObjectURL(dataURL), 6000); } } - function getURL(svg, coa, size) { - const serialized = getSVG(svg, coa, size); - const blob = new Blob([serialized], { type: 'image/svg+xml;charset=utf-8' }); - const url = window.URL.createObjectURL(blob); - return url; - } - - function getSVG(svg, size) { + async function getURL(svg, size) { const clone = svg.cloneNode(true); // clone svg clone.setAttribute("width", size); clone.setAttribute("height", size); - return (new XMLSerializer()).serializeToString(clone); + const serialized = (new XMLSerializer()).serializeToString(clone); + const blob = new Blob([serialized], {type: 'image/svg+xml;charset=utf-8'}); + const url = window.URL.createObjectURL(blob); + window.setTimeout(() => window.URL.revokeObjectURL(url), 6000); + return url; } function downloadGallery() { From 5ba7653ad9e2cd1a2c4d6c992184b17b25d2073b Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 15 Feb 2021 23:12:13 +0300 Subject: [PATCH 05/18] v1.5.35 - fixed #569 --- index.html | 2 +- libs/jquery-ui.css | 2 +- modules/coa-generator.js | 2 +- modules/save-and-load.js | 22 +++++++++++++--------- modules/ui/emblems-editor.js | 2 +- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index b56bff5c..06032157 100644 --- a/index.html +++ b/index.html @@ -2863,8 +2863,8 @@
- +
diff --git a/libs/jquery-ui.css b/libs/jquery-ui.css index 9361f041..8d1b6fcc 100644 --- a/libs/jquery-ui.css +++ b/libs/jquery-ui.css @@ -434,7 +434,7 @@ body .ui-dialog { font-family: Arial,Helvetica,sans-serif; font-size: 1em; } -.ui-widget button { +.ui-widget button[class^="icon-"] { padding: 1px 6px; } .ui-widget.ui-widget-content { diff --git a/modules/coa-generator.js b/modules/coa-generator.js index e2cf6493..1afc64c0 100644 --- a/modules/coa-generator.js +++ b/modules/coa-generator.js @@ -467,7 +467,7 @@ const emblemShape = document.getElementById("emblemShape").value; if (emblemShape === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield; if (pack.cultures[culture].shield) return pack.cultures[culture].shield; - console.error("Emblem shape is not defined on culture level", pack.cultures[culture]); + console.error("Shield shape is not defined on culture level", pack.cultures[culture]); return "heater"; } diff --git a/modules/save-and-load.js b/modules/save-and-load.js index 3a42be49..f51b4786 100644 --- a/modules/save-and-load.js +++ b/modules/save-and-load.js @@ -1060,27 +1060,31 @@ function parseLoadedData(data) { } if (version < 1.5) { - // v 1.5 added emblems - emblems = viewbox.append("g").attr("id", "emblems").style("display", "none"); - emblems.append("g").attr("id", "burgEmblems"); - emblems.append("g").attr("id", "provinceEmblems"); - emblems.append("g").attr("id", "stateEmblems"); - regenerateEmblems(); - toggleEmblems(); - // not need to store default styles from v 1.5 localStorage.removeItem("styleClean"); localStorage.removeItem("styleGloom"); localStorage.removeItem("styleAncient"); localStorage.removeItem("styleMonochrome"); + // v 1.5 cultures has shield attribute + pack.cultures.forEach(culture => { + if (culture.removed) return; + culture.shield = Cultures.getRandomShield(); + }); + // v 1.5 added burg type value pack.burgs.forEach(burg => { if (!burg.i || burg.removed) return; burg.type = BurgsAndStates.getType(burg.cell, burg.port); }); - BurgsAndStates.getType(cell, false); + // v 1.5 added emblems + emblems = viewbox.insert("g", "#population").attr("id", "emblems").style("display", "none"); + emblems.append("g").attr("id", "burgEmblems"); + emblems.append("g").attr("id", "provinceEmblems"); + emblems.append("g").attr("id", "stateEmblems"); + regenerateEmblems(); + toggleEmblems(); } }() diff --git a/modules/ui/emblems-editor.js b/modules/ui/emblems-editor.js index 78dede8a..0d9b4b58 100644 --- a/modules/ui/emblems-editor.js +++ b/modules/ui/emblems-editor.js @@ -181,7 +181,7 @@ function editEmblem(type, id, el) { function openInArmoria() { const coa = el.coa && el.coa !== "custom" ? el.coa : {t1: "sable"}; const json = JSON.stringify(coa).replaceAll("#", "%23"); - const url = `http://azgaar.github.io/Armoria/?coa=${json}&from=FMG`; + const url = `https://azgaar.github.io/Armoria/?coa=${json}&from=FMG`; openURL(url); } From 46fe5514d17079d390f749d6861a56b151002400 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 16 Feb 2021 00:23:45 +0300 Subject: [PATCH 06/18] v1.5.36 - remove division gaps in firefox --- modules/coa-renderer.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/coa-renderer.js b/modules/coa-renderer.js index e1a1dab9..d4ce075e 100644 --- a/modules/coa-renderer.js +++ b/modules/coa-renderer.js @@ -757,13 +757,13 @@ const templates = { // divisions - perFess: line => ``, - perPale: line => ``, - perBend: line => ``, - perBendSinister: line => ``, + perFess: line => ``, + perPale: line => ``, + perBend: line => ``, + perBendSinister: line => ``, perChevron: line => ``, - perChevronReversed: line => ``, - perCross: line => ``, + perChevronReversed: line => ``, + perCross: line => ``, perPile: line => ``, perSaltire: () => ``, gyronny: () => ``, From 40bb9c2847f5cd9e410e4a67087d2646d214bd66 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 16 Feb 2021 13:01:52 +0300 Subject: [PATCH 07/18] v1.5.37 - add defs-emblems on update --- modules/save-and-load.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/save-and-load.js b/modules/save-and-load.js index f51b4786..93ff12cc 100644 --- a/modules/save-and-load.js +++ b/modules/save-and-load.js @@ -1079,6 +1079,7 @@ function parseLoadedData(data) { }); // v 1.5 added emblems + defs.append("g").attr("id", "defs-emblems"); emblems = viewbox.insert("g", "#population").attr("id", "emblems").style("display", "none"); emblems.append("g").attr("id", "burgEmblems"); emblems.append("g").attr("id", "provinceEmblems"); From 6c29cca231feb3918aec8afbc849bbe5da532d69 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 17 Feb 2021 14:40:52 +0300 Subject: [PATCH 08/18] v1.5.38 - #573 fixed --- main.js | 2 +- modules/save-and-load.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index 4e05d8d4..c4f56173 100644 --- a/main.js +++ b/main.js @@ -569,7 +569,7 @@ function generate() { Names.getMapName(); WARN && console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`); - INFO && showStatistics(); + showStatistics(); INFO && console.groupEnd("Generated Map " + seed); } catch(error) { diff --git a/modules/save-and-load.js b/modules/save-and-load.js index 93ff12cc..f725a6e8 100644 --- a/modules/save-and-load.js +++ b/modules/save-and-load.js @@ -1160,7 +1160,7 @@ function parseLoadedData(data) { invokeActiveZooming(); WARN && console.warn(`TOTAL: ${rn((performance.now()-uploadMap.timeStart)/1000,2)}s`); - INFO && showStatistics(); + showStatistics(); INFO && console.groupEnd("Loaded Map " + seed); tip("Map is successfully loaded", true, "success", 7000); } From 47b1c144ac0a9c04b559a977e614a79a9ad4a983 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 18 Feb 2021 18:52:59 +0300 Subject: [PATCH 09/18] v1.5.40 charges update from API --- charges/annulet.svg | 2 +- charges/bullHeadCaboshed.svg | 2 +- charges/bullPassant.svg | 2 +- charges/cancer.svg | 2 +- charges/{сarreau.svg => carreau.svg} | 0 charges/cock.svg | 2 +- charges/compassRose.svg | 2 +- charges/crown.svg | 2 +- charges/escallop.svg | 4 ++-- charges/lozengeFaceted.svg | 6 +++--- charges/mullet6Pierced.svg | 2 +- charges/mulletPierced.svg | 2 +- modules/coa-generator.js | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) rename charges/{сarreau.svg => carreau.svg} (100%) diff --git a/charges/annulet.svg b/charges/annulet.svg index 976c2474..2677e045 100644 --- a/charges/annulet.svg +++ b/charges/annulet.svg @@ -1,6 +1,6 @@ - + diff --git a/charges/bullHeadCaboshed.svg b/charges/bullHeadCaboshed.svg index 65112450..88ae177e 100644 --- a/charges/bullHeadCaboshed.svg +++ b/charges/bullHeadCaboshed.svg @@ -4,9 +4,9 @@ + - diff --git a/charges/bullPassant.svg b/charges/bullPassant.svg index 48b9c9ca..c73a9795 100644 --- a/charges/bullPassant.svg +++ b/charges/bullPassant.svg @@ -1,6 +1,6 @@ - + diff --git a/charges/cancer.svg b/charges/cancer.svg index 937ea3eb..49a2c83b 100644 --- a/charges/cancer.svg +++ b/charges/cancer.svg @@ -1,6 +1,6 @@ - + diff --git a/charges/сarreau.svg b/charges/carreau.svg similarity index 100% rename from charges/сarreau.svg rename to charges/carreau.svg diff --git a/charges/cock.svg b/charges/cock.svg index d8f92c52..cfd58106 100644 --- a/charges/cock.svg +++ b/charges/cock.svg @@ -1,6 +1,6 @@ - + diff --git a/charges/compassRose.svg b/charges/compassRose.svg index 16e3ba6b..8a6a8139 100644 --- a/charges/compassRose.svg +++ b/charges/compassRose.svg @@ -2,6 +2,6 @@ - + diff --git a/charges/crown.svg b/charges/crown.svg index 1b06f811..3a7629c8 100644 --- a/charges/crown.svg +++ b/charges/crown.svg @@ -1,6 +1,6 @@ - + diff --git a/charges/escallop.svg b/charges/escallop.svg index 14652210..6d912bfd 100644 --- a/charges/escallop.svg +++ b/charges/escallop.svg @@ -1,7 +1,7 @@ - - + + diff --git a/charges/lozengeFaceted.svg b/charges/lozengeFaceted.svg index 81bec3dc..23462185 100644 --- a/charges/lozengeFaceted.svg +++ b/charges/lozengeFaceted.svg @@ -2,9 +2,9 @@ - - - + + + diff --git a/charges/mullet6Pierced.svg b/charges/mullet6Pierced.svg index 03be315a..a58b68e4 100644 --- a/charges/mullet6Pierced.svg +++ b/charges/mullet6Pierced.svg @@ -1,6 +1,6 @@ - + diff --git a/charges/mulletPierced.svg b/charges/mulletPierced.svg index f1f67375..88f706b9 100644 --- a/charges/mulletPierced.svg +++ b/charges/mulletPierced.svg @@ -1,6 +1,6 @@ - + diff --git a/modules/coa-generator.js b/modules/coa-generator.js index 1afc64c0..2a8bfda8 100644 --- a/modules/coa-generator.js +++ b/modules/coa-generator.js @@ -23,7 +23,7 @@ conventional: { lozenge: 2, fusil: 4, mascle: 4, rustre: 2, lozengeFaceted: 3, lozengePloye: 1, roundel: 4, roundel2: 3, annulet: 4, mullet: 5, mulletPierced: 1, mulletFaceted: 1, mullet4: 3, mullet6: 4, mullet6Pierced: 1, mullet6Faceted: 1, mullet7: 1, mullet8: 1, mullet10: 1, - estoile: 1, compassRose: 1, billet: 5, delf: 0, triangle: 3, trianglePierced: 1, goutte: 4, heart: 4, pique: 2, сarreau: 1, trefle: 2, + estoile: 1, compassRose: 1, billet: 5, delf: 0, triangle: 3, trianglePierced: 1, goutte: 4, heart: 4, pique: 2, carreau: 1, trefle: 2, fleurDeLis: 6, sun: 3, sunInSplendour: 1, crescent: 5, fountain: 1 }, crosses: { From 66edd3f6c7be9400666ba3031ed84cbf2beec2cd Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 20 Feb 2021 17:32:30 +0300 Subject: [PATCH 10/18] v1.5.41 downloadGallery fix, async load --- modules/coa-generator.js | 8 +++--- modules/coa-renderer.js | 19 ++++++-------- modules/ui/emblems-editor.js | 48 +++++++++++++++++++----------------- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/modules/coa-generator.js b/modules/coa-generator.js index 2a8bfda8..2bdb195e 100644 --- a/modules/coa-generator.js +++ b/modules/coa-generator.js @@ -398,10 +398,10 @@ function definePattern(pattern, element, size = "") { let t1 = null, t2 = null; - if (P(.15)) size = "-small"; - else if (P(.05)) size = "-smaller"; - else if (P(.035)) size = "-big"; - else if (P(.001)) size = "-smallest"; + if (P(.1)) size = "-small"; + else if (P(.1)) size = "-smaller"; + else if (P(.01)) size = "-big"; + else if (P(.005)) size = "-smallest"; // apply standard tinctures if (P(.5) && ["vair", "vairInPale", "vairEnPointe"].includes(pattern)) {t1 = "azure"; t2 = "argent";} diff --git a/modules/coa-renderer.js b/modules/coa-renderer.js index d4ce075e..03716d7a 100644 --- a/modules/coa-renderer.js +++ b/modules/coa-renderer.js @@ -839,11 +839,7 @@ const divisionClip = division ? `${getTemplate(division.division, division.line)}` : ""; const loadedCharges = await getCharges(coa, id, shieldPath); const loadedPatterns = getPatterns(coa, id); - const blacklight = ` - - - - `; + const blacklight = ``; const field = ``; const divisionGroup = division ? templateDivision() : ""; const overlay = ``; @@ -855,6 +851,7 @@ // insert coa svg to defs document.getElementById("coas").insertAdjacentHTML("beforeend", svg); + return true; function templateDivision() { let svg = ""; @@ -1000,10 +997,10 @@ } function getSizeMod(size) { - if (size === "small") return .5; - if (size === "smaller") return .25; - if (size === "smallest") return .125; - if (size === "big") return 2; + if (size === "small") return .8; + if (size === "smaller") return .5; + if (size === "smallest") return .25; + if (size === "big") return 1.6; return 1; } @@ -1027,7 +1024,7 @@ } // render coa if does not exist - const trigger = function(id, coa) { + const trigger = async function(id, coa) { if (coa === "custom") { console.warn("Cannot render custom emblem", coa); return; @@ -1036,7 +1033,7 @@ console.warn(`Emblem ${id} is undefined`); return; } - if (!document.getElementById(id)) draw(id, coa); + if (!document.getElementById(id)) return draw(id, coa); } const add = function(type, i, coa, x, y) { diff --git a/modules/ui/emblems-editor.js b/modules/ui/emblems-editor.js index 0d9b4b58..a2a14520 100644 --- a/modules/ui/emblems-editor.js +++ b/modules/ui/emblems-editor.js @@ -271,41 +271,41 @@ function editEmblem(type, id, el) { } async function getURL(svg, size) { - const clone = svg.cloneNode(true); // clone svg - clone.setAttribute("width", size); - clone.setAttribute("height", size); - const serialized = (new XMLSerializer()).serializeToString(clone); + const serialized = getSVG(svg, size); const blob = new Blob([serialized], {type: 'image/svg+xml;charset=utf-8'}); const url = window.URL.createObjectURL(blob); window.setTimeout(() => window.URL.revokeObjectURL(url), 6000); return url; } - function downloadGallery() { + function getSVG(svg, size) { + const clone = svg.cloneNode(true); + clone.setAttribute("width", size); + clone.setAttribute("height", size); + return (new XMLSerializer()).serializeToString(clone); + } + + async function downloadGallery() { const name = getFileName("Emblems Gallery"); const validStates = pack.states.filter(s => s.i && !s.removed && s.coa); const validProvinces = pack.provinces.filter(p => p.i && !p.removed && p.coa); const validBurgs = pack.burgs.filter(b => b.i && !b.removed && b.coa); - triggerCOALoad(validStates, validProvinces, validBurgs); - const timeout = (validStates.length + validProvinces.length + validBurgs.length) * 8; - tip("Preparing to download...", true, "warn", timeout); - d3.timeout(runDownload, timeout); + await renderAllEmblems(validStates, validProvinces, validBurgs); + runDownload(); function runDownload() { const back = `Go Back`; const stateSection = `

States

` + validStates.map(state => { const el = document.getElementById("stateCOA"+state.i); - const svg = getSVG(el, state.coa, 200); - return `
${state.fullName}
${svg}
`; + return `
${state.fullName}
${getSVG(el, 200)}
`; }).join("") + `
`; const provinceSections = validStates.map(state => { const stateProvinces = validProvinces.filter(p => p.state === state.i); const figures = stateProvinces.map(province => { const el = document.getElementById("provinceCOA"+province.i); - const svg = getSVG(el, province.coa, 200); - return `
${province.fullName}
${svg}
`; + return `
${province.fullName}
${getSVG(el, 200)}
`; }).join(""); return stateProvinces.length ? `
${back}

${state.fullName} provinces

${figures}
` : ""; }).join(""); @@ -316,8 +316,7 @@ function editEmblem(type, id, el) { const provinceBurgs = stateBurgs.filter(b => pack.cells.province[b.cell] === province.i); const provinceBurgFigures = provinceBurgs.map(burg => { const el = document.getElementById("burgCOA"+burg.i); - const svg = getSVG(el, burg.coa, 200); - return `
${burg.name}
${svg}
`; + return `
${burg.name}
${getSVG(el, 200)}
`; }).join(""); return provinceBurgs.length ? `
${back}

${province.fullName} burgs

${provinceBurgFigures}
` : ""; }).join(""); @@ -325,8 +324,7 @@ function editEmblem(type, id, el) { const stateBurgOutOfProvinces = stateBurgs.filter(b => !pack.cells.province[b.cell]); const stateBurgOutOfProvincesFigures = stateBurgOutOfProvinces.map(burg => { const el = document.getElementById("burgCOA"+burg.i); - const svg = getSVG(el, burg.coa, 200); - return `
${burg.name}
${svg}
`; + return `
${burg.name}
${getSVG(el, 200)}
`; }).join(""); if (stateBurgOutOfProvincesFigures) stateBurgSections += `

${state.fullName} burgs under direct control

${stateBurgOutOfProvincesFigures}
`; return stateBurgSections; @@ -335,8 +333,7 @@ function editEmblem(type, id, el) { const neutralBurgs = validBurgs.filter(b => !b.state); const neutralsSection = neutralBurgs.length ? "

Independent burgs

" + neutralBurgs.map(burg => { const el = document.getElementById("burgCOA"+burg.i); - const svg = getSVG(el, burg.coa, 200); - return `
${burg.name}
${svg}
`; + return `
${burg.name}
${getSVG(el, 200)}
`; }).join("") + "
" : ""; const FMG = `Azgaar's Fantasy Map Generator`; @@ -367,10 +364,15 @@ function editEmblem(type, id, el) { } } - function triggerCOALoad(states, provinces, burgs) { - states.forEach(state => COArenderer.trigger("stateCOA"+state.i, state.coa)); - provinces.forEach(province => COArenderer.trigger("provinceCOA"+province.i, province.coa)); - burgs.forEach(burg => COArenderer.trigger("burgCOA"+burg.i, burg.coa)); + async function renderAllEmblems(states, provinces, burgs) { + tip("Preparing for download...", true, "warn"); + + const statePromises = states.map(state => COArenderer.trigger("stateCOA"+state.i, state.coa)); + const provincePromises = provinces.map(province => COArenderer.trigger("provinceCOA"+province.i, province.coa)); + const burgPromises = burgs.map(burg => COArenderer.trigger("burgCOA"+burg.i, burg.coa)); + const promises = [...statePromises, ...provincePromises, ...burgPromises]; + + return Promise.allSettled(promises).then(res => clearMainTip()); } function dragEmblem() { From bd7b7c2a5dd82d1c5767af2799e00eb22d85144f Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 20 Feb 2021 18:10:18 +0300 Subject: [PATCH 11/18] v1.5.42 edit emblem button --- index.html | 1 + modules/ui/general.js | 6 ++++++ modules/ui/tools.js | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/index.html b/index.html index 06032157..5a33c573 100644 --- a/index.html +++ b/index.html @@ -1289,6 +1289,7 @@ + diff --git a/modules/ui/general.js b/modules/ui/general.js index 90070057..4ad5a21f 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -19,6 +19,12 @@ document.getElementById("dialogs").addEventListener("mousemove", showDataTip); document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip); document.getElementById("exitCustomization").addEventListener("mousemove", showDataTip); +/** + * @param {string} tip Tooltip text + * @param {boolean} main Show above other tooltips + * @param {string} type Message type (color): error, warn, success + * @param {number} time Timeout to auto hide, ms + */ function tip(tip = "Tip is undefined", main, type, time) { tooltip.innerHTML = tip; tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)"; diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 5269e185..51abcab0 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -14,6 +14,7 @@ toolsContent.addEventListener("click", function(event) { if (button === "editDiplomacyButton") editDiplomacy(); else if (button === "editCulturesButton") editCultures(); else if (button === "editReligions") editReligions(); else + if (button === "editEmblemButton") openEmblemEditor(); else if (button === "editNamesBaseButton") editNamesbase(); else if (button === "editUnitsButton") editUnits(); else if (button === "editNotesButton") editNotes(); else @@ -72,6 +73,26 @@ function processFeatureRegeneration(event, button) { if (button === "regenerateZones") regenerateZones(event); } +async function openEmblemEditor() { + let type, id, el; + + if (pack.states[1]?.coa) { + type = "state"; + id = "stateCOA1"; + el = pack.states[1]; + } else if (pack.burgs[1]?.coa) { + type = "burg"; + id = "burgCOA1"; + el = pack.burgs[1]; + } else { + tip("No emblems to edit, please generate states and burgs first", false, "error"); + return; + } + + await COArenderer.trigger(id, el.coa); + editEmblem(type, id, el); +} + function regenerateRivers() { Rivers.generate(); Rivers.specify(); From d61970de1f089ca1c9cba465a8e781917a2d3e5a Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 20 Feb 2021 18:37:43 +0300 Subject: [PATCH 12/18] v1.5.43 update screen change --- index.css | 4 ++-- main.js | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/index.css b/index.css index 2fff4e78..4390c271 100644 --- a/index.css +++ b/index.css @@ -2003,8 +2003,8 @@ svg.button { } #alertMessage ul { - padding-left: 15px; - margin: 10px 0; + padding-left: 1.2em; + margin: 1em 0; } .pseudoLink { diff --git a/main.js b/main.js index c4f56173..37d89739 100644 --- a/main.js +++ b/main.js @@ -338,7 +338,7 @@ function applyDefaultBiomesSystem() { } function showWelcomeMessage() { - const post = link("https://www.reddit.com/r/FantasyMapGenerator/comments/ft5b41/update_v15/", "Main changes:"); // announcement on Reddit + const post = "Main changes:" //link("https://www.reddit.com/r/FantasyMapGenerator/comments/ft5b41/update_v15/", "Main changes:"); const changelog = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "previous version"); const reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit community"); const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server"); @@ -347,18 +347,19 @@ function showWelcomeMessage() { alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version ${version}. This version is compatible with ${changelog}, loaded .map files will be auto-updated.
    ${post} -
  • Emblems generation
  • -
  • Emblem editor integrated with ${link("https://azgaar.github.io/Armoria", "Armoria")}
  • +
  • State, province and burg Emblems generation
  • +
  • Emblem editor integrated with ${link("https://azgaar.github.io/Armoria", "Armoria")} — our new dedicated Heraldry generator and editor
  • Burg editor screen update
  • Speak name functionality
+ Armoria preview

Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.

Thanks for all supporters on ${patreon}!`; $("#alert").dialog( {resizable: false, title: "Fantasy Map Generator update", width: "28em", buttons: {OK: function() {$(this).dialog("close")}}, - position: {my: "center", at: "center", of: "svg"}, + position: {my: "center center-80", at: "center", of: "svg"}, close: () => localStorage.setItem("version", version)} ); } From c6c415ba4c7e6dc65c74e4a2c8cb9374d8597e22 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 20 Feb 2021 21:21:28 +0300 Subject: [PATCH 13/18] v1.5.44 - shortcut + spelling --- index.html | 2 +- modules/ui/general.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 5a33c573..3cb08413 100644 --- a/index.html +++ b/index.html @@ -2667,7 +2667,7 @@
- + diff --git a/modules/ui/general.js b/modules/ui/general.js index 4ad5a21f..4d415549 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -480,6 +480,7 @@ document.addEventListener("keyup", event => { else if (shift && key === 78) editNamesbase(); // Shift + "N" to edit Namesbase else if (shift && key === 90) editZones(); // Shift + "Z" to edit Zones else if (shift && key === 82) editReligions(); // Shift + "R" to edit Religions + else if (shift && key === 89) openEmblemEditor(); // Shift + "Y" to edit Emblems else if (shift && key === 81) editUnits(); // Shift + "Q" to edit Units else if (shift && key === 79) editNotes(); // Shift + "O" to edit Notes else if (shift && key === 84) overviewBurgs(); // Shift + "T" to open Burgs overview From 75c17da355cdb0ab7a6c946179b2dcca89e11f29 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 20 Feb 2021 21:30:00 +0300 Subject: [PATCH 14/18] v1.5.45 - highlightEmblem move to debug layer --- modules/ui/general.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ui/general.js b/modules/ui/general.js index 4d415549..d6de7148 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -303,13 +303,12 @@ function getPopulationTip(i) { } function highlightEmblemElement(type, el) { - if (emblems.selectAll("line, circle").size()) return; const i = el.i, cells = pack.cells; const animation = d3.transition().duration(1000).ease(d3.easeSinIn); if (type === "burg") { const {x, y} = el; - emblems.append("circle").attr("cx", x).attr("cy", y).attr("r", 0) + debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0) .attr("fill", "none").attr("stroke", "#d0240f").attr("stroke-width", 1).attr("opacity", 1) .transition(animation).attr("r", 20).attr("opacity", .1).attr("stroke-width", 0).remove(); return; @@ -320,7 +319,7 @@ function highlightEmblemElement(type, el) { const borderCells = cells.i.filter(id => obj[id] === i && cells.c[id].some(n => obj[n] !== i)); const data = Array.from(borderCells).filter((c, i) => !(i%2)).map(i => cells.p[i]).map(i => [i[0], i[1], Math.hypot(i[0]-x, i[1]-y)]); - emblems.selectAll("line").data(data).enter().append("line") + debug.selectAll("line").data(data).enter().append("line") .attr("x1", x).attr("y1", y).attr("x2", d => d[0]).attr("y2", d => d[1]) .attr("stroke", "#d0240f").attr("stroke-width", .5).attr("opacity", .2) .attr("stroke-dashoffset", d => d[2]).attr("stroke-dasharray", d => d[2]) From 4e34e32b1f12b62836852ffa071bae12b76c64ce Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 20 Feb 2021 22:33:33 +0300 Subject: [PATCH 15/18] v1.5.46 - new patterns --- modules/coa-generator.js | 9 +- modules/coa-renderer.js | 175 ++++++++++++++++++++++++--------------- 2 files changed, 116 insertions(+), 68 deletions(-) diff --git a/modules/coa-generator.js b/modules/coa-generator.js index 2bdb195e..e3071f9e 100644 --- a/modules/coa-generator.js +++ b/modules/coa-generator.js @@ -11,7 +11,14 @@ metals: { argent: 3, or: 2 }, colours: { gules: 5, azure: 4, sable: 3, purpure: 3, vert: 2 }, stains: { murrey: 1, sanguine: 1, tenné: 1 }, - patterns: { semy: 1, vair: 2, vairInPale: 1, vairEnPointe: 2, ermine: 2, chequy: 5, lozengy: 2, fusily: 1, pally: 4, barry: 4, gemelles: 1, bendy: 3, bendySinister: 2, palyBendy: 1, pappellony: 2, masoned: 3, fretty: 2 } + patterns: { + semy: 8, ermine: 6, + vair: 4, counterVair: 1, vairInPale: 1, vairEnPointe: 2, vairAncien: 2, + potent: 2, counterPotent: 1, potentInPale: 1, potentEnPointe: 1, + chequy: 8, lozengy: 5, fusily: 2, pally: 8, barry: 10, gemelles: 1, + bendy: 8, bendySinister: 4, palyBendy: 2, barryBendy: 1, + pappellony: 2, pappellony2: 3, scaly: 1, plumetty: 1, + masoned: 6, fretty: 3, grillage: 1, chainy: 1, maily: 2, honeycombed: 1 } } const charges = { diff --git a/modules/coa-renderer.js b/modules/coa-renderer.js index 03716d7a..3a451782 100644 --- a/modules/coa-renderer.js +++ b/modules/coa-renderer.js @@ -756,72 +756,112 @@ } const templates = { - // divisions - perFess: line => ``, - perPale: line => ``, - perBend: line => ``, - perBendSinister: line => ``, - perChevron: line => ``, - perChevronReversed: line => ``, - perCross: line => ``, - perPile: line => ``, - perSaltire: () => ``, - gyronny: () => ``, - chevronny: () => ``, - // oprinaries - fess: line => ``, - pale: line => ``, - bend: line => ``, - bendSinister: line => ``, - chief: line => ``, - bar: line => ``, - gemelle: line => ``, - fessCotissed: line => ``, - fessDoubleCotissed: line => ``, - bendlet: line => ``, - bendletSinister: line => ``, - terrace: line => ``, - cross: line => ``, - crossParted: line => ``, - saltire: line => ``, - saltireParted: line => ``, - mount: () => ``, - point: () => ``, - flaunches: () => ``, - gore: () => ``, - pall: () => ``, - pallReversed: () => ``, - chevron: () => ``, - chevronReversed: () => ``, - gyron: () => ``, - quarter: () => ``, - canton: () => ``, - pile: () => ``, - pileInBend: () => ``, - pileInBendSinister: () => ``, - piles: () => ``, - pilesInPoint: () => ``, - label: () => `` + // straight divisions + perFess: ``, + perPale: ``, + perBend: ``, + perBendSinister: ``, + perChevron: ``, + perChevronReversed: ``, + perCross: ``, + perPile: ``, + perSaltire: ``, + gyronny: ``, + chevronny: ``, + // lined divisions + perFessLined: line => ``, + perPaleLined: line => ``, + perBendLined: line => ``, + perBendSinisterLined: line => ``, + perChevronLined: line => ``, + perChevronReversedLined: line => ``, + perCrossLined: line => ``, + perPileLined: line => ``, + // straight ordinaries + fess: ``, + pale: ``, + bend: ``, + bendSinister: ``, + chief: ``, + bar: ``, + gemelle: ``, + fessCotissed: ``, + fessDoubleCotissed: ``, + bendlet: ``, + bendletSinister: ``, + terrace: ``, + cross: ``, + crossParted: ``, + saltire: ``, + saltireParted: ``, + mount: ``, + point: ``, + flaunches: ``, + gore: ``, + pall: ``, + pallReversed: ``, + chevron: ``, + chevronReversed: ``, + gyron: ``, + quarter: ``, + canton: ``, + pile: ``, + pileInBend: ``, + pileInBendSinister: ``, + piles: ``, + pilesInPoint: ``, + label: ``, + // lined ordinaries + fessLined: line => ``, + paleLined: line => ``, + bendLined: line => ``, + bendSinisterLined: line => ``, + chiefLined: line => ``, + barLined: line => ``, + gemelleLined: line => ``, + fessCotissedLined: line => ``, + fessDoubleCotissedLined: line => ``, + bendletLined: line => ``, + bendletSinisterLined: line => ``, + terraceLined: line => ``, + crossLined: line => ``, + crossPartedLined: line => ``, + saltireLined: line => ``, + saltirePartedLined: line => `` } const patterns = { - semy: (p, c1, c2, size, chargeId) => ``, - vair: (p, c1, c2, size) => ``, - vairInPale: (p, c1, c2, size) => ``, - vairEnPointe: (p, c1, c2, size) => ``, - ermine: (p, c1, c2, size) => ``, - chequy: (p, c1, c2, size) => ``, - lozengy: (p, c1, c2, size) => ``, - fusily: (p, c1, c2, size) => ``, - pally: (p, c1, c2, size) => ``, - barry: (p, c1, c2, size) => ``, - gemelles: (p, c1, c2, size) => ``, - bendy: (p, c1, c2, size) => ``, - bendySinister: (p, c1, c2, size) => ``, - palyBendy: (p, c1, c2, size) => ``, - pappellony: (p, c1, c2, size) => ``, - masoned: (p, c1, c2, size) => ``, - fretty: (p, c1, c2, size) => `` + semy: (p, c1, c2, size, chargeId) => ``, + vair: (p, c1, c2, size) => ``, + counterVair: (p, c1, c2, size) => ``, + vairInPale: (p, c1, c2, size) => ``, + vairEnPointe: (p, c1, c2, size) => ``, + vairAncien: (p, c1, c2, size) => ``, + potent: (p, c1, c2, size) => ``, + counterPotent: (p, c1, c2, size) => ``, + potentInPale: (p, c1, c2, size) => ``, + potentEnPointe: (p, c1, c2, size) => ``, + ermine: (p, c1, c2, size) => ``, + chequy: (p, c1, c2, size) => ``, + lozengy: (p, c1, c2, size) => ``, + fusily: (p, c1, c2, size) => ``, + pally: (p, c1, c2, size) => ``, + barry: (p, c1, c2, size) => ``, + gemelles: (p, c1, c2, size) => ``, + bendy: (p, c1, c2, size) => ``, + bendySinister: (p, c1, c2, size) => ``, + palyBendy: (p, c1, c2, size) => ``, + barryBendy: (p, c1, c2, size) => ``, + pappellony: (p, c1, c2, size) => ``, + pappellony2: (p, c1, c2, size) => ``, + scaly: (p, c1, c2, size) => ``, + plumetty: (p, c1, c2, size) => ``, + masoned: (p, c1, c2, size) => ``, + fretty: (p, c1, c2, size) => ``, + grillage: (p, c1, c2, size) => ``, + chainy: (p, c1, c2, size) => ``, + maily: (p, c1, c2, size) => ``, + honeycombed: (p, c1, c2, size) => `` } const draw = async function(id, coa) { @@ -1004,10 +1044,11 @@ return 1; } - function getTemplate(templateId, lineId) { - if (!lineId) return templates[templateId](); - const line = lines[lineId] || lines.straight; - return templates[templateId](line); + function getTemplate(id, line) { + const linedId = id+"Lined"; + if (!line || line === "straight" || !templates[linedId]) return templates[id]; + const linePath = lines[line]; + return templates[linedId](linePath); } // get color or link to pattern From e088c011eb2623948cc4e4ffa60ba7b1e81424c9 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 21 Feb 2021 00:57:25 +0300 Subject: [PATCH 16/18] v1.5.47 - hold Shift to show emblem area --- modules/ui/general.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui/general.js b/modules/ui/general.js index d6de7148..99c51095 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -106,13 +106,13 @@ function showMapTooltip(point, e, i, g) { parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"]; const i = +e.target.dataset.i; - highlightEmblemElement(type, g[i]); + if (event.shiftKey) highlightEmblemElement(type, g[i]); d3.select(e.target).raise(); d3.select(parent).raise(); const name = g[i].fullName || g[i].name; - tip(`${name} ${type} emblem. Click to edit`); + tip(`${name} ${type} emblem. Click to edit. Hold Shift to show associated area or place`); return; } if (group === "rivers") { From 7b1e463c922ca39d9d4d3558162c288ee30827d9 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 21 Feb 2021 01:10:52 +0300 Subject: [PATCH 17/18] v1.5.48 - hunting and nomad type only for small settlements --- modules/burgs-and-states.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index 4270870c..d3d75e97 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -208,8 +208,12 @@ if (cells.haven[i] && pack.features[cells.f[cells.haven[i]]].type === "lake") return "Lake"; if (cells.h[i] > 60) return "Highland"; if (cells.r[i] && cells.r[i].length > 100 && cells.r[i].length >= pack.rivers[0].length) return "River"; - if ([1, 2, 3, 4].includes(cells.biome[i])) return "Nomadic"; - if (cells.biome[i] > 4 && cells.biome[i] < 10) return "Hunting"; + + if (!cells.burg[i] || pack.burgs[cells.burg[i]].population < 6) { + if (population < 5 && [1, 2, 3, 4].includes(cells.biome[i])) return "Nomadic"; + if (cells.biome[i] > 4 && cells.biome[i] < 10) return "Hunting"; + } + return "Generic"; } From bfc881bda9414e55c3abaf6cd7eec48ce438e4f9 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 21 Feb 2021 15:55:52 +0300 Subject: [PATCH 18/18] v1.5.49 - select emblem shape on culture level --- index.css | 10 +---- index.html | 1 + modules/coa-generator.js | 21 +++++++++-- modules/cultures-generator.js | 19 ++-------- modules/ui/cultures-editor.js | 70 +++++++++++++++++++++++++++++------ modules/ui/options.js | 3 +- 6 files changed, 84 insertions(+), 40 deletions(-) diff --git a/index.css b/index.css index 4390c271..d104ffb3 100644 --- a/index.css +++ b/index.css @@ -1361,10 +1361,7 @@ div.states>.culturePopulation { cursor: pointer; } -div.states > .cultureBase, -div.states > .cultureType, -div.states > .stateCulture, -div.states > .diplomacyRelations { +div.states > select { width: 4.6em; cursor: pointer; border: 0; @@ -1374,10 +1371,7 @@ div.states > .diplomacyRelations { appearance: none; } -div.states > .cultureBase { - width: 6em; -} - +div.states > .cultureBase, div.states > .burgName, div.states > .burgState, div.states > .burgCulture { diff --git a/index.html b/index.html index 3cb08413..2218f7a5 100644 --- a/index.html +++ b/index.html @@ -2604,6 +2604,7 @@
Area 
Population 
Namesbase 
+
Emblems 
diff --git a/modules/coa-generator.js b/modules/coa-generator.js index e3071f9e..22265084 100644 --- a/modules/coa-generator.js +++ b/modules/coa-generator.js @@ -196,6 +196,18 @@ } }; + const shields = { + types: {basic: 10, regional: 2, historical: 1, specific: 1, banner: 1, simple: 2, fantasy: 1, middleEarth: 0}, + basic: {heater: 12, spanish: 6, french: 1}, + regional: {horsehead: 1, horsehead2: 1, polish: 1, hessen: 1, swiss: 1}, + historical: {boeotian: 1, roman: 2, kite: 1, oldFrench: 5, renaissance: 2, baroque: 2}, + specific: {targe: 1, targe2: 0, pavise: 5, wedged: 10}, + banner: {flag: 1, pennon: 0, guidon: 0, banner: 0, dovetail: 1, gonfalon: 5, pennant: 0}, + simple: {round: 12, oval: 6, vesicaPiscis: 1, square: 1, diamond: 2, no: 0}, + fantasy: {fantasy1: 2, fantasy2: 2, fantasy3: 1, fantasy4: 1, fantasy5: 3}, + middleEarth: {noldor: 1, gondor: 1, easterling: 1, erebor: 1, ironHills: 1, urukHai: 1, moriaOrc: 1} + } + const generate = function(parent, kinship, dominion, type) { if (parent === "custom") parent = null; let usedPattern = null, usedTinctures = []; @@ -471,8 +483,11 @@ } const getShield = function(culture, state) { - const emblemShape = document.getElementById("emblemShape").value; - if (emblemShape === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield; + const emblemShape = document.getElementById("emblemShape"); + const shapeGroup = emblemShape.selectedOptions[0].parentNode.label; + if (shapeGroup !== "Diversiform") return emblemShape.value; + + if (emblemShape.value === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield; if (pack.cultures[culture].shield) return pack.cultures[culture].shield; console.error("Shield shape is not defined on culture level", pack.cultures[culture]); return "heater"; @@ -481,6 +496,6 @@ const toString = coa => JSON.stringify(coa).replaceAll("#", "%23"); const copy = coa => JSON.parse(JSON.stringify(coa)); - return {generate, toString, copy, getShield}; + return {generate, toString, copy, getShield, shields}; }))); \ No newline at end of file diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js index 4e4d4e62..d0e37161 100644 --- a/modules/cultures-generator.js +++ b/modules/cultures-generator.js @@ -56,7 +56,6 @@ c.code = getCode(c.name); cells.culture[cell] = i+1; if (emblemShape === "random") c.shield = getRandomShield(); - else if (emblemShape !== "culture" && emblemShape !== "state") c.shield = emblemShape; }); function placeCenter(v) { @@ -147,10 +146,9 @@ const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex(); // define emblem shape - const emblemShape = document.getElementById("emblemShape").value; let shield = culture.shield; + const emblemShape = document.getElementById("emblemShape").value; if (emblemShape === "random") shield = getRandomShield(); - else if (emblemShape !== "culture" && emblemShape !== "state") shield = emblemShape; pack.cultures.push({name, color, base, center, i, expansionism:1, type:"Generic", cells:0, area:0, rural:0, urban:0, origin:0, code, shield}); } @@ -420,19 +418,8 @@ } const getRandomShield = function() { - const shields = { - types: {basic: 10, regional: 2, historical: 1, specific: 1, banner: 1, simple: 2, fantasy: 1, middleEarth: 0}, - basic: {heater: 12, spanish: 6, french: 1}, - regional: {horsehead: 1, horsehead2: 1, polish: 1, hessen: 1, swiss: 1}, - historical: {boeotian: 1, roman: 2, kite: 1, oldFrench: 5, renaissance: 2, baroque: 2}, - specific: {targe: 1, targe2: 0, pavise: 5, wedged: 10}, - banner: {flag: 1, pennon: 0, guidon: 0, banner: 0, dovetail: 1, gonfalon: 5, pennant: 0}, - simple: {round: 12, oval: 6, vesicaPiscis: 1, square: 1, diamond: 2, no: 0}, - fantasy: {fantasy1: 2, fantasy2: 2, fantasy3: 1, fantasy4: 1, fantasy5: 3}, - middleEarth: {noldor: 1, gondor: 1, easterling: 1, erebor: 1, ironHills: 1, urukHai: 1, moriaOrc: 1} - } - const type = rw(shields.types); - return rw(shields[type]); + const type = rw(COA.shields.types); + return rw(COA.shields[type]); } return {generate, add, expand, getDefault, getRandomShield}; diff --git a/modules/ui/cultures-editor.js b/modules/ui/cultures-editor.js index c7a6c45e..5f25fb38 100644 --- a/modules/ui/cultures-editor.js +++ b/modules/ui/cultures-editor.js @@ -60,6 +60,9 @@ function editCultures() { const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; let lines = "", totalArea = 0, totalPopulation = 0; + const emblemShapeGroup = document.getElementById("emblemShape").selectedOptions[0].parentNode.label; + const selectShape = emblemShapeGroup === "Diversiform" + for (const c of pack.cultures) { if (c.removed) continue; const area = c.area * (distanceScaleInput.value ** 2); @@ -73,7 +76,7 @@ function editCultures() { if (!c.i) { // Uncultured (neutral) line lines += `
+ data-area=${area} data-population=${population} data-base=${c.base} data-type="" data-expansionism="" data-emblems="${c.shield}"> @@ -86,26 +89,28 @@ function editCultures() {
${si(population)}
- + + ${selectShape ? `` : ""}
`; continue; } lines += `
+ data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}">
${c.cells}
- +
${si(area) + unit}
${si(population)}
- + + ${selectShape ? `` : ""}
`; } @@ -128,6 +133,7 @@ function editCultures() { body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism)); body.querySelectorAll("div > select.cultureType").forEach(el => el.addEventListener("change", cultureChangeType)); body.querySelectorAll("div > select.cultureBase").forEach(el => el.addEventListener("change", cultureChangeBase)); + body.querySelectorAll("div > select.cultureShape").forEach(el => el.addEventListener("change", cultureChangeShape)); body.querySelectorAll("div > div.culturePopulation").forEach(el => el.addEventListener("click", changePopulation)); body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", cultureRegenerateBurgs)); body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", cultureRemove)); @@ -150,6 +156,11 @@ function editCultures() { return options; } + function getShapeOptions(selected) { + const shapes = Object.keys(COA.shields.types).map(type => Object.keys(COA.shields[type])).flat(); + return shapes.map(shape => ``); + } + function cultureHighlightOn(event) { const culture = +event.target.dataset.id; const info = document.getElementById("cultureInfo"); @@ -225,6 +236,40 @@ function editCultures() { this.parentNode.dataset.base = pack.cultures[culture].base = v; } + function cultureChangeShape() { + const culture = +this.parentNode.dataset.id; + const shape = this.value; + this.parentNode.dataset.emblems = pack.cultures[culture].shield = shape; + + const rerenderCOA = (id, coa) => { + const coaEl = document.getElementById(id); + if (!coaEl) return; // not rendered + coaEl.remove(); + COArenderer.trigger(id, coa); + } + + pack.states.forEach(state => { + if (state.culture !== culture || !state.i || state.removed || !state.coa || state.coa === "custom") return; + if (shape === state.coa.shield) return; + state.coa.shield = shape; + rerenderCOA("stateCOA" + state.i, state.coa); + }); + + pack.provinces.forEach(province => { + if (pack.cells.culture[province.center] !== culture || !province.i || province.removed || !province.coa || province.coa === "custom") return; + if (shape === province.coa.shield) return; + province.coa.shield = shape; + rerenderCOA("provinceCOA" + province.i, province.coa); + }); + + pack.burgs.forEach(burg => { + if (burg.culture !== culture || !burg.i || burg.removed || !burg.coa || burg.coa === "custom") return; + if (shape === burg.coa.shield) return; + burg.coa.shield = shape + rerenderCOA("burgCOA" + burg.i, burg.coa); + }); + } + function changePopulation() { const culture = +this.parentNode.dataset.id; const c = pack.cultures[culture]; @@ -293,7 +338,7 @@ function editCultures() { b.name = Names.getCulture(culture); labels.select("[data-id='" + b.i +"']").text(b.name); }); - tip(`Names for ${cBurgs.length} burgs are re-generated`); + tip(`Names for ${cBurgs.length} burgs are regenerated`, false, "success"); } function cultureRemove() { @@ -306,12 +351,12 @@ function editCultures() { Remove: function() { cults.select("#culture"+culture).remove(); debug.select("#cultureCenter"+culture).remove(); - + pack.burgs.filter(b => b.culture == culture).forEach(b => b.culture = 0); pack.states.forEach((s, i) => {if(s.culture === culture) s.culture = 0;}); pack.cells.culture.forEach((c, i) => {if(c === culture) pack.cells.culture[i] = 0;}); pack.cultures[culture].removed = true; - + const origin = pack.cultures[culture].origin; pack.cultures.forEach(c => {if(c.origin === culture) c.origin = origin;}); refreshCulturesEditor(); @@ -493,8 +538,8 @@ function editCultures() { culturesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); culturesHeader.querySelector("div[data-sortby='type']").style.left = "8.8em"; + culturesHeader.querySelector("div[data-sortby='base']").style.left = "13.6em"; culturesFooter.style.display = "none"; - culturesHeader.querySelector("div[data-sortby='base']").style.marginLeft = "20px"; body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); $("#culturesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); @@ -589,8 +634,8 @@ function editCultures() { culturesEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden")); culturesHeader.querySelector("div[data-sortby='type']").style.left = "18.6em"; + culturesHeader.querySelector("div[data-sortby='base']").style.left = "35.8em"; culturesFooter.style.display = "block"; - culturesHeader.querySelector("div[data-sortby='base']").style.marginLeft = "2px"; body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); if(!close) $("#culturesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); @@ -634,7 +679,7 @@ function editCultures() { function downloadCulturesData() { const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; - let data = "Id,Culture,Color,Cells,Expansionism,Type,Area "+unit+",Population,Namesbase\n"; // headers + let data = "Id,Culture,Color,Cells,Expansionism,Type,Area "+unit+",Population,Namesbase,Emblems Shape\n"; // headers body.querySelectorAll(":scope > div").forEach(function(el) { data += el.dataset.id + ","; @@ -646,7 +691,8 @@ function editCultures() { data += el.dataset.area + ","; data += el.dataset.population + ","; const base = +el.dataset.base; - data += nameBases[base].name + "\n"; + data += nameBases[base].name + ","; + data += el.dataset.emblems + "\n"; }); const name = getFileName("Cultures") + ".csv"; diff --git a/modules/ui/options.js b/modules/ui/options.js index 25b27ded..875438be 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -299,7 +299,8 @@ function changeEmblemShape(emblemShape) { const rerenderCOA = (id, coa) => { const coaEl = document.getElementById(id); - if (coaEl) coaEl.remove(); + if (!coaEl) return; // not rendered + coaEl.remove(); COArenderer.trigger(id, coa); }
Onload behavior - -
Interface size - - - -
Tooltip size - - - -
Transparency - - - -
Speaker voice - - - 🔊 -