diff --git a/index.html b/index.html index 2c9fce59..eb8a3e97 100644 --- a/index.html +++ b/index.html @@ -3114,14 +3114,14 @@
1 population point =
- - + +
Urbanization rate:
- - + +
diff --git a/main.js b/main.js index 0abcf164..091acf11 100644 --- a/main.js +++ b/main.js @@ -137,6 +137,9 @@ let options = {pinNotes: false}; // options object let mapCoordinates = {}; // map coordinates on globe options.winds = [225, 45, 225, 315, 135, 315]; // default wind directions +let populationRate = +document.getElementById("populationRateInput").value; +let urbanization = +document.getElementById("urbanizationInput").value; + applyStoredOptions(); let graphWidth = +mapWidthInput.value, graphHeight = +mapHeightInput.value; // voronoi graph extention, cannot be changed arter generation @@ -1480,7 +1483,7 @@ function addMarkers(number = 1) { const resource = rw(resources); const burg = pack.burgs[cells.burg[cell]]; const name = `${burg.name} — ${resource} mining town`; - const population = rn(burg.population * populationRate.value * urbanization.value); + const population = rn(burg.population * populationRate * urbanization); const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`; notes.push({id, name, legend}); count--; diff --git a/modules/load.js b/modules/load.js index 60c5b3e3..f723acfe 100644 --- a/modules/load.js +++ b/modules/load.js @@ -131,8 +131,8 @@ function parseLoadedData(data) { if (settings[9]) barBackColor.value = settings[9]; if (settings[10]) barPosX.value = settings[10]; if (settings[11]) barPosY.value = settings[11]; - if (settings[12]) populationRate.value = populationRateOutput.value = settings[12]; - if (settings[13]) urbanization.value = urbanizationOutput.value = settings[13]; + if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12]; + if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13]; if (settings[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(settings[14], 100), 1); if (settings[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(settings[15], 100), 0); if (settings[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = settings[16]; diff --git a/modules/military-generator.js b/modules/military-generator.js index b434e279..bff44019 100644 --- a/modules/military-generator.js +++ b/modules/military-generator.js @@ -1,60 +1,67 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Military = factory()); -}(this, (function () {'use strict'; + typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Military = factory()); +})(this, function () { + "use strict"; - const generate = function() { + const generate = function () { TIME && console.time("generateMilitaryForces"); - const cells = pack.cells, p = cells.p, states = pack.states; + const cells = pack.cells, + p = cells.p, + states = pack.states; const valid = states.filter(s => s.i && !s.removed); // valid states if (!options.military) options.military = getDefaultOptions(); const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion const area = d3.sum(valid.map(s => s.area)); // total area - const rate = {x:0, Ally:-.2, Friendly:-.1, Neutral:0, Suspicion:.1, Enemy:1, Unknown:0, Rival:.5, Vassal:.5, Suzerain:-.5}; + const rate = {x: 0, Ally: -0.2, Friendly: -0.1, Neutral: 0, Suspicion: 0.1, Enemy: 1, Unknown: 0, Rival: 0.5, Vassal: 0.5, Suzerain: -0.5}; const stateModifier = { - "melee": {"Nomadic":.5, "Highland":1.2, "Lake":1, "Naval":.7, "Hunting":1.2, "River":1.1}, - "ranged": {"Nomadic":.9, "Highland":1.3, "Lake":1, "Naval":.8, "Hunting":2, "River":.8}, - "mounted": {"Nomadic":2.3, "Highland":.6, "Lake":.7, "Naval":.3, "Hunting":.7, "River":.8}, - "machinery":{"Nomadic":.8, "Highland":1.4, "Lake":1.1, "Naval":1.4, "Hunting":.4, "River":1.1}, - "naval": {"Nomadic":.5, "Highland":.5, "Lake":1.2, "Naval":1.8, "Hunting":.7, "River":1.2}, + melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1}, + ranged: {Nomadic: 0.9, Highland: 1.3, Lake: 1, Naval: 0.8, Hunting: 2, River: 0.8}, + mounted: {Nomadic: 2.3, Highland: 0.6, Lake: 0.7, Naval: 0.3, Hunting: 0.7, River: 0.8}, + machinery: {Nomadic: 0.8, Highland: 1.4, Lake: 1.1, Naval: 1.4, Hunting: 0.4, River: 1.1}, + naval: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.8, Hunting: 0.7, River: 1.2}, // non-default generic: - "armored": {"Nomadic":1, "Highland":.5, "Lake":1, "Naval":1, "Hunting":.7, "River":1.1}, - "aviation": {"Nomadic":.5, "Highland":.5, "Lake":1.2, "Naval":1.2, "Hunting":.6, "River":1.2}, - "magical": {"Nomadic":1, "Highland":2, "Lake":1, "Naval":1, "Hunting":1, "River":1} + armored: {Nomadic: 1, Highland: 0.5, Lake: 1, Naval: 1, Hunting: 0.7, River: 1.1}, + aviation: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.2, Hunting: 0.6, River: 1.2}, + magical: {Nomadic: 1, Highland: 2, Lake: 1, Naval: 1, Hunting: 1, River: 1} }; const cellTypeModifier = { - "nomadic": {"melee":.2, "ranged":.5, "mounted":3, "machinery":.4, "naval":.3, "armored":1.6, "aviation":1, "magical":.5}, - "wetland": {"melee":.8, "ranged":2, "mounted":0.3, "machinery":1.2, "naval":1.0, "armored":0.2, "aviation":.5, "magical":0.5}, - "highland": {"melee":1.2, "ranged":1.6, "mounted":0.3, "machinery":3, "naval":1.0, "armored":0.8, "aviation":.3, "magical":2} - } + nomadic: {melee: 0.2, ranged: 0.5, mounted: 3, machinery: 0.4, naval: 0.3, armored: 1.6, aviation: 1, magical: 0.5}, + wetland: {melee: 0.8, ranged: 2, mounted: 0.3, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5}, + highland: {melee: 1.2, ranged: 1.6, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2} + }; const burgTypeModifier = { - "nomadic": {"melee":.3, "ranged":.8, "mounted":3, "machinery":.4, "naval":1.0, "armored":1.6, "aviation":1, "magical":0.5}, - "wetland": {"melee":1, "ranged":1.6, "mounted":.2, "machinery":1.2, "naval":1.0, "armored":0.2, "aviation":0.5, "magical":0.5}, - "highland": {"melee":1.2, "ranged":2, "mounted":.3, "machinery":3, "naval":1.0, "armored":0.8, "aviation":0.3, "magical":2} - } + nomadic: {melee: 0.3, ranged: 0.8, mounted: 3, machinery: 0.4, naval: 1.0, armored: 1.6, aviation: 1, magical: 0.5}, + wetland: {melee: 1, ranged: 1.6, mounted: 0.2, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5}, + highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2} + }; valid.forEach(s => { - const temp = s.temp = {}, d = s.diplomacy; - const expansionRate = Math.min(Math.max((s.expansionism / expn) / (s.area / area), .25), 4); // how much state expansionism is realized - const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? .8 : d.some(d => d === "Suspicion") ? .5 : .1; // peacefulness - const neighborsRate = Math.min(Math.max(s.neighbors.map(n => n ? pack.states[n].diplomacy[s.i] : "Suspicion").reduce((s, r) => s += rate[r], .5), .3), 3); // neighbors rate - s.alert = Math.min(Math.max(rn(expansionRate * diplomacyRate * neighborsRate, 2), .1), 5); // war alert rate (army modifier) + const temp = (s.temp = {}), + d = s.diplomacy; + const expansionRate = Math.min(Math.max(s.expansionism / expn / (s.area / area), 0.25), 4); // how much state expansionism is realized + const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? 0.8 : d.some(d => d === "Suspicion") ? 0.5 : 0.1; // peacefulness + const neighborsRate = Math.min( + Math.max( + s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5), + 0.3 + ), + 3 + ); // neighbors rate + s.alert = Math.min(Math.max(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1), 5); // war alert rate (army modifier) temp.platoons = []; // apply overall state modifiers for unit types based on state features for (const unit of options.military) { if (!stateModifier[unit.type]) continue; let modifier = stateModifier[unit.type][s.type] || 1; - if (unit.type === "mounted" && s.formName.includes("Horde")) modifier *= 2; else - if (unit.type === "naval" && s.form === "Republic") modifier *= 1.2; + if (unit.type === "mounted" && s.formName.includes("Horde")) modifier *= 2; + else if (unit.type === "naval" && s.form === "Republic") modifier *= 1.2; temp[unit.name] = modifier * s.alert; } - }); const getType = cell => { @@ -62,7 +69,7 @@ if ([7, 8, 9, 12].includes(cells.biome[cell])) return "wetland"; if (cells.h[cell] >= 70) return "highland"; return "generic"; - } + }; for (const i of cells.i) { if (!cells.pop[i]) continue; @@ -79,13 +86,19 @@ const perc = +u.rural; if (isNaN(perc) || perc <= 0 || !s.temp[u.name]) continue; - const mod = type === "generic" ? 1 : cellTypeModifier[type][u.type] // cell specific modifier + const mod = type === "generic" ? 1 : cellTypeModifier[type][u.type]; // cell specific modifier const army = m * perc * mod; // rural cell army - const t = rn(army * s.temp[u.name] * populationRate.value); // total troops + const t = rn(army * s.temp[u.name] * populationRate); // total troops if (!t) continue; - let x = p[i][0], y = p[i][1], n = 0; - if (u.type === "naval") {let haven = cells.haven[i]; x = p[haven][0], y = p[haven][1]; n = 1}; // place naval to sea - s.temp.platoons.push({cell: i, a:t, t, x, y, u:u.name, n, s:u.separate, type:u.type}); + let x = p[i][0], + y = p[i][1], + n = 0; + if (u.type === "naval") { + let haven = cells.haven[i]; + (x = p[haven][0]), (y = p[haven][1]); + n = 1; + } // place naval to sea + s.temp.platoons.push({cell: i, a: t, t, x, y, u: u.name, n, s: u.separate, type: u.type}); } } @@ -93,7 +106,7 @@ if (!b.i || b.removed || !b.state || !b.population) continue; const s = states[b.state]; // burg state - let m = b.population * urbanization.value / 100; // basic urban army in percentages + let m = (b.population * urbanization) / 100; // basic urban army in percentages if (b.capital) m *= 1.2; // capital has household troops if (b.culture !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture if (cells.religion[b.cell] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion @@ -105,25 +118,31 @@ const perc = +u.urban; if (isNaN(perc) || perc <= 0 || !s.temp[u.name]) continue; - const mod = type === "generic" ? 1 : burgTypeModifier[type][u.type] // cell specific modifier + const mod = type === "generic" ? 1 : burgTypeModifier[type][u.type]; // cell specific modifier const army = m * perc * mod; // urban cell army - const t = rn(army * s.temp[u.name] * populationRate.value); // total troops + const t = rn(army * s.temp[u.name] * populationRate); // total troops if (!t) continue; - let x = p[b.cell][0], y = p[b.cell][1], n = 0; - if (u.type === "naval") {let haven = cells.haven[b.cell]; x = p[haven][0], y = p[haven][1]; n = 1}; // place naval in sea cell - s.temp.platoons.push({cell: b.cell, a:t, t, x, y, u:u.name, n, s:u.separate, type:u.type}); + let x = p[b.cell][0], + y = p[b.cell][1], + n = 0; + if (u.type === "naval") { + let haven = cells.haven[b.cell]; + (x = p[haven][0]), (y = p[haven][1]); + n = 1; + } // place naval in sea cell + s.temp.platoons.push({cell: b.cell, a: t, t, x, y, u: u.name, n, s: u.separate, type: u.type}); } } - void function removeExistingRegiments() { - armies.selectAll("g > g").each(function() { + void (function removeExistingRegiments() { + armies.selectAll("g > g").each(function () { const index = notes.findIndex(n => n.id === this.id); if (index != -1) notes.splice(index, 1); }); armies.selectAll("g").remove(); - }() + })(); - const expected = 3 * populationRate.value; // expected regiment size + const expected = 3 * populationRate; // expected regiment size const mergeable = (n0, n1) => (!n0.s && !n1.s) || n0.type === n1.type; // check if regiments can be merged // get regiments for each state @@ -135,34 +154,49 @@ function createRegiments(nodes, s) { if (!nodes.length) return []; - nodes.sort((a,b) => a.a - b.a); // form regiments in cells with most troops - const tree = d3.quadtree(nodes, d => d.x, d => d.y); + nodes.sort((a, b) => a.a - b.a); // form regiments in cells with most troops + const tree = d3.quadtree( + nodes, + d => d.x, + d => d.y + ); nodes.forEach(n => { tree.remove(n); const overlap = tree.find(n.x, n.y, 20); - if (overlap && overlap.t && mergeable(n, overlap)) {merge(n, overlap); return;} + if (overlap && overlap.t && mergeable(n, overlap)) { + merge(n, overlap); + return; + } if (n.t > expected) return; - const r = (expected - n.t) / (n.s?40:20); // search radius + const r = (expected - n.t) / (n.s ? 40 : 20); // search radius const candidates = tree.findAll(n.x, n.y, r); for (const c of candidates) { - if (c.t < expected && mergeable(n, c)) {merge(n, c); break;} + if (c.t < expected && mergeable(n, c)) { + merge(n, c); + break; + } } }); // add n0 to n1's ultimate parent function merge(n0, n1) { - if (!n1.childen) n1.childen = [n0]; else n1.childen.push(n0); + if (!n1.childen) n1.childen = [n0]; + else n1.childen.push(n0); if (n0.childen) n0.childen.forEach(n => n1.childen.push(n)); n1.t += n0.t; n0.t = 0; } // parse regiments data - const regiments = nodes.filter(n => n.t).sort((a,b) => b.t - a.t).map((r, i) => { - const u = {}; u[r.u] = r.a; - (r.childen||[]).forEach(n => u[n.u] = u[n.u] ? u[n.u] += n.a : n.a); - return {i, a:r.t, cell:r.cell, x:r.x, y:r.y, bx:r.x, by:r.y, u, n:r.n, name, state: s.i}; - }); + const regiments = nodes + .filter(n => n.t) + .sort((a, b) => b.t - a.t) + .map((r, i) => { + const u = {}; + u[r.u] = r.a; + (r.childen || []).forEach(n => (u[n.u] = u[n.u] ? (u[n.u] += n.a) : n.a)); + return {i, a: r.t, cell: r.cell, x: r.x, y: r.y, bx: r.x, by: r.y, u, n: r.n, name, state: s.i}; + }); // generate name for regiments regiments.forEach(r => { @@ -175,65 +209,109 @@ } TIME && console.timeEnd("generateMilitaryForces"); - } + }; - const getDefaultOptions = function() { + const getDefaultOptions = function () { return [ - {icon: "⚔️", name:"infantry", rural:.25, urban:.2, crew:1, power:1, type:"melee", separate:0}, - {icon: "🏹", name:"archers", rural:.12, urban:.2, crew:1, power:1, type:"ranged", separate:0}, - {icon: "🐴", name:"cavalry", rural:.12, urban:.03, crew:2, power:2, type:"mounted", separate:0}, - {icon: "💣", name:"artillery", rural:0, urban:.03, crew:8, power:12, type:"machinery", separate:0}, - {icon: "🌊", name:"fleet", rural:0, urban:.015, crew:100, power:50, type:"naval", separate:1} + {icon: "⚔️", name: "infantry", rural: 0.25, urban: 0.2, crew: 1, power: 1, type: "melee", separate: 0}, + {icon: "🏹", name: "archers", rural: 0.12, urban: 0.2, crew: 1, power: 1, type: "ranged", separate: 0}, + {icon: "🐴", name: "cavalry", rural: 0.12, urban: 0.03, crew: 2, power: 2, type: "mounted", separate: 0}, + {icon: "💣", name: "artillery", rural: 0, urban: 0.03, crew: 8, power: 12, type: "machinery", separate: 0}, + {icon: "🌊", name: "fleet", rural: 0, urban: 0.015, crew: 100, power: 50, type: "naval", separate: 1} ]; - } + }; - const drawRegiments = function(regiments, s) { + const drawRegiments = function (regiments, s) { const size = +armies.attr("box-size"); - const w = d => d.n ? size * 4 : size * 6; + const w = d => (d.n ? size * 4 : size * 6); const h = size * 2; const x = d => rn(d.x - w(d) / 2, 2); const y = d => rn(d.y - size, 2); const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999"; const darkerColor = d3.color(baseColor).darker().hex(); - const army = armies.append("g").attr("id", "army"+s).attr("fill", baseColor); + const army = armies + .append("g") + .attr("id", "army" + s) + .attr("fill", baseColor); - const g = army.selectAll("g").data(regiments).enter().append("g") - .attr("id", d => "regiment"+s+"-"+d.i).attr("data-name", d => d.name).attr("data-state", s).attr("data-id", d => d.i); - g.append("rect").attr("x", d => x(d)).attr("y", d => y(d)).attr("width", d => w(d)).attr("height", h); - g.append("text").attr("x", d => d.x).attr("y", d => d.y).text(d => getTotal(d)); - g.append("rect").attr("fill", darkerColor).attr("x", d => x(d)-h).attr("y", d => y(d)).attr("width", h).attr("height", h); - g.append("text").attr("class", "regimentIcon").attr("x", d => x(d)-size).attr("y", d => d.y).text(d => d.icon); - } + const g = army + .selectAll("g") + .data(regiments) + .enter() + .append("g") + .attr("id", d => "regiment" + s + "-" + d.i) + .attr("data-name", d => d.name) + .attr("data-state", s) + .attr("data-id", d => d.i); + g.append("rect") + .attr("x", d => x(d)) + .attr("y", d => y(d)) + .attr("width", d => w(d)) + .attr("height", h); + g.append("text") + .attr("x", d => d.x) + .attr("y", d => d.y) + .text(d => getTotal(d)); + g.append("rect") + .attr("fill", darkerColor) + .attr("x", d => x(d) - h) + .attr("y", d => y(d)) + .attr("width", h) + .attr("height", h); + g.append("text") + .attr("class", "regimentIcon") + .attr("x", d => x(d) - size) + .attr("y", d => d.y) + .text(d => d.icon); + }; - const drawRegiment = function(reg, s) { + const drawRegiment = function (reg, s) { const size = +armies.attr("box-size"); const w = reg.n ? size * 4 : size * 6; const h = size * 2; const x1 = rn(reg.x - w / 2, 2); const y1 = rn(reg.y - size, 2); - let army = armies.select("g#army"+s); + let army = armies.select("g#army" + s); if (!army.size()) { const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999"; - army = armies.append("g").attr("id", "army"+s).attr("fill", baseColor); + army = armies + .append("g") + .attr("id", "army" + s) + .attr("fill", baseColor); } const darkerColor = d3.color(army.attr("fill")).darker().hex(); - const g = army.append("g").attr("id", "regiment"+s+"-"+reg.i).attr("data-name", reg.name).attr("data-state", s).attr("data-id", reg.i); + const g = army + .append("g") + .attr("id", "regiment" + s + "-" + reg.i) + .attr("data-name", reg.name) + .attr("data-state", s) + .attr("data-id", reg.i); g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h); g.append("text").attr("x", reg.x).attr("y", reg.y).text(getTotal(reg)); - g.append("rect").attr("fill", darkerColor).attr("x", x1-h).attr("y", y1).attr("width", h).attr("height", h); - g.append("text").attr("class", "regimentIcon").attr("x", x1-size).attr("y", reg.y).text(reg.icon); - } + g.append("rect") + .attr("fill", darkerColor) + .attr("x", x1 - h) + .attr("y", y1) + .attr("width", h) + .attr("height", h); + g.append("text") + .attr("class", "regimentIcon") + .attr("x", x1 - size) + .attr("y", reg.y) + .text(reg.icon); + }; // move one regiment to another - const moveRegiment = function(reg, x, y) { - const el = armies.select("g#army"+reg.state).select("g#regiment"+reg.state+"-"+reg.i); + const moveRegiment = function (reg, x, y) { + const el = armies.select("g#army" + reg.state).select("g#regiment" + reg.state + "-" + reg.i); if (!el.size()) return; const duration = Math.hypot(reg.x - x, reg.y - y) * 8; - reg.x = x; reg.y = y; + reg.x = x; + reg.y = y; const size = +armies.attr("box-size"); const w = reg.n ? size * 4 : size * 6; const h = size * 2; @@ -243,48 +321,54 @@ const move = d3.transition().duration(duration).ease(d3.easeSinInOut); el.select("rect").transition(move).attr("x", x1(x)).attr("y", y1(y)); el.select("text").transition(move).attr("x", x).attr("y", y); - el.selectAll("rect:nth-of-type(2)").transition(move).attr("x", x1(x)-h).attr("y", y1(y)); - el.select(".regimentIcon").transition(move).attr("x", x1(x)-size).attr("y", y); - } + el.selectAll("rect:nth-of-type(2)") + .transition(move) + .attr("x", x1(x) - h) + .attr("y", y1(y)); + el.select(".regimentIcon") + .transition(move) + .attr("x", x1(x) - size) + .attr("y", y); + }; // utilize si function to make regiment total text fit regiment box - const getTotal = reg => reg.a > (reg.n ? 999 : 99999) ? si(reg.a) : reg.a; + const getTotal = reg => (reg.a > (reg.n ? 999 : 99999) ? si(reg.a) : reg.a); - const getName = function(r, regiments) { + const getName = function (r, regiments) { const cells = pack.cells; - const proper = r.n ? null : - cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].name : - cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : null - const number = nth(regiments.filter(reg => reg.n === r.n && reg.i < r.i).length+1); + const proper = r.n ? null : cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].name : cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : null; + const number = nth(regiments.filter(reg => reg.n === r.n && reg.i < r.i).length + 1); const form = r.n ? "Fleet" : "Regiment"; - return `${number}${proper?` (${proper}) `:` `}${form}`; - } + return `${number}${proper ? ` (${proper}) ` : ` `}${form}`; + }; // get default regiment emblem - const getEmblem = function(r) { + const getEmblem = function (r) { if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops if (!r.n && pack.states[r.state].form === "Monarchy" && pack.cells.burg[r.cell] && pack.burgs[pack.cells.burg[r.cell]].capital) return "👑"; // "Royal" regiment based in capital - const mainUnit = Object.entries(r.u).sort((a,b) => b[1]-a[1])[0][0]; // unit with more troops in regiment + const mainUnit = Object.entries(r.u).sort((a, b) => b[1] - a[1])[0][0]; // unit with more troops in regiment const unit = options.military.find(u => u.name === mainUnit); return unit.icon; - } + }; - const generateNote = function(r, s) { + const generateNote = function (r, s) { const cells = pack.cells; - const base = cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : - cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].fullName : null; + const base = cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].fullName : null; const station = base ? `${r.name} is ${r.n ? "based" : "stationed"} in ${base}. ` : ""; - const composition = r.a ? Object.keys(r.u).map(t => `— ${t}: ${r.u[t]}`).join("\r\n") : null; + const composition = r.a + ? Object.keys(r.u) + .map(t => `— ${t}: ${r.u[t]}`) + .join("\r\n") + : null; const troops = composition ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.` : ""; const campaign = s.campaigns ? ra(s.campaigns) : null; - const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year-100, 150, 1, options.year-6); + const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6); const conflict = campaign ? ` during the ${campaign.name}` : ""; const legend = `Regiment was formed in ${year} ${options.era}${conflict}. ${station}${troops}`; - notes.push({id:`regiment${s.i}-${r.i}`, name:`${r.icon} ${r.name}`, legend}); - } + notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend}); + }; return {generate, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, moveRegiment, getTotal, getEmblem}; - -}))); \ No newline at end of file +}); diff --git a/modules/save.js b/modules/save.js index d612799c..7d109e10 100644 --- a/modules/save.js +++ b/modules/save.js @@ -417,7 +417,7 @@ function getMapData() { const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(); const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator"; const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|"); - const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value, barSizeInput.value, barLabel.value, barBackOpacity.value, barBackColor.value, barPosX.value, barPosY.value, populationRate.value, urbanization.value, mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value, temperaturePoleOutput.value, precOutput.value, JSON.stringify(options), mapName.value, +hideLabels.checked].join("|"); + const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value, barSizeInput.value, barLabel.value, barBackOpacity.value, barBackColor.value, barPosX.value, barPosY.value, populationRate, urbanization, mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value, temperaturePoleOutput.value, precOutput.value, JSON.stringify(options), mapName.value, +hideLabels.checked].join("|"); const coords = JSON.stringify(mapCoordinates); const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|"); const notesData = JSON.stringify(notes); diff --git a/modules/ui/battle-screen.js b/modules/ui/battle-screen.js index c87bf0f4..778866ad 100644 --- a/modules/ui/battle-screen.js +++ b/modules/ui/battle-screen.js @@ -1,6 +1,5 @@ "use strict"; class Battle { - constructor(attacker, defender) { if (customization) return; closeDialogs(".stable"); @@ -11,8 +10,8 @@ class Battle { this.x = defender.x; this.y = defender.y; this.cell = findCell(this.x, this.y); - this.attackers = {regiments:[], distances:[], morale:100, casualties:0, power:0}; - this.defenders = {regiments:[], distances:[], morale:100, casualties:0, power:0}; + this.attackers = {regiments: [], distances: [], morale: 100, casualties: 0, power: 0}; + this.defenders = {regiments: [], distances: [], morale: 100, casualties: 0, power: 0}; this.addHeaders(); this.addRegiment("attackers", attacker); @@ -26,7 +25,9 @@ class Battle { this.getInitialMorale(); $("#battleScreen").dialog({ - title: this.name, resizable: false, width: fitContent(), + title: this.name, + resizable: false, + width: fitContent(), position: {my: "center", at: "center", of: "#map"}, close: () => Battle.prototype.context.cancelResults() }); @@ -38,7 +39,7 @@ class Battle { document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev)); document.getElementById("battleType").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev)); document.getElementById("battleNameShow").addEventListener("click", () => Battle.prototype.context.showNameSection()); - document.getElementById("battleNamePlace").addEventListener("change", ev => Battle.prototype.context.place = ev.target.value); + document.getElementById("battleNamePlace").addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value)); document.getElementById("battleNameFull").addEventListener("change", ev => Battle.prototype.context.changeName(ev)); document.getElementById("battleNameCulture").addEventListener("click", () => Battle.prototype.context.generateName("culture")); document.getElementById("battleNameRandom").addEventListener("click", () => Battle.prototype.context.generateName("random")); @@ -69,9 +70,9 @@ class Battle { if (typesA.every(t => t === "aviation") && typesD.every(t => t === "aviation")) return "air"; // if attackers and defender have only aviation units if (attacker.n && !defender.n && typesA.some(t => t !== "naval")) return "landing"; // if attacked is naval with non-naval units and defender is not naval if (!defender.n && pack.burgs[pack.cells.burg[this.cell]].walls) return "siege"; // defender is in walled town - if (P(.1) && [5,6,7,8,9,12].includes(pack.cells.biome[this.cell])) return "ambush"; // 20% if defenders are in forest or marshes + if (P(0.1) && [5, 6, 7, 8, 9, 12].includes(pack.cells.biome[this.cell])) return "ambush"; // 20% if defenders are in forest or marshes return "field"; - } + }; this.type = getType(); this.setType(); @@ -80,9 +81,9 @@ class Battle { setType() { document.getElementById("battleType").className = "icon-button-" + this.type; - const sideSpecific = document.getElementById("battlePhases_"+this.type+"_attackers"); - const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_"+this.type).content; - const defenders = sideSpecific ? document.getElementById("battlePhases_"+this.type+"_defenders").content : attackers; + const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers"); + const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_" + this.type).content; + const defenders = sideSpecific ? document.getElementById("battlePhases_" + this.type + "_defenders").content : attackers; document.getElementById("battlePhase_attackers").nextElementSibling.innerHTML = ""; document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = ""; @@ -91,9 +92,13 @@ class Battle { } definePlace() { - const cells = pack.cells, i = this.cell; + const cells = pack.cells, + i = this.cell; const burg = cells.burg[i] ? pack.burgs[cells.burg[i]].name : null; - const getRiver = i => {const river = pack.rivers.find(r => r.i === i); return river.name + " " + river.type}; + const getRiver = i => { + const river = pack.rivers.find(r => r.i === i); + return river.name + " " + river.type; + }; const river = !burg && cells.r[i] ? getRiver(cells.r[i]) : null; const proper = burg || river ? null : Names.getCulture(cells.culture[this.cell]); return burg ? burg : river ? river : proper; @@ -102,10 +107,10 @@ class Battle { defineName() { if (this.type === "field") return "Battle of " + this.place; if (this.type === "naval") return "Naval Battle of " + this.place; - if (this.type === "siege") return "Siege of "+ this.place; + if (this.type === "siege") return "Siege of " + this.place; if (this.type === "ambush") return this.place + " Ambush"; if (this.type === "landing") return this.place + " Landing"; - if (this.type === "air") return `${this.place} ${P(.8) ? "Air Battle" : "Dogfight"}`; + if (this.type === "air") return `${this.place} ${P(0.8) ? "Air Battle" : "Dogfight"}`; } getTypeName() { @@ -121,7 +126,7 @@ class Battle { let headers = ""; for (const u of options.military) { - const label = capitalize(u.name.replace(/_/g, ' ')); + const label = capitalize(u.name.replace(/_/g, " ")); headers += `${u.icon}`; } @@ -130,11 +135,11 @@ class Battle { } addRegiment(side, regiment) { - regiment.casualties = Object.keys(regiment.u).reduce((a,b) => (a[b]=0,a), {}); + regiment.casualties = Object.keys(regiment.u).reduce((a, b) => ((a[b] = 0), a), {}); regiment.survivors = Object.assign({}, regiment.u); const state = pack.states[regiment.state]; - const distance = Math.hypot(this.y-regiment.by, this.x-regiment.bx) * distanceScaleInput.value | 0; // distance between regiment and its base + const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScaleInput.value) | 0; // distance between regiment and its base const color = state.color[0] === "#" ? state.color : "#999"; const icon = ` @@ -146,14 +151,14 @@ class Battle { let survivors = `
Distance to base: ${distance} ${distanceUnitInput.value}`; for (const u of options.military) { - initial += `${regiment.u[u.name]||0}`; + initial += `${regiment.u[u.name] || 0}`; casualties += `0`; - survivors += `${regiment.u[u.name]||0}`; + survivors += `${regiment.u[u.name] || 0}`; } - initial += `${regiment.a||0}
`; + initial += `${regiment.a || 0}`; casualties += `0`; - survivors += `${regiment.a||0}`; + survivors += `${regiment.a || 0}`; const div = side === "attackers" ? battleAttackers : battleDefenders; div.innerHTML += body + initial + casualties + survivors + ""; @@ -164,13 +169,19 @@ class Battle { addSide() { const body = document.getElementById("regimentSelectorBody"); const context = Battle.prototype.context; - const regiments = pack.states.filter(s => s.military && !s.removed).map(s => s.military).flat(); - const distance = reg => rn(Math.hypot(context.y-reg.y, context.x-reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value; + const regiments = pack.states + .filter(s => s.military && !s.removed) + .map(s => s.military) + .flat(); + const distance = reg => rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value; const isAdded = reg => context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === reg); - body.innerHTML = regiments.map(r => { - const s = pack.states[r.state], added = isAdded(r), dist = added ? "0 " + distanceUnitInput.value : distance(r); - return `
{ + const s = pack.states[r.state], + added = isAdded(r), + dist = added ? "0 " + distanceUnitInput.value : distance(r); + return `
${s.name.slice(0, 11)}
@@ -179,11 +190,15 @@ class Battle {
${r.a}
${dist}
`; - }).join(""); + }) + .join(""); $("#regimentSelectorScreen").dialog({ - resizable: false, width: fitContent(), title: "Add regiment to the battle", - position: {my: "left center", at: "right+10 center", of: "#battleScreen"}, close: addSideClosed, + resizable: false, + width: fitContent(), + title: "Add regiment to the battle", + position: {my: "left center", at: "right+10 center", of: "#battleScreen"}, + close: addSideClosed, buttons: { "Add to attackers": () => addSideClicked("attackers"), "Add to defenders": () => addSideClicked("defenders"), @@ -195,13 +210,19 @@ class Battle { body.addEventListener("click", selectLine); function selectLine(ev) { - if (ev.target.className === "inactive") {tip("Regiment is already in the battle", false, "error"); return}; + if (ev.target.className === "inactive") { + tip("Regiment is already in the battle", false, "error"); + return; + } ev.target.classList.toggle("selected"); } function addSideClicked(side) { const selected = body.querySelectorAll(".selected"); - if (!selected.length) {tip("Please select a regiment first", false, "error"); return} + if (!selected.length) { + tip("Please select a regiment first", false, "error"); + return; + } $("#regimentSelectorScreen").dialog("close"); selected.forEach(line => { @@ -212,8 +233,9 @@ class Battle { Battle.prototype.getInitialMorale.call(context); // move regiment - const defenders = context.defenders.regiments, attackers = context.attackers.regiments; - const shift = side === "attackers" ? attackers.length * -8 : (defenders.length-1) * 8; + const defenders = context.defenders.regiments, + attackers = context.attackers.regiments; + const shift = side === "attackers" ? attackers.length * -8 : (defenders.length - 1) * 8; regiment.px = regiment.x; regiment.py = regiment.y; Military.moveRegiment(regiment, defenders[0].x, defenders[0].y + shift); @@ -227,7 +249,7 @@ class Battle { } showNameSection() { - document.querySelectorAll("#battleBottom > button").forEach(el => el.style.display = "none"); + document.querySelectorAll("#battleBottom > button").forEach(el => (el.style.display = "none")); document.getElementById("battleNameSection").style.display = "inline-block"; document.getElementById("battleNamePlace").value = this.place; @@ -235,22 +257,20 @@ class Battle { } hideNameSection() { - document.querySelectorAll("#battleBottom > button").forEach(el => el.style.display = "inline-block"); + document.querySelectorAll("#battleBottom > button").forEach(el => (el.style.display = "inline-block")); document.getElementById("battleNameSection").style.display = "none"; } changeName(ev) { this.name = ev.target.value; - $("#battleScreen").dialog({"title":this.name}); + $("#battleScreen").dialog({title: this.name}); } generateName(type) { - const place = type === "culture" - ? Names.getCulture(pack.cells.culture[this.cell], null, null, "") - : Names.getBase(rand(nameBases.length-1)); + const place = type === "culture" ? Names.getCulture(pack.cells.culture[this.cell], null, null, "") : Names.getBase(rand(nameBases.length - 1)); document.getElementById("battleNamePlace").value = this.place = place; document.getElementById("battleNameFull").value = this.name = this.defineName(); - $("#battleScreen").dialog({"title":this.name}); + $("#battleScreen").dialog({title: this.name}); } getJoinedForces(regiments) { @@ -266,47 +286,47 @@ class Battle { calculateStrength(side) { const scheme = { // field battle phases - "skirmish": {"melee":.2, "ranged":2.4, "mounted":.1, "machinery":3, "naval":1, "armored":.2, "aviation":1.8, "magical":1.8}, // ranged excel - "melee": {"melee":2, "ranged":1.2, "mounted":1.5, "machinery":.5, "naval":.2, "armored":2, "aviation":.8, "magical":.8}, // melee excel - "pursue": {"melee":1, "ranged":1, "mounted":4, "machinery":.05, "naval":1, "armored":1, "aviation":1.5, "magical":.6}, // mounted excel - "retreat": {"melee":.1, "ranged":.01, "mounted":.5, "machinery":.01, "naval":.2, "armored":.1, "aviation":.8, "magical":.05}, // reduced + skirmish: {melee: 0.2, ranged: 2.4, mounted: 0.1, machinery: 3, naval: 1, armored: 0.2, aviation: 1.8, magical: 1.8}, // ranged excel + melee: {melee: 2, ranged: 1.2, mounted: 1.5, machinery: 0.5, naval: 0.2, armored: 2, aviation: 0.8, magical: 0.8}, // melee excel + pursue: {melee: 1, ranged: 1, mounted: 4, machinery: 0.05, naval: 1, armored: 1, aviation: 1.5, magical: 0.6}, // mounted excel + retreat: {melee: 0.1, ranged: 0.01, mounted: 0.5, machinery: 0.01, naval: 0.2, armored: 0.1, aviation: 0.8, magical: 0.05}, // reduced // naval battle phases - "shelling": {"melee":0, "ranged":.2, "mounted":0, "machinery":2, "naval":2, "armored":0, "aviation":.1, "magical":.5}, // naval and machinery excel - "boarding": {"melee":1, "ranged":.5, "mounted":.5, "machinery":0, "naval":.5, "armored":.4, "aviation":0, "magical":.2}, // melee excel - "chase": {"melee":0, "ranged":.15, "mounted":0, "machinery":1, "naval":1, "armored":0, "aviation":.15, "magical":.5}, // reduced - "withdrawal": {"melee":0, "ranged":.02, "mounted":0, "machinery":.5, "naval":.1, "armored":0, "aviation":.1, "magical":.3}, // reduced + shelling: {melee: 0, ranged: 0.2, mounted: 0, machinery: 2, naval: 2, armored: 0, aviation: 0.1, magical: 0.5}, // naval and machinery excel + boarding: {melee: 1, ranged: 0.5, mounted: 0.5, machinery: 0, naval: 0.5, armored: 0.4, aviation: 0, magical: 0.2}, // melee excel + chase: {melee: 0, ranged: 0.15, mounted: 0, machinery: 1, naval: 1, armored: 0, aviation: 0.15, magical: 0.5}, // reduced + withdrawal: {melee: 0, ranged: 0.02, mounted: 0, machinery: 0.5, naval: 0.1, armored: 0, aviation: 0.1, magical: 0.3}, // reduced // siege phases - "blockade": {"melee":.25, "ranged":.25, "mounted":.2, "machinery":.5, "naval":.2, "armored":.1, "aviation":.25, "magical":.25}, // no active actions - "sheltering": {"melee":.3, "ranged":.5, "mounted":.2, "machinery":.5, "naval":.2, "armored":.1, "aviation":.25, "magical":.25}, // no active actions - "sortie": {"melee":2, "ranged":.5, "mounted":1.2, "machinery":.2, "naval":.1, "armored":.5, "aviation":1, "magical":1}, // melee excel - "bombardment": {"melee":.2, "ranged":.5, "mounted":.2, "machinery":3, "naval":1, "armored":.5, "aviation":1, "magical":1}, // machinery excel - "storming": {"melee":1, "ranged":.6, "mounted":.5, "machinery":1, "naval":.1, "armored":.1, "aviation":.5, "magical":.5}, // melee excel - "defense": {"melee":2, "ranged":3, "mounted":1, "machinery":1, "naval":.1, "armored":1, "aviation":.5, "magical":1}, // ranged excel - "looting": {"melee":1.6, "ranged":1.6, "mounted":.5, "machinery":.2, "naval":.02, "armored":.2, "aviation":.1, "magical":.3}, // melee excel - "surrendering": {"melee":.1, "ranged":.1, "mounted":.05, "machinery":.01, "naval":.01, "armored":.02, "aviation":.01, "magical":.03}, // reduced + blockade: {melee: 0.25, ranged: 0.25, mounted: 0.2, machinery: 0.5, naval: 0.2, armored: 0.1, aviation: 0.25, magical: 0.25}, // no active actions + sheltering: {melee: 0.3, ranged: 0.5, mounted: 0.2, machinery: 0.5, naval: 0.2, armored: 0.1, aviation: 0.25, magical: 0.25}, // no active actions + sortie: {melee: 2, ranged: 0.5, mounted: 1.2, machinery: 0.2, naval: 0.1, armored: 0.5, aviation: 1, magical: 1}, // melee excel + bombardment: {melee: 0.2, ranged: 0.5, mounted: 0.2, machinery: 3, naval: 1, armored: 0.5, aviation: 1, magical: 1}, // machinery excel + storming: {melee: 1, ranged: 0.6, mounted: 0.5, machinery: 1, naval: 0.1, armored: 0.1, aviation: 0.5, magical: 0.5}, // melee excel + defense: {melee: 2, ranged: 3, mounted: 1, machinery: 1, naval: 0.1, armored: 1, aviation: 0.5, magical: 1}, // ranged excel + looting: {melee: 1.6, ranged: 1.6, mounted: 0.5, machinery: 0.2, naval: 0.02, armored: 0.2, aviation: 0.1, magical: 0.3}, // melee excel + surrendering: {melee: 0.1, ranged: 0.1, mounted: 0.05, machinery: 0.01, naval: 0.01, armored: 0.02, aviation: 0.01, magical: 0.03}, // reduced // ambush phases - "surprise": {"melee":2, "ranged":2.4, "mounted":1, "machinery":1, "naval":1, "armored":1, "aviation":.8, "magical":1.2}, // increased - "shock": {"melee":.5, "ranged":.5, "mounted":.5, "machinery":.4, "naval":.3, "armored":.1, "aviation":.4, "magical":.5}, // reduced + surprise: {melee: 2, ranged: 2.4, mounted: 1, machinery: 1, naval: 1, armored: 1, aviation: 0.8, magical: 1.2}, // increased + shock: {melee: 0.5, ranged: 0.5, mounted: 0.5, machinery: 0.4, naval: 0.3, armored: 0.1, aviation: 0.4, magical: 0.5}, // reduced // langing phases - "landing": {"melee":.8, "ranged":.6, "mounted":.6, "machinery":.5, "naval":.5, "armored":.5, "aviation":.5, "magical":.6}, // reduced - "flee": {"melee":.1, "ranged":.01, "mounted":.5, "machinery":.01, "naval":.5, "armored":.1, "aviation":.2, "magical":.05}, // reduced - "waiting": {"melee":.05, "ranged":.5, "mounted":.05, "machinery":.5, "naval":2, "armored":.05, "aviation":.5, "magical":.5}, // reduced + landing: {melee: 0.8, ranged: 0.6, mounted: 0.6, machinery: 0.5, naval: 0.5, armored: 0.5, aviation: 0.5, magical: 0.6}, // reduced + flee: {melee: 0.1, ranged: 0.01, mounted: 0.5, machinery: 0.01, naval: 0.5, armored: 0.1, aviation: 0.2, magical: 0.05}, // reduced + waiting: {melee: 0.05, ranged: 0.5, mounted: 0.05, machinery: 0.5, naval: 2, armored: 0.05, aviation: 0.5, magical: 0.5}, // reduced // air battle phases - "maneuvering": {"melee":0, "ranged":.1, "mounted":0, "machinery":.2, "naval":0, "armored":0, "aviation":1, "magical":.2}, // aviation - "dogfight": {"melee":0, "ranged":.1, "mounted":0, "machinery":.1, "naval":0, "armored":0, "aviation":2, "magical":.1} // aviation + maneuvering: {melee: 0, ranged: 0.1, mounted: 0, machinery: 0.2, naval: 0, armored: 0, aviation: 1, magical: 0.2}, // aviation + dogfight: {melee: 0, ranged: 0.1, mounted: 0, machinery: 0.1, naval: 0, armored: 0, aviation: 2, magical: 0.1} // aviation }; const forces = this.getJoinedForces(this[side].regiments); const phase = this[side].phase; - const adjuster = Math.max(populationRate.value / 10, 10); // population adjuster, by default 100 + const adjuster = Math.max(populationRate / 10, 10); // population adjuster, by default 100 this[side].power = d3.sum(options.military.map(u => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster; - const UIvalue = this[side].power ? Math.max(this[side].power|0, 1) : 0; - document.getElementById("battlePower_"+side).innerHTML = UIvalue; + const UIvalue = this[side].power ? Math.max(this[side].power | 0, 1) : 0; + document.getElementById("battlePower_" + side).innerHTML = UIvalue; } getInitialMorale() { @@ -320,7 +340,7 @@ class Battle { } updateMorale(side) { - const morale = document.getElementById("battleMorale_"+side); + const morale = document.getElementById("battleMorale_" + side); morale.dataset.tip = morale.dataset.tip.replace(morale.value, ""); morale.value = this[side].morale | 0; morale.dataset.tip += morale.value; @@ -335,9 +355,11 @@ class Battle { } rollDie(side) { - const el = document.getElementById("battleDie_"+side); + const el = document.getElementById("battleDie_" + side); const prev = +el.innerHTML; - do {el.innerHTML = rand(1, 6)} while (el.innerHTML == prev) + do { + el.innerHTML = rand(1, 6); + } while (el.innerHTML == prev); this[side].die = +el.innerHTML; } @@ -357,12 +379,18 @@ class Battle { if (prev[0] === "skirmish" && prev[1] === "skirmish") { const forces = this.getJoinedForces(this.attackers.regiments.concat(this.defenders.regiments)); const total = d3.sum(Object.values(forces)); // total forces - const ranged = d3.sum(options.military.filter(u => u.type === "ranged").map(u => u.name).map(u => forces[u])) / total; // ranged units - if (P(ranged) || P(.8-i/10)) return ["skirmish", "skirmish"]; + const ranged = + d3.sum( + options.military + .filter(u => u.type === "ranged") + .map(u => u.name) + .map(u => forces[u]) + ) / total; // ranged units + if (P(ranged) || P(0.8 - i / 10)) return ["skirmish", "skirmish"]; } return ["melee", "melee"]; // default option - } + }; const getNavalBattlePhase = () => { const prev = [this.attackers.phase || "shelling", this.defenders.phase || "shelling"]; // previous phase @@ -372,66 +400,66 @@ class Battle { // withdrawal phase when power imbalanced if (!prev[0] === "boarding") { - if (powerRatio < .5 || P(this.attackers.casualties) && powerRatio < 1) return ["withdrawal", "chase"]; - if (powerRatio > 2 || P(this.defenders.casualties) && powerRatio > 1) return ["chase", "withdrawal"]; + if (powerRatio < 0.5 || (P(this.attackers.casualties) && powerRatio < 1)) return ["withdrawal", "chase"]; + if (powerRatio > 2 || (P(this.defenders.casualties) && powerRatio > 1)) return ["chase", "withdrawal"]; } // boarding phase can start from 2nd iteration - if (prev[0] === "boarding" || P(i/10 - .1)) return ["boarding", "boarding"]; + if (prev[0] === "boarding" || P(i / 10 - 0.1)) return ["boarding", "boarding"]; return ["shelling", "shelling"]; // default option - } + }; const getSiegePhase = () => { const prev = [this.attackers.phase || "blockade", this.defenders.phase || "sheltering"]; // previous phase - let phase = ["blockade", "sheltering"] // default phase + let phase = ["blockade", "sheltering"]; // default phase if (prev[0] === "retreat" || prev[0] === "looting") return prev; if (P(1 - morale[0] / 30) && powerRatio < 1) return ["retreat", "pursue"]; // attackers retreat chance if moral < 30 if (P(1 - morale[1] / 15)) return ["looting", "surrendering"]; // defenders surrendering chance if moral < 15 - if (P((powerRatio-1) / 2)) return ["storming", "defense"]; // start storm + if (P((powerRatio - 1) / 2)) return ["storming", "defense"]; // start storm if (prev[0] !== "storming") { const machinery = options.military.filter(u => u.type === "machinery").map(u => u.name); // machinery units const attackers = this.getJoinedForces(this.attackers.regiments); const machineryA = d3.sum(machinery.map(u => attackers[u])); - if (i && machineryA && P(.9)) phase[0] = "bombardment"; + if (i && machineryA && P(0.9)) phase[0] = "bombardment"; const defenders = this.getJoinedForces(this.defenders.regiments); const machineryD = d3.sum(machinery.map(u => defenders[u])); - if (machineryD && P(.9)) phase[1] = "bombardment"; + if (machineryD && P(0.9)) phase[1] = "bombardment"; - if (i && prev[1] !== "sortie" && machineryD < machineryA && P(.25) && P(morale[1]/70)) phase[1] = "sortie"; // defenders sortie + if (i && prev[1] !== "sortie" && machineryD < machineryA && P(0.25) && P(morale[1] / 70)) phase[1] = "sortie"; // defenders sortie } return phase; - } + }; const getAmbushPhase = () => { const prev = [this.attackers.phase || "shock", this.defenders.phase || "surprise"]; // previous phase - if (prev[1] === "surprise" && P(1-powerRatio*i/5)) return ["shock", "surprise"]; + if (prev[1] === "surprise" && P(1 - (powerRatio * i) / 5)) return ["shock", "surprise"]; // chance if moral < 25 if (P(1 - morale[0] / 25)) return ["retreat", "pursue"]; if (P(1 - morale[1] / 25)) return ["pursue", "retreat"]; return ["melee", "melee"]; // default option - } + }; const getLandingPhase = () => { const prev = [this.attackers.phase || "landing", this.defenders.phase || "defense"]; // previous phase if (prev[1] === "waiting") return ["flee", "waiting"]; - if (prev[1] === "pursue") return ["flee", P(.3) ? "pursue" : "waiting"]; + if (prev[1] === "pursue") return ["flee", P(0.3) ? "pursue" : "waiting"]; if (prev[1] === "retreat") return ["pursue", "retreat"]; if (prev[0] === "landing") { - const attackers = P(i/2) ? "melee" : "landing"; - const defenders = i ? prev[1] : P(.5) ? "defense" : "shock"; + const attackers = P(i / 2) ? "melee" : "landing"; + const defenders = i ? prev[1] : P(0.5) ? "defense" : "shock"; return [attackers, defenders]; } @@ -439,7 +467,7 @@ class Battle { if (P(1 - morale[1] / 25)) return ["pursue", "retreat"]; // chance if moral < 25 return ["melee", "melee"]; // default option - } + }; const getAirBattlePhase = () => { const prev = [this.attackers.phase || "maneuvering", this.defenders.phase || "maneuvering"]; // previous phase @@ -448,53 +476,87 @@ class Battle { if (P(1 - morale[0] / 25)) return ["retreat", "pursue"]; if (P(1 - morale[1] / 25)) return ["pursue", "retreat"]; - if (prev[0] === "maneuvering" && P(1-i/10)) return ["maneuvering", "maneuvering"]; + if (prev[0] === "maneuvering" && P(1 - i / 10)) return ["maneuvering", "maneuvering"]; return ["dogfight", "dogfight"]; // default option - } + }; - const phase = function(type) { + const phase = (function (type) { switch (type) { - case "field": return getFieldBattlePhase(); - case "naval": return getNavalBattlePhase(); - case "siege": return getSiegePhase(); - case "ambush": return getAmbushPhase(); - case "landing": return getLandingPhase(); - case "air": return getAirBattlePhase(); - default: getFieldBattlePhase(); + case "field": + return getFieldBattlePhase(); + case "naval": + return getNavalBattlePhase(); + case "siege": + return getSiegePhase(); + case "ambush": + return getAmbushPhase(); + case "landing": + return getLandingPhase(); + case "air": + return getAirBattlePhase(); + default: + getFieldBattlePhase(); } - }(this.type); + })(this.type); this.attackers.phase = phase[0]; this.defenders.phase = phase[1]; const buttonA = document.getElementById("battlePhase_attackers"); buttonA.className = "icon-button-" + this.attackers.phase; - buttonA.dataset.tip = buttonA.nextElementSibling.querySelector("[data-phase='"+phase[0]+"']").dataset.tip; + buttonA.dataset.tip = buttonA.nextElementSibling.querySelector("[data-phase='" + phase[0] + "']").dataset.tip; const buttonD = document.getElementById("battlePhase_defenders"); buttonD.className = "icon-button-" + this.defenders.phase; - buttonD.dataset.tip = buttonD.nextElementSibling.querySelector("[data-phase='"+phase[1]+"']").dataset.tip; + buttonD.dataset.tip = buttonD.nextElementSibling.querySelector("[data-phase='" + phase[1] + "']").dataset.tip; } run() { // validations - if (!this.attackers.power) {tip("Attackers army destroyed", false, "warn"); return} - if (!this.defenders.power) {tip("Defenders army destroyed", false, "warn"); return} + if (!this.attackers.power) { + tip("Attackers army destroyed", false, "warn"); + return; + } + if (!this.defenders.power) { + tip("Defenders army destroyed", false, "warn"); + return; + } // calculate casualties - const attack = this.attackers.power * (this.attackers.die / 10 + .4); - const defense = this.defenders.power * (this.defenders.die / 10 + .4); + const attack = this.attackers.power * (this.attackers.die / 10 + 0.4); + const defense = this.defenders.power * (this.defenders.die / 10 + 0.4); // casualties modifier for phase const phase = { - "skirmish":.1, "melee":.2, "pursue":.3, "retreat":.3, "boarding":.2, "shelling":.1, "chase":.03, "withdrawal": .03, - "blockade":0, "sheltering":0, "sortie":.1, "bombardment":.05, "storming":.2, "defense":.2, "looting":.5, "surrendering":.5, - "surprise":.3, "shock":.3, "landing":.3, "flee":0, "waiting":0, "maneuvering":.1, "dogfight":.2}; + skirmish: 0.1, + melee: 0.2, + pursue: 0.3, + retreat: 0.3, + boarding: 0.2, + shelling: 0.1, + chase: 0.03, + withdrawal: 0.03, + blockade: 0, + sheltering: 0, + sortie: 0.1, + bombardment: 0.05, + storming: 0.2, + defense: 0.2, + looting: 0.5, + surrendering: 0.5, + surprise: 0.3, + shock: 0.3, + landing: 0.3, + flee: 0, + waiting: 0, + maneuvering: 0.1, + dogfight: 0.2 + }; - const casualties = Math.random() * (Math.max(phase[this.attackers.phase], phase[this.defenders.phase])); // total casualties, ~10% per iteration - const casualtiesA = casualties * defense / (attack + defense); // attackers casualties, ~5% per iteration - const casualtiesD = casualties * attack / (attack + defense); // defenders casualties, ~5% per iteration + const casualties = Math.random() * Math.max(phase[this.attackers.phase], phase[this.defenders.phase]); // total casualties, ~10% per iteration + const casualtiesA = (casualties * defense) / (attack + defense); // attackers casualties, ~5% per iteration + const casualtiesD = (casualties * attack) / (attack + defense); // defenders casualties, ~5% per iteration this.calculateCasualties("attackers", casualtiesA); this.calculateCasualties("defenders", casualtiesD); @@ -519,7 +581,7 @@ class Battle { calculateCasualties(side, casualties) { for (const r of this[side].regiments) { for (const unit in r.u) { - const rand = .8 + Math.random() * .4; + const rand = 0.8 + Math.random() * 0.4; const died = Math.min(Pint(r.u[unit] * casualties * rand), r.survivors[unit]); r.casualties[unit] -= died; r.survivors[unit] -= died; @@ -551,10 +613,16 @@ class Battle { const button = ev.target; const div = button.nextElementSibling; - const hideSection = function() {button.style.opacity = 1; div.style.display = "none"} - if (div.style.display === "block") {hideSection(); return} + const hideSection = function () { + button.style.opacity = 1; + div.style.display = "none"; + }; + if (div.style.display === "block") { + hideSection(); + return; + } - button.style.opacity = .5; + button.style.opacity = 0.5; div.style.display = "block"; document.getElementsByTagName("body")[0].addEventListener("click", hideSection, {once: true}); @@ -568,13 +636,13 @@ class Battle { this.calculateStrength("attackers"); this.calculateStrength("defenders"); this.name = this.defineName(); - $("#battleScreen").dialog({"title":this.name}); + $("#battleScreen").dialog({title: this.name}); } changePhase(ev, side) { if (ev.target.tagName !== "BUTTON") return; - const phase = this[side].phase = ev.target.dataset.phase; - const button = document.getElementById("battlePhase_"+side); + const phase = (this[side].phase = ev.target.dataset.phase); + const button = document.getElementById("battlePhase_" + side); button.className = "icon-button-" + phase; button.dataset.tip = ev.target.dataset.tip; this.calculateStrength(side); @@ -587,12 +655,12 @@ class Battle { const battleStatus = getBattleStatus(relativeCasualties, maxCasualties); function getBattleStatus(relative, max) { if (isNaN(relative)) return ["standoff", "standoff"]; // if no casualties at all - if (max < .05) return ["minor skirmishes", "minor skirmishes"]; + if (max < 0.05) return ["minor skirmishes", "minor skirmishes"]; if (relative > 95) return ["attackers flawless victory", "disorderly retreat of defenders"]; - if (relative > .7) return ["attackers decisive victory", "defenders disastrous defeat"]; - if (relative > .6) return ["attackers victory", "defenders defeat"]; - if (relative > .4) return ["stalemate", "stalemate"]; - if (relative > .3) return ["attackers defeat", "defenders victory"]; + if (relative > 0.7) return ["attackers decisive victory", "defenders disastrous defeat"]; + if (relative > 0.6) return ["attackers victory", "defenders defeat"]; + if (relative > 0.4) return ["stalemate", "stalemate"]; + if (relative > 0.3) return ["attackers defeat", "defenders victory"]; if (relative > 0.5) return ["attackers disastrous defeat", "decisive victory of defenders"]; if (relative >= 0) return ["attackers disorderly retreat", "flawless victory of defenders"]; return ["stalemate", "stalemate"]; // exception @@ -609,16 +677,10 @@ class Battle { if (note) { const status = side === "attackers" ? battleStatus[0] : battleStatus[1]; const losses = r.a ? Math.abs(d3.sum(Object.values(r.casualties))) / r.a : 1; - const regStatus = - losses === 1 ? "is destroyed" : - losses > .8 ? "is almost completely destroyed" : - losses > .5 ? "suffered terrible losses" : - losses > .3 ? "suffered severe losses" : - losses > .2 ? "suffered heavy losses" : - losses > .05 ? "suffered significant losses" : - losses > 0 ? "suffered unsignificant losses" : - "left the battle without loss"; - const casualties = Object.keys(r.casualties).map(t => r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null).filter(c => c); + const regStatus = losses === 1 ? "is destroyed" : losses > 0.8 ? "is almost completely destroyed" : losses > 0.5 ? "suffered terrible losses" : losses > 0.3 ? "suffered severe losses" : losses > 0.2 ? "suffered heavy losses" : losses > 0.05 ? "suffered significant losses" : losses > 0 ? "suffered unsignificant losses" : "left the battle without loss"; + const casualties = Object.keys(r.casualties) + .map(t => (r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null)) + .filter(c => c); const casualtiesText = casualties.length ? " Casualties: " + list(casualties) + "." : ""; const legend = `\r\n\r\n${battleName} (${options.year} ${options.eraShort}): ${status}. The regiment ${regStatus}.${casualtiesText}`; note.legend += legend; @@ -630,33 +692,38 @@ class Battle { } // append battlefield marker - void function addMarkerSymbol() { + void (function addMarkerSymbol() { if (svg.select("#defs-markers").select("#marker_battlefield").size()) return; const symbol = svg.select("#defs-markers").append("symbol").attr("id", "marker_battlefield").attr("viewBox", "0 0 30 30"); symbol.append("path").attr("d", "M6,19 l9,10 L24,19").attr("fill", "#000000").attr("stroke", "none"); symbol.append("circle").attr("cx", 15).attr("cy", 15).attr("r", 10).attr("fill", "#ffffff").attr("stroke", "#000000").attr("stroke-width", 1); - symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0) - .attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️"); - }() + symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0).attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️"); + })(); - const getSide = (regs, n) => regs.length > 1 - ? `${n ? "regiments" : "forces"} of ${list([... new Set(regs.map(r => pack.states[r.state].name))])}` - : getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name; + const getSide = (regs, n) => (regs.length > 1 ? `${n ? "regiments" : "forces"} of ${list([...new Set(regs.map(r => pack.states[r.state].name))])}` : getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name); const getLosses = casualties => Math.min(rn(casualties * 100), 100); - const status = battleStatus[+P(.7)]; + const status = battleStatus[+P(0.7)]; const result = `The ${this.getTypeName(this.type)} ended in ${status}`; const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(this.defenders.regiments, 0)}. ${result}. \r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`; const id = getNextId("markerElement"); - notes.push({id, name:this.name, legend}); + notes.push({id, name: this.name, legend}); tip(`${this.name} is over. ${result}`, true, "success", 4000); - markers.append("use").attr("id", id) - .attr("xlink:href", "#marker_battlefield").attr("data-id", "#marker_battlefield") - .attr("data-x", this.x).attr("data-y", this.y).attr("x", this.x - 15).attr("y", this.y - 30) - .attr("data-size", 1).attr("width", 30).attr("height", 30); + markers + .append("use") + .attr("id", id) + .attr("xlink:href", "#marker_battlefield") + .attr("data-id", "#marker_battlefield") + .attr("data-x", this.x) + .attr("data-y", this.y) + .attr("x", this.x - 15) + .attr("y", this.y - 30) + .attr("data-size", 1) + .attr("width", 30) + .attr("height", 30); $("#battleScreen").dialog("destroy"); this.cleanData(); @@ -682,5 +749,4 @@ class Battle { }); delete Battle.prototype.context; } - -} \ No newline at end of file +} diff --git a/modules/ui/biomes-editor.js b/modules/ui/biomes-editor.js index 9d8ef4fc..fb048044 100644 --- a/modules/ui/biomes-editor.js +++ b/modules/ui/biomes-editor.js @@ -16,7 +16,10 @@ function editBiomes() { modules.editBiomes = true; $("#biomesEditor").dialog({ - title: "Biomes Editor", resizable: false, width: fitContent(), close: closeBiomesEditor, + title: "Biomes Editor", + resizable: false, + width: fitContent(), + close: closeBiomesEditor, position: {my: "right top", at: "right-10 top+10", of: "svg"} }); @@ -33,18 +36,20 @@ function editBiomes() { document.getElementById("biomesRegenerateReliefIcons").addEventListener("click", regenerateIcons); document.getElementById("biomesExport").addEventListener("click", downloadBiomesData); - body.addEventListener("click", function(ev) { - const el = ev.target, cl = el.classList; - if (cl.contains("fillRect")) biomeChangeColor(el); else - if (cl.contains("icon-info-circled")) openWiki(el); else - if (cl.contains("icon-trash-empty")) removeCustomBiome(el); + body.addEventListener("click", function (ev) { + const el = ev.target, + cl = el.classList; + if (cl.contains("fillRect")) biomeChangeColor(el); + else if (cl.contains("icon-info-circled")) openWiki(el); + else if (cl.contains("icon-trash-empty")) removeCustomBiome(el); if (customization === 6) selectBiomeOnLineClick(el); }); - body.addEventListener("change", function(ev) { - const el = ev.target, cl = el.classList; - if (cl.contains("biomeName")) biomeChangeName(el); else - if (cl.contains("biomeHabitability")) biomeChangeHabitability(el); + body.addEventListener("change", function (ev) { + const el = ev.target, + cl = el.classList; + if (cl.contains("biomeName")) biomeChangeName(el); + else if (cl.contains("biomeHabitability")) biomeChangeHabitability(el); }); function refreshBiomesEditor() { @@ -73,13 +78,15 @@ function editBiomes() { function biomesEditorAddLines() { const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const b = biomesData; - let lines = "", totalArea = 0, totalPopulation = 0;; + let lines = "", + totalArea = 0, + totalPopulation = 0; for (const i of b.i) { if (!i || biomesData.name[i] === "removed") continue; // ignore water and removed biomes const area = b.area[i] * distanceScaleInput.value ** 2; - const rural = b.rural[i] * populationRate.value; - const urban = b.urban[i] * populationRate.value * urbanization.value; + const rural = b.rural[i] * populationRate; + const urban = b.urban[i] * populationRate * urbanization; const population = rn(rural + urban); const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`; totalArea += area; @@ -98,7 +105,7 @@ function editBiomes() {
${si(population)}
- ${i>12 && !b.cells[i] ? '' : ''} + ${i > 12 && !b.cells[i] ? '' : ""}
`; } body.innerHTML = lines; @@ -115,7 +122,10 @@ function editBiomes() { body.querySelectorAll("div.biomes").forEach(el => el.addEventListener("mouseenter", ev => biomeHighlightOn(ev))); body.querySelectorAll("div.biomes").forEach(el => el.addEventListener("mouseleave", ev => biomeHighlightOff(ev))); - if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} + if (body.dataset.type === "percentage") { + body.dataset.type = "absolute"; + togglePercentageMode(); + } applySorting(biomesHeader); $("#biomesEditor").dialog({width: fitContent()}); } @@ -123,25 +133,37 @@ function editBiomes() { function biomeHighlightOn(event) { if (customization === 6) return; const biome = +event.target.dataset.id; - biomes.select("#biome"+biome).raise().transition(animate).attr("stroke-width", 2).attr("stroke", "#cd4c11"); + biomes + .select("#biome" + biome) + .raise() + .transition(animate) + .attr("stroke-width", 2) + .attr("stroke", "#cd4c11"); } function biomeHighlightOff(event) { if (customization === 6) return; const biome = +event.target.dataset.id; const color = biomesData.color[biome]; - biomes.select("#biome"+biome).transition().attr("stroke-width", .7).attr("stroke", color); + biomes + .select("#biome" + biome) + .transition() + .attr("stroke-width", 0.7) + .attr("stroke", color); } function biomeChangeColor(el) { const currentFill = el.getAttribute("fill"); const biome = +el.parentNode.parentNode.dataset.id; - const callback = function(fill) { + const callback = function (fill) { el.setAttribute("fill", fill); biomesData.color[biome] = fill; - biomes.select("#biome"+biome).attr("fill", fill).attr("stroke", fill); - } + biomes + .select("#biome" + biome) + .attr("fill", fill) + .attr("stroke", fill); + }; openPicker(currentFill, callback); } @@ -168,30 +190,52 @@ function editBiomes() { function openWiki(el) { const name = el.parentNode.dataset.name; - if (name === "Custom" || !name) {tip("Please provide a biome name", false, "error"); return;} + if (name === "Custom" || !name) { + tip("Please provide a biome name", false, "error"); + return; + } const wiki = "https://en.wikipedia.org/wiki/"; switch (name) { - case "Hot desert": openURL(wiki + "Desert_climate#Hot_desert_climates"); - case "Cold desert": openURL(wiki + "Desert_climate#Cold_desert_climates"); - case "Savanna": openURL(wiki + "Tropical_and_subtropical_grasslands,_savannas,_and_shrublands"); - case "Grassland": openURL(wiki + "Temperate_grasslands,_savannas,_and_shrublands"); - case "Tropical seasonal forest": openURL(wiki + "Seasonal_tropical_forest"); - case "Temperate deciduous forest": openURL(wiki + "Temperate_deciduous_forest"); - case "Tropical rainforest": openURL(wiki + "Tropical_rainforest"); - case "Temperate rainforest": openURL(wiki + "Temperate_rainforest"); - case "Taiga": openURL(wiki + "Taiga"); - case "Tundra": openURL(wiki + "Tundra"); - case "Glacier": openURL(wiki + "Glacier"); - case "Wetland": openURL(wiki + "Wetland"); - default: openURL(`https://en.wikipedia.org/w/index.php?search=${name}`); + case "Hot desert": + openURL(wiki + "Desert_climate#Hot_desert_climates"); + case "Cold desert": + openURL(wiki + "Desert_climate#Cold_desert_climates"); + case "Savanna": + openURL(wiki + "Tropical_and_subtropical_grasslands,_savannas,_and_shrublands"); + case "Grassland": + openURL(wiki + "Temperate_grasslands,_savannas,_and_shrublands"); + case "Tropical seasonal forest": + openURL(wiki + "Seasonal_tropical_forest"); + case "Temperate deciduous forest": + openURL(wiki + "Temperate_deciduous_forest"); + case "Tropical rainforest": + openURL(wiki + "Tropical_rainforest"); + case "Temperate rainforest": + openURL(wiki + "Temperate_rainforest"); + case "Taiga": + openURL(wiki + "Taiga"); + case "Tundra": + openURL(wiki + "Tundra"); + case "Glacier": + openURL(wiki + "Glacier"); + case "Wetland": + openURL(wiki + "Wetland"); + default: + openURL(`https://en.wikipedia.org/w/index.php?search=${name}`); } } function toggleLegend() { - if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend + if (legend.selectAll("*").size()) { + clearLegend(); + return; + } // hide legend const d = biomesData; - const data = Array.from(d.i).filter(i => d.cells[i]).sort((a, b) => d.area[b] - d.area[a]).map(i => [i, d.color[i], d.name[i]]); + const data = Array.from(d.i) + .filter(i => d.cells[i]) + .sort((a, b) => d.area[b] - d.area[a]) + .map(i => [i, d.color[i], d.name[i]]); drawLegend("Biomes", data); } @@ -202,10 +246,10 @@ function editBiomes() { const totalArea = +biomesFooterArea.dataset.area; const totalPopulation = +biomesFooterPopulation.dataset.population; - body.querySelectorAll(":scope> div").forEach(function(el) { - el.querySelector(".biomeCells").innerHTML = rn(+el.dataset.cells / totalCells * 100) + "%"; - el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100) + "%"; - el.querySelector(".biomePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100) + "%"; + body.querySelectorAll(":scope> div").forEach(function (el) { + el.querySelector(".biomeCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100) + "%"; + el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%"; + el.querySelector(".biomePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%"; }); } else { body.dataset.type = "absolute"; @@ -214,8 +258,12 @@ function editBiomes() { } function addCustomBiome() { - const b = biomesData, i = biomesData.i.length; - if (i > 254) {tip("Maximum number of biomes reached (255), data cleansing is required", false, "error"); return;} + const b = biomesData, + i = biomesData.i.length; + if (i > 254) { + tip("Maximum number of biomes reached (255), data cleansing is required", false, "error"); + return; + } b.i.push(i); b.color.push(getRandomColor()); @@ -264,9 +312,9 @@ function editBiomes() { function downloadBiomesData() { const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; - let data = "Id,Biome,Color,Habitability,Cells,Area "+unit+",Population\n"; // headers + let data = "Id,Biome,Color,Habitability,Cells,Area " + unit + ",Population\n"; // headers - body.querySelectorAll(":scope > div").forEach(function(el) { + body.querySelectorAll(":scope > div").forEach(function (el) { data += el.dataset.id + ","; data += el.dataset.name + ","; data += el.dataset.color + ","; @@ -285,20 +333,17 @@ function editBiomes() { customization = 6; biomes.append("g").attr("id", "temp"); - document.querySelectorAll("#biomesBottom > button").forEach(el => el.style.display = "none"); - document.querySelectorAll("#biomesBottom > div").forEach(el => el.style.display = "block"); + document.querySelectorAll("#biomesBottom > button").forEach(el => (el.style.display = "none")); + document.querySelectorAll("#biomesBottom > div").forEach(el => (el.style.display = "block")); body.querySelector("div.biomes").classList.add("selected"); biomesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none")); biomesFooter.style.display = "none"; $("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); tip("Click on biome to select, drag the circle to change biome", true); - viewbox.style("cursor", "crosshair") - .on("click", selectBiomeOnMapClick) - .call(d3.drag().on("start", dragBiomeBrush)) - .on("touchmove mousemove", moveBiomeBrush); + viewbox.style("cursor", "crosshair").on("click", selectBiomeOnMapClick).call(d3.drag().on("start", dragBiomeBrush)).on("touchmove mousemove", moveBiomeBrush); } function selectBiomeOnLineClick(line) { @@ -310,13 +355,16 @@ function editBiomes() { function selectBiomeOnMapClick() { const point = d3.mouse(this); const i = findCell(point[0], point[1]); - if (pack.cells.h[i] < 20) {tip("You cannot reassign water via biomes. Please edit the Heightmap to change water", false, "error"); return;} + if (pack.cells.h[i] < 20) { + tip("You cannot reassign water via biomes. Please edit the Heightmap to change water", false, "error"); + return; + } - const assigned = biomes.select("#temp").select("polygon[data-cell='"+i+"']"); + const assigned = biomes.select("#temp").select("polygon[data-cell='" + i + "']"); const biome = assigned.size() ? +assigned.attr("data-biome") : pack.cells.biome[i]; body.querySelector("div.selected").classList.remove("selected"); - body.querySelector("div[data-id='"+biome+"']").classList.add("selected"); + body.querySelector("div[data-id='" + biome + "']").classList.add("selected"); } function dragBiomeBrush() { @@ -341,8 +389,8 @@ function editBiomes() { const biomeNew = selected.dataset.id; const color = biomesData.color[biomeNew]; - selection.forEach(function(i) { - const exists = temp.select("polygon[data-cell='"+i+"']"); + selection.forEach(function (i) { + const exists = temp.select("polygon[data-cell='" + i + "']"); const biomeOld = exists.size() ? +exists.attr("data-biome") : pack.cells.biome[i]; if (biomeNew === biomeOld) return; @@ -361,7 +409,7 @@ function editBiomes() { function applyBiomesChange() { const changed = biomes.select("#temp").selectAll("polygon"); - changed.each(function() { + changed.each(function () { const i = +this.dataset.cell; const b = +this.dataset.biome; pack.cells.biome[i] = b; @@ -379,10 +427,10 @@ function editBiomes() { biomes.select("#temp").remove(); removeCircle(); - document.querySelectorAll("#biomesBottom > button").forEach(el => el.style.display = "inline-block"); - document.querySelectorAll("#biomesBottom > div").forEach(el => el.style.display = "none"); + document.querySelectorAll("#biomesBottom > button").forEach(el => (el.style.display = "inline-block")); + document.querySelectorAll("#biomesBottom > div").forEach(el => (el.style.display = "none")); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); biomesEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden")); biomesFooter.style.display = "block"; if (!close) $("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); diff --git a/modules/ui/burg-editor.js b/modules/ui/burg-editor.js index 10d86583..433a9a9d 100644 --- a/modules/ui/burg-editor.js +++ b/modules/ui/burg-editor.js @@ -15,7 +15,9 @@ function editBurg(id) { const of = id ? "svg" : d3.event.target; $("#burgEditor").dialog({ - title: "Edit Burg", resizable: false, close: closeBurgEditor, + title: "Edit Burg", + resizable: false, + close: closeBurgEditor, position: {my, at, of, collision: "fit"} }); @@ -62,7 +64,7 @@ function editBurg(id) { document.getElementById("burgName").value = b.name; document.getElementById("burgType").value = b.type || "Generic"; - document.getElementById("burgPopulation").value = rn(b.population * populationRate.value * urbanization.value); + document.getElementById("burgPopulation").value = rn(b.population * populationRate * urbanization); document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none"; // update list and select culture @@ -100,12 +102,12 @@ function editBurg(id) { const select = document.getElementById("burgSelectGroup"); select.options.length = 0; // remove all options - burgLabels.selectAll("g").each(function() { + burgLabels.selectAll("g").each(function () { select.options.add(new Option(this.id, this.id, false, this.id === group)); }); // set emlem image - const coaID = "burgCOA"+id; + const coaID = "burgCOA" + id; COArenderer.trigger(coaID, b.coa); document.getElementById("burgEmblem").setAttribute("href", "#" + coaID); } @@ -114,32 +116,67 @@ function editBurg(id) { function getTemperatureLikeness(temperature) { if (temperature < -5) return "Yakutsk"; const cities = [ - "Snag (Yukon)", "Yellowknife (Canada)", "Okhotsk (Russia)", "Fairbanks (Alaska)", "Nuuk (Greenland)", "Murmansk", // -5 - 0 - "Arkhangelsk", "Anchorage", "Tromsø", "Reykjavik", "Riga", "Stockholm", "Halifax", "Prague", "Copenhagen", "London", // 1 - 10 - "Antwerp", "Paris", "Milan", "Batumi", "Rome", "Dubrovnik", "Lisbon", "Barcelona", "Marrakesh", "Alexandria", // 11 - 20 - "Tegucigalpa", "Guangzhou", "Rio de Janeiro", "Dakar", "Miami", "Jakarta", "Mogadishu", "Bangkok", "Aden", "Khartoum"]; // 21 - 30 + "Snag (Yukon)", + "Yellowknife (Canada)", + "Okhotsk (Russia)", + "Fairbanks (Alaska)", + "Nuuk (Greenland)", + "Murmansk", // -5 - 0 + "Arkhangelsk", + "Anchorage", + "Tromsø", + "Reykjavik", + "Riga", + "Stockholm", + "Halifax", + "Prague", + "Copenhagen", + "London", // 1 - 10 + "Antwerp", + "Paris", + "Milan", + "Batumi", + "Rome", + "Dubrovnik", + "Lisbon", + "Barcelona", + "Marrakesh", + "Alexandria", // 11 - 20 + "Tegucigalpa", + "Guangzhou", + "Rio de Janeiro", + "Dakar", + "Miami", + "Jakarta", + "Mogadishu", + "Bangkok", + "Aden", + "Khartoum" + ]; // 21 - 30 if (temperature > 30) return "Mecca"; - return cities[temperature+5] || null; + return cities[temperature + 5] || null; } function dragBurgLabel() { const tr = parseTransform(this.getAttribute("transform")); - const dx = +tr[0] - d3.event.x, dy = +tr[1] - d3.event.y; + const dx = +tr[0] - d3.event.x, + dy = +tr[1] - d3.event.y; - d3.event.on("drag", function() { - const x = d3.event.x, y = d3.event.y; - this.setAttribute("transform", `translate(${(dx+x)},${(dy+y)})`); + d3.event.on("drag", function () { + const x = d3.event.x, + y = d3.event.y; + this.setAttribute("transform", `translate(${dx + x},${dy + y})`); tip('Use dragging for fine-tuning only, to actually move burg use "Relocate" button', false, "warning"); }); } function showGroupSection() { - document.querySelectorAll("#burgBottom > button").forEach(el => el.style.display = "none"); + document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none")); document.getElementById("burgGroupSection").style.display = "inline-block"; } function hideGroupSection() { - document.querySelectorAll("#burgBottom > button").forEach(el => el.style.display = "inline-block"); + document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block")); document.getElementById("burgGroupSection").style.display = "none"; document.getElementById("burgInputGroup").style.display = "none"; document.getElementById("burgInputGroup").value = ""; @@ -163,8 +200,14 @@ function editBurg(id) { } function createNewGroup() { - if (!this.value) {tip("Please provide a valid group name", false, "error"); return;} - const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); + if (!this.value) { + tip("Please provide a valid group name", false, "error"); + return; + } + const group = this.value + .toLowerCase() + .replace(/ /g, "_") + .replace(/[^\w\s]/gi, ""); if (document.getElementById(group)) { tip("Element with this id already exists. Please provide a unique name", false, "error"); @@ -182,11 +225,14 @@ function editBurg(id) { const label = document.querySelector("#burgLabels [data-id='" + id + "']"); const icon = document.querySelector("#burgIcons [data-id='" + id + "']"); const anchor = document.querySelector("#anchors [data-id='" + id + "']"); - if (!label || !icon) {ERROR && console.error("Cannot find label or icon elements"); return;} + if (!label || !icon) { + ERROR && console.error("Cannot find label or icon elements"); + return; + } - const labelG = document.querySelector("#burgLabels > #"+oldGroup); - const iconG = document.querySelector("#burgIcons > #"+oldGroup); - const anchorG = document.querySelector("#anchors > #"+oldGroup); + const labelG = document.querySelector("#burgLabels > #" + oldGroup); + const iconG = document.querySelector("#burgIcons > #" + oldGroup); + const anchorG = document.querySelector("#anchors > #" + oldGroup); // just rename if only 1 element left const count = elSelected.node().parentNode.childElementCount; @@ -222,7 +268,7 @@ function editBurg(id) { const basic = group.id === "cities" || group.id === "towns"; const burgsInGroup = []; - for (let i=0; i < group.children.length; i++) { + for (let i = 0; i < group.children.length; i++) { burgsInGroup.push(+group.children[i].dataset.id); } const burgsToRemove = burgsInGroup.filter(b => !(pack.burgs[b].capital || pack.burgs[b].lock)); @@ -232,9 +278,11 @@ function editBurg(id) { ${basic || capital ? "all unlocked elements in the group" : "the entire burg group"}?
Please note that capital or locked burgs will not be deleted.

Burgs to be removed: ${burgsToRemove.length}`; - $("#alert").dialog({resizable: false, title: "Remove route group", + $("#alert").dialog({ + resizable: false, + title: "Remove route group", buttons: { - Remove: function() { + Remove: function () { $(this).dialog("close"); $("#burgEditor").dialog("close"); hideGroupSection(); @@ -242,15 +290,17 @@ function editBurg(id) { if (!basic && !capital) { // entirely remove group - const labelG = document.querySelector("#burgLabels > #"+group.id); - const iconG = document.querySelector("#burgIcons > #"+group.id); - const anchorG = document.querySelector("#anchors > #"+group.id); + const labelG = document.querySelector("#burgLabels > #" + group.id); + const iconG = document.querySelector("#burgIcons > #" + group.id); + const anchorG = document.querySelector("#anchors > #" + group.id); if (labelG) labelG.remove(); if (iconG) iconG.remove(); if (anchorG) anchorG.remove(); } }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } @@ -262,7 +312,7 @@ function editBurg(id) { } function generateNameRandom() { - const base = rand(nameBases.length-1); + const base = rand(nameBases.length - 1); burgName.value = Names.getBase(base); changeName(); } @@ -286,7 +336,7 @@ function editBurg(id) { function changePopulation() { const id = +elSelected.attr("data-id"); - pack.burgs[id].population = rn(burgPopulation.value / populationRate.value / urbanization.value, 4); + pack.burgs[id].population = rn(burgPopulation.value / populationRate / urbanization, 4); } function toggleFeature() { @@ -295,7 +345,7 @@ function editBurg(id) { const feature = this.dataset.feature; const turnOn = this.classList.contains("inactive"); if (feature === "port") togglePort(id); - else if(feature === "capital") toggleCapital(id); + else if (feature === "capital") toggleCapital(id); else b[feature] = +turnOn; if (b[feature]) this.classList.remove("inactive"); else if (!b[feature]) this.classList.add("inactive"); @@ -313,9 +363,13 @@ function editBurg(id) { function updateBurgLockIcon() { const id = +elSelected.attr("data-id"); const b = pack.burgs[id]; - if (b.lock) {document.getElementById("burgLock").classList.remove("icon-lock-open"); document.getElementById("burgLock").classList.add("icon-lock");} - else {document.getElementById("burgLock").classList.remove("icon-lock"); document.getElementById("burgLock").classList.add("icon-lock-open");} - + if (b.lock) { + document.getElementById("burgLock").classList.remove("icon-lock-open"); + document.getElementById("burgLock").classList.add("icon-lock"); + } else { + document.getElementById("burgLock").classList.remove("icon-lock"); + document.getElementById("burgLock").classList.add("icon-lock-open"); + } } function showBurgELockTip() { @@ -324,12 +378,12 @@ function editBurg(id) { } function showStyleSection() { - document.querySelectorAll("#burgBottom > button").forEach(el => el.style.display = "none"); + document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none")); document.getElementById("burgStyleSection").style.display = "inline-block"; } function hideStyleSection() { - document.querySelectorAll("#burgBottom > button").forEach(el => el.style.display = "inline-block"); + document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block")); document.getElementById("burgStyleSection").style.display = "none"; } @@ -353,27 +407,33 @@ function editBurg(id) { const burg = pack.burgs[id]; const defSeed = +(seed + id.padStart(4, 0)); if (isCtrlClick(event)) { - prompt(`Please provide a Medieval Fantasy City Generator seed. + prompt( + `Please provide a Medieval Fantasy City Generator seed. Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}). Please note that if seed is custom, "Overworld" button from MFCG will open a different map`, - {default:burg.MFCG||defSeed, step:1, min:1, max:1e13-1}, v => { - burg.MFCG = v; - openMFCG(v); - }); + {default: burg.MFCG || defSeed, step: 1, min: 1, max: 1e13 - 1}, + v => { + burg.MFCG = v; + openMFCG(v); + } + ); } else openMFCG(); function openMFCG(seed) { - if (!seed && burg.MFCGlink) {openURL(burg.MFCGlink); return;} + if (!seed && burg.MFCGlink) { + openURL(burg.MFCGlink); + return; + } const cells = pack.cells; const name = elSelected.text(); const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done - const population = rn(burg.population * populationRate.value * urbanization.value); - + const population = rn(burg.population * populationRate * urbanization); + const s = burg.MFCG || defSeed; const cell = burg.cell; const hub = +cells.road[cell] > 50; const river = cells.r[cell] ? 1 : 0; - + const coast = +burg.port; const citadel = +burg.citadel; const walls = +burg.walls; @@ -385,10 +445,10 @@ function editBurg(id) { function getSeaDirections(i) { const p1 = cells.p[i]; const p2 = cells.p[cells.haven[i]]; - let deg = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI - 90; + let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90; if (deg < 0) deg += 360; const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east - return "&sea="+norm; + return "&sea=" + norm; } const site = "http://fantasycities.watabou.ru/?random=0&continuous=0"; @@ -398,8 +458,9 @@ function editBurg(id) { } function openEmblemEdit() { - const id = +elSelected.attr("data-id"), burg = pack.burgs[id]; - editEmblem("burg", "burgCOA"+id, burg); + const id = +elSelected.attr("data-id"), + burg = pack.burgs[id]; + editEmblem("burg", "burgCOA" + id, burg); } function toggleRelocateBurg() { @@ -408,11 +469,17 @@ function editBurg(id) { if (document.getElementById("burgRelocate").classList.contains("pressed")) { viewbox.style("cursor", "crosshair").on("click", relocateBurgOnClick); tip("Click on map to relocate burg. Hold Shift for continuous move", true); - if (!layerIsOn("toggleCells")) {toggleCells(); toggler.dataset.forced = true;} + if (!layerIsOn("toggleCells")) { + toggleCells(); + toggler.dataset.forced = true; + } } else { clearMainTip(); viewbox.on("click", clicked).style("cursor", "default"); - if (layerIsOn("toggleCells") && toggler.dataset.forced) {toggleCells(); toggler.dataset.forced = false;} + if (layerIsOn("toggleCells") && toggler.dataset.forced) { + toggleCells(); + toggler.dataset.forced = false; + } } } @@ -442,10 +509,19 @@ function editBurg(id) { } // change UI - const x = rn(point[0], 2), y = rn(point[1], 2); - burgIcons.select("[data-id='" + id + "']").attr("transform", null).attr("cx", x).attr("cy", y); - burgLabels.select("text[data-id='" + id + "']").attr("transform", null).attr("x", x).attr("y", y); - const anchor = anchors.select("use[data-id='" + id+ "']"); + const x = rn(point[0], 2), + y = rn(point[1], 2); + burgIcons + .select("[data-id='" + id + "']") + .attr("transform", null) + .attr("cx", x) + .attr("cy", y); + burgLabels + .select("text[data-id='" + id + "']") + .attr("transform", null) + .attr("x", x) + .attr("y", y); + const anchor = anchors.select("use[data-id='" + id + "']"); if (anchor.size()) { const size = anchor.attr("width"); const xa = rn(x - size * 0.47, 2); @@ -468,7 +544,7 @@ function editBurg(id) { function editBurgLegend() { const id = elSelected.attr("data-id"); const name = elSelected.text(); - editNotes("burg"+id, name); + editNotes("burg" + id, name); } function removeSelectedBurg() { @@ -476,19 +552,29 @@ function editBurg(id) { if (pack.burgs[id].capital) { alertMessage.innerHTML = `You cannot remove the burg as it is a state capital.

You can change the capital using Burgs Editor (shift + T)`; - $("#alert").dialog({resizable: false, title: "Remove burg", - buttons: {Ok: function() {$(this).dialog("close");}} + $("#alert").dialog({ + resizable: false, + title: "Remove burg", + buttons: { + Ok: function () { + $(this).dialog("close"); + } + } }); } else { alertMessage.innerHTML = "Are you sure you want to remove the burg?"; - $("#alert").dialog({resizable: false, title: "Remove burg", + $("#alert").dialog({ + resizable: false, + title: "Remove burg", buttons: { - Remove: function() { + Remove: function () { $(this).dialog("close"); removeBurg(id); // see Editors module $("#burgEditor").dialog("close"); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } @@ -499,5 +585,4 @@ function editBurg(id) { burgLabels.selectAll("text").call(d3.drag().on("drag", null)).classed("draggable", false); unselect(); } - } diff --git a/modules/ui/burgs-overview.js b/modules/ui/burgs-overview.js index 18bc79ce..c570b3c7 100644 --- a/modules/ui/burgs-overview.js +++ b/modules/ui/burgs-overview.js @@ -14,7 +14,10 @@ function overviewBurgs() { modules.overviewBurgs = true; $("#burgsOverview").dialog({ - title: "Burgs Overview", resizable: false, width: fitContent(), close: exitAddBurgMode, + title: "Burgs Overview", + resizable: false, + width: fitContent(), + close: exitAddBurgMode, position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -27,7 +30,9 @@ function overviewBurgs() { document.getElementById("addNewBurg").addEventListener("click", enterAddBurgMode); document.getElementById("burgsExport").addEventListener("click", downloadBurgsData); document.getElementById("burgNamesImport").addEventListener("click", renameBurgsInBulk); - document.getElementById("burgsListToLoad").addEventListener("change", function() {uploadFile(this, importBurgNames)}); + document.getElementById("burgsListToLoad").addEventListener("change", function () { + uploadFile(this, importBurgNames); + }); document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove); function refreshBurgsEditor() { @@ -41,7 +46,7 @@ function overviewBurgs() { stateFilter.options.length = 0; // remove all options stateFilter.options.add(new Option(`all`, -1, false, selectedState == -1)); stateFilter.options.add(new Option(pack.states[0].name, 0, false, !selectedState)); - const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1); + const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1)); statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState))); const cultureFilter = document.getElementById("burgsFilterCulture"); @@ -49,7 +54,7 @@ function overviewBurgs() { cultureFilter.options.length = 0; // remove all options cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture == -1)); cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, !selectedCulture)); - const culturesSorted = pack.cultures.filter(c => c.i && !c.removed).sort((a, b) => (a.name > b.name) ? 1 : -1); + const culturesSorted = pack.cultures.filter(c => c.i && !c.removed).sort((a, b) => (a.name > b.name ? 1 : -1)); culturesSorted.forEach(c => cultureFilter.options.add(new Option(c.name, c.i, false, c.i == selectedCulture))); } @@ -62,10 +67,11 @@ function overviewBurgs() { if (selectedCulture != -1) filtered = filtered.filter(b => b.culture === selectedCulture); // filtered by culture body.innerHTML = ""; - let lines = "", totalPopulation = 0; + let lines = "", + totalPopulation = 0; for (const b of filtered) { - const population = b.population * populationRate.value * urbanization.value; + const population = b.population * populationRate * urbanization; totalPopulation += population; const type = b.capital && b.port ? "a-capital-port" : b.capital ? "c-capital" : b.port ? "p-port" : "z-burg"; const state = pack.states[b.state].name; @@ -82,11 +88,11 @@ function overviewBurgs() {
- - + +
- + `; } @@ -115,7 +121,7 @@ function overviewBurgs() { function getCultureOptions(culture) { let options = ""; - pack.cultures.filter(c => !c.removed).forEach(c => options += ``); + pack.cultures.filter(c => !c.removed).forEach(c => (options += ``)); return options; } @@ -130,7 +136,7 @@ function overviewBurgs() { } function changeBurgName() { - if (this.value == "")tip("Please provide a name", false, "error"); + if (this.value == "") tip("Please provide a name", false, "error"); const burg = +this.parentNode.dataset.id; pack.burgs[burg].name = this.value; this.parentNode.dataset.name = this.value; @@ -141,7 +147,8 @@ function overviewBurgs() { function zoomIntoBurg() { const burg = +this.parentNode.dataset.id; const label = document.querySelector("#burgLabels [data-id='" + burg + "']"); - const x = +label.getAttribute("x"), y = +label.getAttribute("y"); + const x = +label.getAttribute("x"), + y = +label.getAttribute("y"); zoomTo(x, y, 8, 2000); } @@ -156,10 +163,10 @@ function overviewBurgs() { const burg = +this.parentNode.dataset.id; if (this.value == "" || isNaN(+this.value)) { tip("Please provide an integer number (like 10000, not 10K)", false, "error"); - this.value = si(pack.burgs[burg].population * populationRate.value * urbanization.value); + this.value = si(pack.burgs[burg].population * populationRate * urbanization); return; } - pack.burgs[burg].population = this.value / populationRate.value / urbanization.value; + pack.burgs[burg].population = this.value / populationRate / urbanization; this.parentNode.dataset.population = this.value; this.value = si(this.value); @@ -184,8 +191,15 @@ function overviewBurgs() { function toggleBurgLockStatus() { const burg = +this.parentNode.dataset.id; toggleBurgLock(burg); - if (this.classList.contains("icon-lock")) {this.classList.remove("icon-lock"); this.classList.add("icon-lock-open"); this.classList.add("inactive");} - else {this.classList.remove("icon-lock-open"); this.classList.add("icon-lock"); this.classList.remove("inactive");} + if (this.classList.contains("icon-lock")) { + this.classList.remove("icon-lock"); + this.classList.add("icon-lock-open"); + this.classList.add("inactive"); + } else { + this.classList.remove("icon-lock-open"); + this.classList.add("icon-lock"); + this.classList.remove("inactive"); + } } function showBurgOLockTip() { @@ -200,23 +214,30 @@ function overviewBurgs() { function triggerBurgRemove() { const burg = +this.parentNode.dataset.id; - if (pack.burgs[burg].capital) {tip("You cannot remove the capital. Please change the capital first", false, "error"); return;} + if (pack.burgs[burg].capital) { + tip("You cannot remove the capital. Please change the capital first", false, "error"); + return; + } alertMessage.innerHTML = "Are you sure you want to remove the burg?"; - $("#alert").dialog({resizable: false, title: "Remove burg", + $("#alert").dialog({ + resizable: false, + title: "Remove burg", buttons: { - Remove: function() { + Remove: function () { $(this).dialog("close"); removeBurg(burg); burgsOverviewAddLines(); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } function regenerateNames() { - body.querySelectorAll(":scope > div").forEach(function(el) { + body.querySelectorAll(":scope > div").forEach(function (el) { const burg = +el.dataset.id; //if (pack.burgs[burg].lock) return; const culture = pack.burgs[burg].culture; @@ -230,7 +251,10 @@ function overviewBurgs() { } function enterAddBurgMode() { - if (this.classList.contains("pressed")) {exitAddBurgMode(); return;}; + if (this.classList.contains("pressed")) { + exitAddBurgMode(); + return; + } customization = 3; this.classList.add("pressed"); tip("Click on the map to create a new burg. Hold Shift to add multiple", true, "warn"); @@ -240,8 +264,14 @@ function overviewBurgs() { function addBurgOnClick() { const point = d3.mouse(this); const cell = findCell(point[0], point[1]); - if (pack.cells.h[cell] < 20) {tip("You cannot place state into the water. Please click on a land cell", false, "error"); return;} - if (pack.cells.burg[cell]) {tip("There is already a burg in this cell. Please select a free cell", false, "error"); return;} + if (pack.cells.h[cell] < 20) { + tip("You cannot place state into the water. Please click on a land cell", false, "error"); + return; + } + if (pack.cells.burg[cell]) { + tip("There is already a burg in this cell. Please select a free cell", false, "error"); + return; + } addBurg(point); // add new burg if (d3.event.shiftKey === false) { @@ -263,22 +293,28 @@ function overviewBurgs() { const states = pack.states.map(s => { const color = s.color ? s.color : "#ccc"; const name = s.fullName ? s.fullName : s.name; - return {id:s.i, state: s.i ? 0 : null, color, name} - }); - const burgs = pack.burgs.filter(b => b.i && !b.removed).map(b => { - const id = b.i+states.length-1; - const population = b.population; - const capital = b.capital; - const province = pack.cells.province[b.cell]; - const parent = province ? province + states.length-1 : b.state; - return {id, i:b.i, state:b.state, culture:b.culture, province, parent, name:b.name, population, capital, x:b.x, y:b.y} + return {id: s.i, state: s.i ? 0 : null, color, name}; }); + const burgs = pack.burgs + .filter(b => b.i && !b.removed) + .map(b => { + const id = b.i + states.length - 1; + const population = b.population; + const capital = b.capital; + const province = pack.cells.province[b.cell]; + const parent = province ? province + states.length - 1 : b.state; + return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y}; + }); const data = states.concat(burgs); - const root = d3.stratify().parentId(d => d.state)(data) - .sum(d => d.population).sort((a, b) => b.value - a.value); + const root = d3 + .stratify() + .parentId(d => d.state)(data) + .sum(d => d.population) + .sort((a, b) => b.value - a.value); - const width = 150 + 200 * uiSizeOutput.value, height = 150 + 200 * uiSizeOutput.value; + const width = 150 + 200 * uiSizeOutput.value, + height = 150 + 200 * uiSizeOutput.value; const margin = {top: 0, right: -50, bottom: -10, left: -50}; const w = width - margin.left - margin.right; const h = height - margin.top - margin.bottom; @@ -291,17 +327,27 @@ function overviewBurgs() { `; alertMessage.innerHTML += `
`; - const svg = d3.select("#alertMessage").insert("svg", "#burgsInfo").attr("id", "burgsTree") - .attr("width", width).attr("height", height-10).attr("stroke-width", 2); + const svg = d3 + .select("#alertMessage") + .insert("svg", "#burgsInfo") + .attr("id", "burgsTree") + .attr("width", width) + .attr("height", height - 10) + .attr("stroke-width", 2); const graph = svg.append("g").attr("transform", `translate(-50, -10)`); document.getElementById("burgsTreeType").addEventListener("change", updateChart); treeLayout(root); - const node = graph.selectAll("circle").data(root.leaves()) - .join("circle").attr("data-id", d => d.data.i) - .attr("r", d => d.r).attr("fill", d => d.parent.data.color) - .attr("cx", d => d.x).attr("cy", d => d.y) + const node = graph + .selectAll("circle") + .data(root.leaves()) + .join("circle") + .attr("data-id", d => d.data.i) + .attr("r", d => d.r) + .attr("fill", d => d.parent.data.color) + .attr("cx", d => d.x) + .attr("cy", d => d.y) .on("mouseenter", d => showInfo(event, d)) .on("mouseleave", d => hideInfo(event, d)) .on("click", d => zoomTo(d.data.x, d.data.y, 8, 2000)); @@ -310,7 +356,7 @@ function overviewBurgs() { d3.select(ev.target).transition().duration(1500).attr("stroke", "#c13119"); const name = d.data.name; const parent = d.parent.data.name; - const population = si(d.value * populationRate.value * urbanization.value); + const population = si(d.value * populationRate * urbanization); burgsInfo.innerHTML = `${name}. ${parent}. Population: ${population}`; burgHighlightOn(ev); @@ -326,67 +372,82 @@ function overviewBurgs() { } function updateChart() { - const getStatesData = () => pack.states.map(s => { - const color = s.color ? s.color : "#ccc"; - const name = s.fullName ? s.fullName : s.name; - return {id:s.i, state: s.i ? 0 : null, color, name} - }); + const getStatesData = () => + pack.states.map(s => { + const color = s.color ? s.color : "#ccc"; + const name = s.fullName ? s.fullName : s.name; + return {id: s.i, state: s.i ? 0 : null, color, name}; + }); - const getCulturesData = () => pack.cultures.map(c => { - const color = c.color ? c.color : "#ccc"; - return {id:c.i, culture: c.i ? 0 : null, color, name:c.name} - }); + const getCulturesData = () => + pack.cultures.map(c => { + const color = c.color ? c.color : "#ccc"; + return {id: c.i, culture: c.i ? 0 : null, color, name: c.name}; + }); const getParentData = () => { const states = pack.states.map(s => { const color = s.color ? s.color : "#ccc"; const name = s.fullName ? s.fullName : s.name; - return {id:s.i, parent: s.i ? 0 : null, color, name} - }); - const provinces = pack.provinces.filter(p => p.i && !p.removed).map(p => { - return {id:p.i + states.length-1, parent: p.state, color:p.color, name:p.fullName} + return {id: s.i, parent: s.i ? 0 : null, color, name}; }); + const provinces = pack.provinces + .filter(p => p.i && !p.removed) + .map(p => { + return {id: p.i + states.length - 1, parent: p.state, color: p.color, name: p.fullName}; + }); return states.concat(provinces); - } + }; - const getProvincesData = () => pack.provinces.map(p => { - const color = p.color ? p.color : "#ccc"; - const name = p.fullName ? p.fullName : p.name; - return {id:p.i ? p.i : 0, province: p.i ? 0 : null, color, name} - }); + const getProvincesData = () => + pack.provinces.map(p => { + const color = p.color ? p.color : "#ccc"; + const name = p.fullName ? p.fullName : p.name; + return {id: p.i ? p.i : 0, province: p.i ? 0 : null, color, name}; + }); const value = d => { if (this.value === "states") return d.state; if (this.value === "cultures") return d.culture; if (this.value === "parent") return d.parent; if (this.value === "provinces") return d.province; - } + }; - const base = this.value === "states" ? getStatesData() - : this.value === "cultures" ? getCulturesData() - : this.value === "parent" ? getParentData() : getProvincesData(); - burgs.forEach(b => b.id = b.i+base.length-1); + const base = this.value === "states" ? getStatesData() : this.value === "cultures" ? getCulturesData() : this.value === "parent" ? getParentData() : getProvincesData(); + burgs.forEach(b => (b.id = b.i + base.length - 1)); const data = base.concat(burgs); - const root = d3.stratify().parentId(d => value(d))(data) - .sum(d => d.population).sort((a, b) => b.value - a.value); + const root = d3 + .stratify() + .parentId(d => value(d))(data) + .sum(d => d.population) + .sort((a, b) => b.value - a.value); - node.data(treeLayout(root).leaves()).transition().duration(2000) - .attr("data-id", d => d.data.i).attr("fill", d => d.parent.data.color) - .attr("cx", d => d.x).attr("cy", d => d.y).attr("r", d => d.r); + node + .data(treeLayout(root).leaves()) + .transition() + .duration(2000) + .attr("data-id", d => d.data.i) + .attr("fill", d => d.parent.data.color) + .attr("cx", d => d.x) + .attr("cy", d => d.y) + .attr("r", d => d.r); } $("#alert").dialog({ - title: "Burgs bubble chart", width: fitContent(), - position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, buttons: {}, - close: () => {alertMessage.innerHTML = "";} + title: "Burgs bubble chart", + width: fitContent(), + position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, + buttons: {}, + close: () => { + alertMessage.innerHTML = ""; + } }); - } function downloadBurgsData() { - let data = "Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation ("+heightUnit.value+"),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers + let data = "Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation (" + heightUnit.value + "),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs valid.forEach(b => { @@ -394,10 +455,10 @@ function overviewBurgs() { data += b.name + ","; const province = pack.cells.province[b.cell]; data += province ? pack.provinces[province].fullName + "," : ","; - data += b.state ? pack.states[b.state].fullName +"," : pack.states[b.state].name + ","; + data += b.state ? pack.states[b.state].fullName + "," : pack.states[b.state].name + ","; data += pack.cultures[b.culture].name + ","; data += pack.religions[pack.cells.religion[b.cell]].name + ","; - data += rn(b.population * populationRate.value * urbanization.value) + ","; + data += rn(b.population * populationRate * urbanization) + ","; // add geography data data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ","; @@ -423,44 +484,62 @@ function overviewBurgs() { If you do not want to change the name, just leave it as is`; alertMessage.innerHTML = message; - $("#alert").dialog({title: "Burgs bulk renaming", width:"22em", + $("#alert").dialog({ + title: "Burgs bulk renaming", + width: "22em", position: {my: "center", at: "center", of: "svg"}, buttons: { - Download: function() { - const data = pack.burgs.filter(b => b.i && !b.removed).map(b => b.name).join("\r\n"); + Download: function () { + const data = pack.burgs + .filter(b => b.i && !b.removed) + .map(b => b.name) + .join("\r\n"); const name = getFileName("Burg names") + ".txt"; downloadFile(data, name); }, Upload: () => burgsListToLoad.click(), - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } function importBurgNames(dataLoaded) { - if (!dataLoaded) {tip("Cannot load the file, please check the format", false, "error"); return;} + if (!dataLoaded) { + tip("Cannot load the file, please check the format", false, "error"); + return; + } const data = dataLoaded.split("\r\n"); - if (!data.length) {tip("Cannot parse the list, please check the file format", false, "error"); return;} + if (!data.length) { + tip("Cannot parse the list, please check the file format", false, "error"); + return; + } - let change = [], message = `Burgs will be renamed as below. Please confirm`; + let change = [], + message = `Burgs will be renamed as below. Please confirm`; message += ``; const burgs = pack.burgs.filter(b => b.i && !b.removed); - for (let i=0; i < data.length && i <= burgs.length; i++) { + for (let i = 0; i < data.length && i <= burgs.length; i++) { const v = data[i]; if (!v || !burgs[i] || v == burgs[i].name) continue; - change.push({id:burgs[i].i, name: v}); + change.push({id: burgs[i].i, name: v}); message += ``; } message += `
IdCurrent nameNew Name
${burgs[i].i}${burgs[i].name}${v}
`; - if (!change.length) message = "No changes found in the file. Please change some names to get a result" + if (!change.length) message = "No changes found in the file. Please change some names to get a result"; alertMessage.innerHTML = message; - $("#alert").dialog({title: "Burgs bulk renaming", width:"22em", + $("#alert").dialog({ + title: "Burgs bulk renaming", + width: "22em", position: {my: "center", at: "center", of: "svg"}, buttons: { - Cancel: function() {$(this).dialog("close");}, - Confirm: function() { - for (let i=0; i < change.length; i++) { + Cancel: function () { + $(this).dialog("close"); + }, + Confirm: function () { + for (let i = 0; i < change.length; i++) { const id = change[i].id; pack.burgs[id].name = change[i].name; burgLabels.select("[data-id='" + id + "']").text(change[i].name); @@ -475,13 +554,17 @@ function overviewBurgs() { function triggerAllBurgsRemove() { alertMessage.innerHTML = `Are you sure you want to remove all unlocked burgs except for capitals?
To remove a capital you have to remove a state first`; - $("#alert").dialog({resizable: false, title: "Remove all burgs", + $("#alert").dialog({ + resizable: false, + title: "Remove all burgs", buttons: { - Remove: function() { + Remove: function () { $(this).dialog("close"); removeAllBurgs(); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } diff --git a/modules/ui/cultures-editor.js b/modules/ui/cultures-editor.js index ecb1060e..75aa2223 100644 --- a/modules/ui/cultures-editor.js +++ b/modules/ui/cultures-editor.js @@ -16,7 +16,10 @@ function editCultures() { modules.editCultures = true; $("#culturesEditor").dialog({ - title: "Cultures Editor", resizable: false, width: fitContent(), close: closeCulturesEditor, + title: "Cultures Editor", + resizable: false, + width: fitContent(), + close: closeCulturesEditor, position: {my: "right top", at: "right-10 top+10", of: "svg"} }); body.focus(); @@ -42,8 +45,9 @@ function editCultures() { } function culturesCollectStatistics() { - const cells = pack.cells, cultures = pack.cultures; - cultures.forEach(c => c.cells = c.area = c.rural = c.urban = 0); + const cells = pack.cells, + cultures = pack.cultures; + cultures.forEach(c => (c.cells = c.area = c.rural = c.urban = 0)); for (const i of cells.i) { if (cells.h[i] < 20) continue; @@ -58,16 +62,18 @@ function editCultures() { // add line for each culture function culturesEditorAddLines() { const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; - let lines = "", totalArea = 0, totalPopulation = 0; + 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); - const rural = c.rural * populationRate.value; - const urban = c.urban * populationRate.value * urbanization.value; + const area = c.area * distanceScaleInput.value ** 2; + const rural = c.rural * populationRate; + const urban = c.urban * populationRate * urbanization; const population = rn(rural + urban); const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to edit`; totalArea += area; @@ -140,7 +146,10 @@ function editCultures() { culturesHeader.querySelector("div[data-sortby='emblems']").style.display = selectShape ? "inline-block" : "none"; - if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} + if (body.dataset.type === "percentage") { + body.dataset.type = "absolute"; + togglePercentageMode(); + } applySorting(culturesHeader); $("#culturesEditor").dialog({width: fitContent()}); } @@ -148,18 +157,20 @@ function editCultures() { function getTypeOptions(type) { let options = ""; const types = ["Generic", "River", "Lake", "Naval", "Nomadic", "Hunting", "Highland"]; - types.forEach(t => options += ``); + types.forEach(t => (options += ``)); return options; } function getBaseOptions(base) { let options = ""; - nameBases.forEach((n, i) => options += ``); + nameBases.forEach((n, i) => (options += ``)); return options; } function getShapeOptions(selected) { - const shapes = Object.keys(COA.shields.types).map(type => Object.keys(COA.shields[type])).flat(); + const shapes = Object.keys(COA.shields.types) + .map(type => Object.keys(COA.shields[type])) + .flat(); return shapes.map(shape => ``); } @@ -167,10 +178,12 @@ function editCultures() { const culture = +event.target.dataset.id; const info = document.getElementById("cultureInfo"); if (info) { - d3.select("#hierarchy").select("g[data-id='"+culture+"'] > path").classed("selected", 1); + d3.select("#hierarchy") + .select("g[data-id='" + culture + "'] > path") + .classed("selected", 1); const c = pack.cultures[culture]; - const rural = c.rural * populationRate.value; - const urban = c.urban * populationRate.value * urbanization.value; + const rural = c.rural * populationRate; + const urban = c.urban * populationRate * urbanization; const population = rural + urban > 0 ? si(rn(rural + urban)) + " people" : "Extinct"; info.innerHTML = `${c.name} culture. ${c.type}. ${population}`; tip("Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation"); @@ -179,22 +192,42 @@ function editCultures() { if (!layerIsOn("toggleCultures")) return; if (customization) return; const animate = d3.transition().duration(2000).ease(d3.easeSinIn); - cults.select("#culture"+culture).raise().transition(animate).attr("stroke-width", 2.5).attr("stroke", "#d0240f"); - debug.select("#cultureCenter"+culture).raise().transition(animate).attr("r", 8).attr("stroke", "#d0240f"); + cults + .select("#culture" + culture) + .raise() + .transition(animate) + .attr("stroke-width", 2.5) + .attr("stroke", "#d0240f"); + debug + .select("#cultureCenter" + culture) + .raise() + .transition(animate) + .attr("r", 8) + .attr("stroke", "#d0240f"); } function cultureHighlightOff(event) { const culture = +event.target.dataset.id; const info = document.getElementById("cultureInfo"); if (info) { - d3.select("#hierarchy").select("g[data-id='"+culture+"'] > path").classed("selected", 0); + d3.select("#hierarchy") + .select("g[data-id='" + culture + "'] > path") + .classed("selected", 0); info.innerHTML = "‍"; tip(""); } if (!layerIsOn("toggleCultures")) return; - cults.select("#culture"+culture).transition().attr("stroke-width", null).attr("stroke", null); - debug.select("#cultureCenter"+culture).transition().attr("r", 6).attr("stroke", null); + cults + .select("#culture" + culture) + .transition() + .attr("stroke-width", null) + .attr("stroke", null); + debug + .select("#cultureCenter" + culture) + .transition() + .attr("r", 6) + .attr("stroke", null); } function cultureChangeColor() { @@ -202,12 +235,15 @@ function editCultures() { const currentFill = el.getAttribute("fill"); const culture = +el.parentNode.parentNode.dataset.id; - const callback = function(fill) { + const callback = function (fill) { el.setAttribute("fill", fill); pack.cultures[culture].color = fill; - cults.select("#culture"+culture).attr("fill", fill).attr("stroke", fill); - debug.select("#cultureCenter"+culture).attr("fill", fill); - } + cults + .select("#culture" + culture) + .attr("fill", fill) + .attr("stroke", fill); + debug.select("#cultureCenter" + culture).attr("fill", fill); + }; openPicker(currentFill, callback); } @@ -216,7 +252,10 @@ function editCultures() { const culture = +this.parentNode.dataset.id; this.parentNode.dataset.name = this.value; pack.cultures[culture].name = this.value; - pack.cultures[culture].code = abbreviate(this.value, pack.cultures.map(c => c.code)); + pack.cultures[culture].code = abbreviate( + this.value, + pack.cultures.map(c => c.code) + ); } function cultureChangeExpansionism() { @@ -249,7 +288,7 @@ function editCultures() { 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; @@ -268,7 +307,7 @@ function editCultures() { 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 + burg.coa.shield = shape; rerenderCOA("burgCOA" + burg.i, burg.coa); }); } @@ -276,61 +315,72 @@ function editCultures() { function changePopulation() { const culture = +this.parentNode.dataset.id; const c = pack.cultures[culture]; - if (!c.cells) {tip("Culture does not have any cells, cannot change population", false, "error"); return;} - const rural = rn(c.rural * populationRate.value); - const urban = rn(c.urban * populationRate.value * urbanization.value); + if (!c.cells) { + tip("Culture does not have any cells, cannot change population", false, "error"); + return; + } + const rural = rn(c.rural * populationRate); + const urban = rn(c.urban * populationRate * urbanization); const total = rural + urban; const l = n => Number(n).toLocaleString(); const burgs = pack.burgs.filter(b => !b.removed && b.culture === culture); alertMessage.innerHTML = ` Rural: - Urban: + Urban:

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

`; - const update = function() { + const update = function () { const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; if (isNaN(totalNew)) return; totalPop.innerHTML = l(totalNew); - totalPopPerc.innerHTML = rn(totalNew / total * 100); - } + totalPopPerc.innerHTML = rn((totalNew / total) * 100); + }; ruralPop.oninput = () => update(); urbanPop.oninput = () => update(); $("#alert").dialog({ - resizable: false, title: "Change culture population", width: "24em", buttons: { - Apply: function() {applyPopulationChange(); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Change culture population", + width: "24em", + buttons: { + Apply: function () { + applyPopulationChange(); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); function applyPopulationChange() { const ruralChange = ruralPop.value / rural; if (isFinite(ruralChange) && ruralChange !== 1) { const cells = pack.cells.i.filter(i => pack.cells.culture[i] === culture); - cells.forEach(i => pack.cells.pop[i] *= ruralChange); + cells.forEach(i => (pack.cells.pop[i] *= ruralChange)); } if (!isFinite(ruralChange) && +ruralPop.value > 0) { - const points = ruralPop.value / populationRate.value; + const points = ruralPop.value / populationRate; const cells = pack.cells.i.filter(i => pack.cells.culture[i] === culture); const pop = rn(points / cells.length); - cells.forEach(i => pack.cells.pop[i] = pop); + cells.forEach(i => (pack.cells.pop[i] = pop)); } const urbanChange = urbanPop.value / urban; if (isFinite(urbanChange) && urbanChange !== 1) { - burgs.forEach(b => b.population = rn(b.population * urbanChange, 4)); + burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4))); } if (!isFinite(urbanChange) && +urbanPop.value > 0) { - const points = urbanPop.value / populationRate.value / urbanization.value; + const points = urbanPop.value / populationRate / urbanization; const population = rn(points / burgs.length, 4); - burgs.forEach(b => b.population = population); + burgs.forEach(b => (b.population = population)); } refreshCulturesEditor(); } - } function cultureRegenerateBurgs() { @@ -339,7 +389,7 @@ function editCultures() { const cBurgs = pack.burgs.filter(b => b.culture === culture && !b.lock); cBurgs.forEach(b => { b.name = Names.getCulture(culture); - labels.select("[data-id='" + b.i +"']").text(b.name); + labels.select("[data-id='" + b.i + "']").text(b.name); }); tip(`Names for ${cBurgs.length} burgs are regenerated`, false, "success"); } @@ -349,40 +399,64 @@ function editCultures() { const culture = +this.parentNode.dataset.id; alertMessage.innerHTML = "Are you sure you want to remove the culture?
This action cannot be reverted"; - $("#alert").dialog({resizable: false, title: "Remove culture", + $("#alert").dialog({ + resizable: false, + title: "Remove culture", buttons: { - Remove: function() { - cults.select("#culture"+culture).remove(); - debug.select("#cultureCenter"+culture).remove(); + 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.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;}); + pack.cultures.forEach(c => { + if (c.origin === culture) c.origin = origin; + }); refreshCulturesEditor(); $(this).dialog("close"); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } function drawCultureCenters() { - const tooltip = 'Drag to move the culture center (ancestral home)'; + const tooltip = "Drag to move the culture center (ancestral home)"; debug.select("#cultureCenters").remove(); - const cultureCenters = debug.append("g").attr("id", "cultureCenters") - .attr("stroke-width", 2).attr("stroke", "#444444").style("cursor", "move"); + const cultureCenters = debug.append("g").attr("id", "cultureCenters").attr("stroke-width", 2).attr("stroke", "#444444").style("cursor", "move"); const data = pack.cultures.filter(c => c.i && !c.removed); - cultureCenters.selectAll("circle").data(data).enter().append("circle") - .attr("id", d => "cultureCenter"+d.i).attr("data-id", d => d.i) - .attr("r", 6).attr("fill", d => d.color) - .attr("cx", d => pack.cells.p[d.center][0]).attr("cy", d => pack.cells.p[d.center][1]) - .on("mouseenter", d => {tip(tooltip, true); body.querySelector(`div[data-id='${d.i}']`).classList.add("selected"); cultureHighlightOn(event);}) - .on("mouseleave", d => {tip('', true); body.querySelector(`div[data-id='${d.i}']`).classList.remove("selected"); cultureHighlightOff(event);}) + cultureCenters + .selectAll("circle") + .data(data) + .enter() + .append("circle") + .attr("id", d => "cultureCenter" + d.i) + .attr("data-id", d => d.i) + .attr("r", 6) + .attr("fill", d => d.color) + .attr("cx", d => pack.cells.p[d.center][0]) + .attr("cy", d => pack.cells.p[d.center][1]) + .on("mouseenter", d => { + tip(tooltip, true); + body.querySelector(`div[data-id='${d.i}']`).classList.add("selected"); + cultureHighlightOn(event); + }) + .on("mouseleave", d => { + tip("", true); + body.querySelector(`div[data-id='${d.i}']`).classList.remove("selected"); + cultureHighlightOff(event); + }) .call(d3.drag().on("start", cultureCenterDrag)); } @@ -399,8 +473,14 @@ function editCultures() { } function toggleLegend() { - if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend - const data = pack.cultures.filter(c => c.i && !c.removed && c.cells).sort((a, b) => b.area - a.area).map(c => [c.i, c.color, c.name]); + if (legend.selectAll("*").size()) { + clearLegend(); + return; + } // hide legend + const data = pack.cultures + .filter(c => c.i && !c.removed && c.cells) + .sort((a, b) => b.area - a.area) + .map(c => [c.i, c.color, c.name]); drawLegend("Cultures", data); } @@ -411,10 +491,10 @@ function editCultures() { const totalArea = +culturesFooterArea.dataset.area; const totalPopulation = +culturesFooterPopulation.dataset.population; - body.querySelectorAll(":scope > div").forEach(function(el) { - el.querySelector(".stateCells").innerHTML = rn(+el.dataset.cells / totalCells * 100) + "%"; - el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100) + "%"; - el.querySelector(".culturePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100) + "%"; + body.querySelectorAll(":scope > div").forEach(function (el) { + el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100) + "%"; + el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%"; + el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%"; }); } else { body.dataset.type = "absolute"; @@ -426,11 +506,18 @@ function editCultures() { // build hierarchy tree pack.cultures[0].origin = null; const cultures = pack.cultures.filter(c => !c.removed); - if (cultures.length < 3) {tip("Not enough cultures to show hierarchy", false, "error"); return;} - const root = d3.stratify().id(d => d.i).parentId(d => d.origin)(cultures); + if (cultures.length < 3) { + tip("Not enough cultures to show hierarchy", false, "error"); + return; + } + const root = d3 + .stratify() + .id(d => d.i) + .parentId(d => d.origin)(cultures); const treeWidth = root.leaves().length; const treeHeight = root.height; - const width = treeWidth * 40, height = treeHeight * 60; + const width = treeWidth * 40, + height = treeHeight * 60; const margin = {top: 10, right: 10, bottom: -5, left: 10}; const w = width - margin.left - margin.right; @@ -439,8 +526,7 @@ function editCultures() { // prepare svg alertMessage.innerHTML = "
"; - const svg = d3.select("#alertMessage").insert("svg", "#cultureInfo").attr("id", "hierarchy") - .attr("width", width).attr("height", height).style("text-anchor", "middle"); + const svg = d3.select("#alertMessage").insert("svg", "#cultureInfo").attr("id", "hierarchy").attr("width", width).attr("height", height).style("text-anchor", "middle"); const graph = svg.append("g").attr("transform", `translate(10, -45)`); const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa"); const nodes = graph.append("g"); @@ -448,49 +534,74 @@ function editCultures() { renderTree(); function renderTree() { treeLayout(root); - links.selectAll('path').data(root.links()).enter() - .append('path').attr("d", d => {return "M" + d.source.x + "," + d.source.y - + "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 - + " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 - + " " + d.target.x + "," + d.target.y;}); + links + .selectAll("path") + .data(root.links()) + .enter() + .append("path") + .attr("d", d => { + return "M" + d.source.x + "," + d.source.y + "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 + " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 + " " + d.target.x + "," + d.target.y; + }); - const node = nodes.selectAll('g').data(root.descendants()).enter() - .append('g').attr("data-id", d => d.data.i).attr("stroke", "#333333") + const node = nodes + .selectAll("g") + .data(root.descendants()) + .enter() + .append("g") + .attr("data-id", d => d.data.i) + .attr("stroke", "#333333") .attr("transform", d => `translate(${d.x}, ${d.y})`) .on("mouseenter", () => cultureHighlightOn(event)) .on("mouseleave", () => cultureHighlightOff(event)) .call(d3.drag().on("start", d => dragToReorigin(d))); - node.append("path").attr("d", d => { - if (!d.data.i) return "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0"; else // small circle - if (d.data.type === "Generic") return "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0"; else // circle - if (d.data.type === "River") return "M0,-14L14,0L0,14L-14,0Z"; else // diamond - if (d.data.type === "Lake") return "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z"; else // hexagon - if (d.data.type === "Naval") return "M-11,-11h22v22h-22Z"; // square - if (d.data.type === "Highland") return "M-11,-11l11,2l11,-2l-2,11l2,11l-11,-2l-11,2l2,-11Z"; // concave square - if (d.data.type === "Nomadic") return "M-4.97,-12.01 l9.95,0 l7.04,7.04 l0,9.95 l-7.04,7.04 l-9.95,0 l-7.04,-7.04 l0,-9.95Z"; // octagon - if (d.data.type === "Hunting") return "M0,-14l14,11l-6,14h-16l-6,-14Z"; // pentagon - return "M-11,-11h22v22h-22Z"; // square - }).attr("fill", d => d.data.i ? d.data.color : "#ffffff") - .attr("stroke-dasharray", d => d.data.cells ? "null" : "1"); + node + .append("path") + .attr("d", d => { + if (!d.data.i) return "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0"; + // small circle + else if (d.data.type === "Generic") return "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0"; + // circle + else if (d.data.type === "River") return "M0,-14L14,0L0,14L-14,0Z"; + // diamond + else if (d.data.type === "Lake") return "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z"; + // hexagon + else if (d.data.type === "Naval") return "M-11,-11h22v22h-22Z"; // square + if (d.data.type === "Highland") return "M-11,-11l11,2l11,-2l-2,11l2,11l-11,-2l-11,2l2,-11Z"; // concave square + if (d.data.type === "Nomadic") return "M-4.97,-12.01 l9.95,0 l7.04,7.04 l0,9.95 l-7.04,7.04 l-9.95,0 l-7.04,-7.04 l0,-9.95Z"; // octagon + if (d.data.type === "Hunting") return "M0,-14l14,11l-6,14h-16l-6,-14Z"; // pentagon + return "M-11,-11h22v22h-22Z"; // square + }) + .attr("fill", d => (d.data.i ? d.data.color : "#ffffff")) + .attr("stroke-dasharray", d => (d.data.cells ? "null" : "1")); - node.append("text").attr("dy", ".35em").text(d => d.data.i ? d.data.code : ''); + node + .append("text") + .attr("dy", ".35em") + .text(d => (d.data.i ? d.data.code : "")); } $("#alert").dialog({ - title: "Cultures tree", width: fitContent(), resizable: false, - position: {my: "left center", at: "left+10 center", of: "svg"}, buttons: {}, - close: () => {alertMessage.innerHTML = "";} + title: "Cultures tree", + width: fitContent(), + resizable: false, + position: {my: "left center", at: "left+10 center", of: "svg"}, + buttons: {}, + close: () => { + alertMessage.innerHTML = ""; + } }); function dragToReorigin(d) { - if (isCtrlClick(d3.event.sourceEvent)) {changeCode(d); return;} + if (isCtrlClick(d3.event.sourceEvent)) { + changeCode(d); + return; + } - const originLine = graph.append("path") - .attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`); + const originLine = graph.append("path").attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`); d3.event.on("drag", () => { - originLine.attr("d", `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`) + originLine.attr("d", `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`); }); d3.event.on("end", () => { @@ -504,14 +615,17 @@ function editCultures() { if (newOrigin == culture) newOrigin = 0; // move to top if (newOrigin && d.descendants().some(node => node.id == newOrigin)) return; // cannot be a child of its own child pack.cultures[culture].origin = d.data.origin = newOrigin; // change data - showHierarchy() // update hierarchy + showHierarchy(); // update hierarchy }); } function changeCode(d) { - prompt(`Please provide an abbreviation for culture: ${d.data.name}`, {default:d.data.code}, v => { + prompt(`Please provide an abbreviation for culture: ${d.data.name}`, {default: d.data.code}, v => { pack.cultures[d.data.i].code = v; - nodes.select("g[data-id='"+d.data.i+"']").select("text").text(v); + nodes + .select("g[data-id='" + d.data.i + "']") + .select("text") + .text(v); }); } } @@ -520,13 +634,13 @@ function editCultures() { if (!must && !culturesAutoChange.checked) return; pack.cells.culture = new Uint16Array(pack.cells.i.length); - pack.cultures.forEach(function(c) { + pack.cultures.forEach(function (c) { if (!c.i || c.removed) return; pack.cells.culture[c.center] = c.i; }); Cultures.expand(); drawCultures(); - pack.burgs.forEach(b => b.culture = pack.cells.culture[b.cell]); + pack.burgs.forEach(b => (b.culture = pack.cells.culture[b.cell])); refreshCulturesEditor(); document.querySelector("input.statePower").focus(); // to not trigger hotkeys } @@ -535,7 +649,7 @@ function editCultures() { if (!layerIsOn("toggleCultures")) toggleCultures(); customization = 4; cults.append("g").attr("id", "temp"); - document.querySelectorAll("#culturesBottom > *").forEach(el => el.style.display = "none"); + document.querySelectorAll("#culturesBottom > *").forEach(el => (el.style.display = "none")); document.getElementById("culturesManuallyButtons").style.display = "inline-block"; debug.select("#cultureCenters").style("display", "none"); @@ -543,14 +657,11 @@ function editCultures() { culturesHeader.querySelector("div[data-sortby='type']").style.left = "8.8em"; culturesHeader.querySelector("div[data-sortby='base']").style.left = "13.6em"; culturesFooter.style.display = "none"; - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + 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"}}); tip("Click on culture to select, drag the circle to change culture", true); - viewbox.style("cursor", "crosshair") - .on("click", selectCultureOnMapClick) - .call(d3.drag().on("start", dragCultureBrush)) - .on("touchmove mousemove", moveCultureBrush); + viewbox.style("cursor", "crosshair").on("click", selectCultureOnMapClick).call(d3.drag().on("start", dragCultureBrush)).on("touchmove mousemove", moveCultureBrush); body.querySelector("div").classList.add("selected"); } @@ -566,11 +677,11 @@ function editCultures() { const i = findCell(point[0], point[1]); if (pack.cells.h[i] < 20) return; - const assigned = cults.select("#temp").select("polygon[data-cell='"+i+"']"); + const assigned = cults.select("#temp").select("polygon[data-cell='" + i + "']"); const culture = assigned.size() ? +assigned.attr("data-culture") : pack.cells.culture[i]; body.querySelector("div.selected").classList.remove("selected"); - body.querySelector("div[data-id='"+culture+"']").classList.add("selected"); + body.querySelector("div[data-id='" + culture + "']").classList.add("selected"); } function dragCultureBrush() { @@ -594,8 +705,8 @@ function editCultures() { const cultureNew = +selected.dataset.id; const color = pack.cultures[cultureNew].color || "#ffffff"; - selection.forEach(function(i) { - const exists = temp.select("polygon[data-cell='"+i+"']"); + selection.forEach(function (i) { + const exists = temp.select("polygon[data-cell='" + i + "']"); const cultureOld = exists.size() ? +exists.attr("data-culture") : pack.cells.culture[i]; if (cultureNew === cultureOld) return; @@ -614,7 +725,7 @@ function editCultures() { function applyCultureManualAssignent() { const changed = cults.select("#temp").selectAll("polygon"); - changed.each(function() { + changed.each(function () { const i = +this.dataset.cell; const c = +this.dataset.culture; pack.cells.culture[i] = c; @@ -632,15 +743,15 @@ function editCultures() { customization = 0; cults.select("#temp").remove(); removeCircle(); - document.querySelectorAll("#culturesBottom > *").forEach(el => el.style.display = "inline-block"); + document.querySelectorAll("#culturesBottom > *").forEach(el => (el.style.display = "inline-block")); document.getElementById("culturesManuallyButtons").style.display = "none"; 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"; - 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"}}); + 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"}}); debug.select("#cultureCenters").style("display", null); restoreDefaultEvents(); @@ -650,28 +761,37 @@ function editCultures() { } function enterAddCulturesMode() { - if (this.classList.contains("pressed")) {exitAddCultureMode(); return;}; + if (this.classList.contains("pressed")) { + exitAddCultureMode(); + return; + } customization = 9; this.classList.add("pressed"); tip("Click on the map to add a new culture", true); viewbox.style("cursor", "crosshair").on("click", addCulture); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none")); } function exitAddCultureMode() { customization = 0; restoreDefaultEvents(); clearMainTip(); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); if (culturesAdd.classList.contains("pressed")) culturesAdd.classList.remove("pressed"); } function addCulture() { const point = d3.mouse(this); const center = findCell(point[0], point[1]); - if (pack.cells.h[center] < 20) {tip("You cannot place culture center into the water. Please click on a land cell", false, "error"); return;} + if (pack.cells.h[center] < 20) { + tip("You cannot place culture center into the water. Please click on a land cell", false, "error"); + return; + } const occupied = pack.cultures.some(c => !c.removed && c.center === center); - if (occupied) {tip("This cell is already a culture center. Please select a different cell", false, "error"); return;} + if (occupied) { + tip("This cell is already a culture center. Please select a different cell", false, "error"); + return; + } if (d3.event.shiftKey === false) exitAddCultureMode(); Cultures.add(center); @@ -682,9 +802,9 @@ 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,Emblems Shape\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) { + body.querySelectorAll(":scope > div").forEach(function (el) { data += el.dataset.id + ","; data += el.dataset.name + ","; data += el.dataset.color + ","; @@ -705,7 +825,6 @@ function editCultures() { function closeCulturesEditor() { debug.select("#cultureCenters").remove(); exitCulturesManualAssignment("close"); - exitAddCultureMode() + exitAddCultureMode(); } - } diff --git a/modules/ui/elevation-profile.js b/modules/ui/elevation-profile.js index 8c54518a..4f148e9b 100644 --- a/modules/ui/elevation-profile.js +++ b/modules/ui/elevation-profile.js @@ -2,10 +2,13 @@ function showEPForRoute(node) { const points = []; - debug.select("#controlPoints").selectAll("circle").each(function() { - const i = findCell(this.getAttribute("cx"), this.getAttribute("cy")); - points.push(i); - }); + debug + .select("#controlPoints") + .selectAll("circle") + .each(function () { + const i = findCell(this.getAttribute("cx"), this.getAttribute("cy")); + points.push(i); + }); const routeLen = node.getTotalLength() * distanceScaleInput.value; showElevationProfile(points, routeLen, false); @@ -13,10 +16,13 @@ function showEPForRoute(node) { function showEPForRiver(node) { const points = []; - debug.select("#controlPoints").selectAll("circle").each(function() { - const i = findCell(this.getAttribute("cx"), this.getAttribute("cy")); - points.push(i); - }); + debug + .select("#controlPoints") + .selectAll("circle") + .each(function () { + const i = findCell(this.getAttribute("cx"), this.getAttribute("cy")); + points.push(i); + }); const riverLen = (node.getTotalLength() / 2) * distanceScaleInput.value; showElevationProfile(points, riverLen, true); @@ -29,7 +35,9 @@ function showElevationProfile(data, routeLen, isRiver) { document.getElementById("epSave").addEventListener("click", downloadCSV); $("#elevationProfile").dialog({ - title: "Elevation profile", resizable: false, width: window.width, + title: "Elevation profile", + resizable: false, + width: window.width, close: closeElevationProfile, position: {my: "left top", at: "left+20 bottom-500", of: window, collision: "fit"} }); @@ -37,27 +45,30 @@ function showElevationProfile(data, routeLen, isRiver) { // prevent river graphs from showing rivers as flowing uphill - remember the general slope let slope = 0; if (isRiver) { - if (pack.cells.h[data[0]] < pack.cells.h[data[data.length-1]]) { + if (pack.cells.h[data[0]] < pack.cells.h[data[data.length - 1]]) { slope = 1; // up-hill - } else if (pack.cells.h[data[0]] > pack.cells.h[data[data.length-1]]) { + } else if (pack.cells.h[data[0]] > pack.cells.h[data[data.length - 1]]) { slope = -1; // down-hill } } - const chartWidth = window.innerWidth-180, chartHeight = 300; // height of our land/sea profile, excluding the biomes data below - const xOffset = 80, yOffset = 80; // this is our drawing starting point from top-left (y = 0) of SVG + const chartWidth = window.innerWidth - 180, + chartHeight = 300; // height of our land/sea profile, excluding the biomes data below + const xOffset = 80, + yOffset = 80; // this is our drawing starting point from top-left (y = 0) of SVG const biomesHeight = 40; let lastBurgIndex = 0; let lastBurgCell = 0; let burgCount = 0; - let chartData = {biome:[], burg:[], cell:[], height:[], mi:1000000, ma:0, mih: 100, mah: 0, points:[]}; + let chartData = {biome: [], burg: [], cell: [], height: [], mi: 1000000, ma: 0, mih: 100, mah: 0, points: []}; for (let i = 0, prevB = 0, prevH = -1; i < data.length; i++) { let cell = data[i]; let h = pack.cells.h[cell]; if (h < 20) { const f = pack.features[pack.cells.f[cell]]; - if (f.type === "lake") h = f.height; else h = 20; + if (f.type === "lake") h = f.height; + else h = 20; } // check for river up-hill @@ -73,21 +84,25 @@ function showElevationProfile(data, routeLen, isRiver) { let b = pack.cells.burg[cell]; if (b == prevB) b = 0; else prevB = b; - if (b) { burgCount++; lastBurgIndex = i; lastBurgCell = cell; } + if (b) { + burgCount++; + lastBurgIndex = i; + lastBurgCell = cell; + } chartData.biome[i] = pack.cells.biome[cell]; chartData.burg[i] = b; chartData.cell[i] = cell; let sh = getHeight(h); - chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(' '))); + chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(" "))); chartData.mih = Math.min(chartData.mih, h); chartData.mah = Math.max(chartData.mah, h); chartData.mi = Math.min(chartData.mi, chartData.height[i]); chartData.ma = Math.max(chartData.ma, chartData.height[i]); } - if (lastBurgIndex != 0 && lastBurgCell == chartData.cell[data.length-1] && lastBurgIndex < data.length-1) { - chartData.burg[data.length-1] = chartData.burg[lastBurgIndex]; + if (lastBurgIndex != 0 && lastBurgCell == chartData.cell[data.length - 1] && lastBurgIndex < data.length - 1) { + chartData.burg[data.length - 1] = chartData.burg[lastBurgIndex]; chartData.burg[lastBurgIndex] = 0; } @@ -96,7 +111,7 @@ function showElevationProfile(data, routeLen, isRiver) { function downloadCSV() { let data = "Point,X,Y,Cell,Height,Height value,Population,Burg,Burg population,Biome,Biome color,Culture,Culture color,Religion,Religion color,Province,Province color,State,State color\n"; // headers - for (let k=0; k < chartData.points.length; k++) { + for (let k = 0; k < chartData.points.length; k++) { let cell = chartData.cell[k]; let burg = pack.cells.burg[cell]; let biome = pack.cells.biome[cell]; @@ -107,16 +122,16 @@ function showElevationProfile(data, routeLen, isRiver) { let pop = pack.cells.pop[cell]; let h = pack.cells.h[cell]; - data += k+1 + ","; + data += k + 1 + ","; data += chartData.points[k][0] + ","; data += chartData.points[k][1] + ","; data += cell + ","; data += getHeight(h) + ","; data += h + ","; - data += rn(pop * populationRate.value) + ","; + data += rn(pop * populationRate) + ","; if (burg) { data += pack.burgs[burg].name + ","; - data += (pack.burgs[burg].population * populationRate.value * urbanization.value) + ","; + data += pack.burgs[burg].population * populationRate * urbanization + ","; } else { data += ",0,"; } @@ -142,18 +157,27 @@ function showElevationProfile(data, routeLen, isRiver) { chartData.points = []; let heightScale = 100 / parseInt(epScaleRange.value); - heightScale *= .9; // curves cause the heights to go slightly higher, adjust here + heightScale *= 0.9; // curves cause the heights to go slightly higher, adjust here const xscale = d3.scaleLinear().domain([0, data.length]).range([0, chartWidth]); - const yscale = d3.scaleLinear().domain([0, chartData.ma * heightScale]).range([chartHeight, 0]); + const yscale = d3 + .scaleLinear() + .domain([0, chartData.ma * heightScale]) + .range([chartHeight, 0]); - for (let i=0; i= chartData.mih; k--) { - let perc = 1 - (k - chartData.mih) / (chartData.mah - chartData.mih); - landdef.append("stop").attr("offset", perc*100 + "%").attr("style", "stop-color:" + getColor(k, colors) + ";stop-opacity:1"); + for (let k = chartData.mah; k >= chartData.mih; k--) { + let perc = 1 - (k - chartData.mih) / (chartData.mah - chartData.mih); + landdef + .append("stop") + .attr("offset", perc * 100 + "%") + .attr("style", "stop-color:" + getColor(k, colors) + ";stop-opacity:1"); } } // land let curve = d3.line().curve(d3.curveBasis); // see https://github.com/d3/d3-shape#curves let epCurveIndex = parseInt(epCurve.selectedIndex); - switch(epCurveIndex) { - case 0 : curve = d3.line().curve(d3.curveLinear); break; - case 1 : curve = d3.line().curve(d3.curveBasis); break; - case 2 : curve = d3.line().curve(d3.curveBundle.beta(1)); break; - case 3 : curve = d3.line().curve(d3.curveCatmullRom.alpha(0.5)); break; - case 4 : curve = d3.line().curve(d3.curveMonotoneX); break; - case 5 : curve = d3.line().curve(d3.curveNatural); break; + switch (epCurveIndex) { + case 0: + curve = d3.line().curve(d3.curveLinear); + break; + case 1: + curve = d3.line().curve(d3.curveBasis); + break; + case 2: + curve = d3.line().curve(d3.curveBundle.beta(1)); + break; + case 3: + curve = d3.line().curve(d3.curveCatmullRom.alpha(0.5)); + break; + case 4: + curve = d3.line().curve(d3.curveMonotoneX); + break; + case 5: + curve = d3.line().curve(d3.curveNatural); + break; } // copy the points so that we can add extra straight pieces, else we get curves at the ends of the chart let extra = chartData.points.slice(); let path = curve(extra); // this completes the right-hand side and bottom of our land "polygon" - path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(extra[extra.length-1][1]); - path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset); - path += " L" + parseInt(xscale(0) + +xOffset) +"," + parseInt(yscale(0) + +yOffset); + path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(extra[extra.length - 1][1]); + path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset); + path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset); path += "Z"; chart.append("g").attr("id", "epland").append("path").attr("d", path).attr("stroke", "purple").attr("stroke-width", "0").attr("fill", "url(#landdef)"); // biome / heights let g = chart.append("g").attr("id", "epbiomes"); const hu = heightUnit.value; - for(let k=0; k < chartData.points.length; k++) { + for (let k = 0; k < chartData.points.length; k++) { const x = chartData.points[k][0]; const y = yOffset + chartHeight; const c = biomesData.color[chartData.biome[k]]; @@ -207,45 +252,53 @@ function showElevationProfile(data, routeLen, isRiver) { const state = pack.cells.state[cell]; let pop = pack.cells.pop[cell]; if (chartData.burg[k]) { - pop += pack.burgs[chartData.burg[k]].population * urbanization.value; + pop += pack.burgs[chartData.burg[k]].population * urbanization; } - const populationDesc = rn(pop * populationRate.value); + const populationDesc = rn(pop * populationRate); const provinceDesc = province ? ", " + pack.provinces[province].name : ""; - const dataTip = biomesData.name[chartData.biome[k]] + - provinceDesc + - ", " + pack.states[state].name + - ", " + pack.religions[religion].name + - ", " + pack.cultures[culture].name + - " (height: " + chartData.height[k] + " " + hu + ", population " + populationDesc + ", cell " + chartData.cell[k] + ")"; + const dataTip = biomesData.name[chartData.biome[k]] + provinceDesc + ", " + pack.states[state].name + ", " + pack.religions[religion].name + ", " + pack.cultures[culture].name + " (height: " + chartData.height[k] + " " + hu + ", population " + populationDesc + ", cell " + chartData.cell[k] + ")"; g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip); } - const xAxis = d3.axisBottom(xscale).ticks(10).tickFormat(function(d){ return (rn(d / chartData.points.length * routeLen) + " " + distanceUnitInput.value);}); - const yAxis = d3.axisLeft(yscale).ticks(5).tickFormat(function(d) { return d + " " + hu; }); + const xAxis = d3 + .axisBottom(xscale) + .ticks(10) + .tickFormat(function (d) { + return rn((d / chartData.points.length) * routeLen) + " " + distanceUnitInput.value; + }); + const yAxis = d3 + .axisLeft(yscale) + .ticks(5) + .tickFormat(function (d) { + return d + " " + hu; + }); const xGrid = d3.axisBottom(xscale).ticks(10).tickSize(-chartHeight).tickFormat(""); const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth).tickFormat(""); - chart.append("g") + chart + .append("g") .attr("id", "epxaxis") .attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset + 20) + ")") .call(xAxis) .selectAll("text") .style("text-anchor", "center") - .attr("transform", function(d) { - return "rotate(0)" // used to rotate labels, - anti-clockwise, + clockwise + .attr("transform", function (d) { + return "rotate(0)"; // used to rotate labels, - anti-clockwise, + clockwise }); - chart.append("g") + chart + .append("g") .attr("id", "epyaxis") - .attr("transform", "translate(" + parseInt(+xOffset-10) + "," + parseInt(+yOffset) + ")") + .attr("transform", "translate(" + parseInt(+xOffset - 10) + "," + parseInt(+yOffset) + ")") .call(yAxis); // add the X gridlines - chart.append("g") + chart + .append("g") .attr("id", "epxgrid") .attr("class", "epgrid") .attr("stroke-dasharray", "4 1") @@ -253,7 +306,8 @@ function showElevationProfile(data, routeLen, isRiver) { .call(xGrid); // add the Y gridlines - chart.append("g") + chart + .append("g") .attr("id", "epygrid") .attr("class", "epgrid") .attr("stroke-dasharray", "4 1") @@ -266,22 +320,33 @@ function showElevationProfile(data, routeLen, isRiver) { const add = 15; let xwidth = chartData.points[1][0] - chartData.points[0][0]; - for (let k=0; k 0) { let b = chartData.burg[k]; let x1 = chartData.points[k][0]; // left side of graph by default - if (k > 0) x1 += xwidth/2; // center it if not first - if (k == chartData.points.length-1) x1 = chartWidth + xOffset; // right part of graph - y1+=add; + if (k > 0) x1 += xwidth / 2; // center it if not first + if (k == chartData.points.length - 1) x1 = chartWidth + xOffset; // right part of graph + y1 += add; if (y1 >= yOffset) y1 = add; // burg name - g.append("text").attr("id", "ep" + b).attr("class", "epburglabel").attr("x", x1).attr("y", y1).attr("text-anchor", "middle"); + g.append("text") + .attr("id", "ep" + b) + .attr("class", "epburglabel") + .attr("x", x1) + .attr("y", y1) + .attr("text-anchor", "middle"); document.getElementById("ep" + b).innerHTML = pack.burgs[b].name; // arrow from burg name to graph line - g.append("path").attr("id", "eparrow" + b).attr("d", "M" + x1.toString() + "," + (y1+3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1]-3).toString()).attr("stroke", "darkgray").attr("fill", "lightgray").attr("stroke-width", "1").attr("marker-end", "url(#arrowhead)"); + g.append("path") + .attr("id", "eparrow" + b) + .attr("d", "M" + x1.toString() + "," + (y1 + 3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1] - 3).toString()) + .attr("stroke", "darkgray") + .attr("fill", "lightgray") + .attr("stroke-width", "1") + .attr("marker-end", "url(#arrowhead)"); } } } diff --git a/modules/ui/general.js b/modules/ui/general.js index 083643a1..d87afe06 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -134,7 +134,7 @@ function showMapTooltip(point, e, i, g) { if (subgroup === "burgLabels" || subgroup === "burgIcons") { const burg = +path[path.length - 10].dataset.id; const b = pack.burgs[burg]; - const population = si(b.population * populationRate.value * urbanization.value); + const population = si(b.population * populationRate * urbanization); tip(`${b.name}. Population: ${population}. Click to edit`); if (burgsOverview.offsetParent) highlightEditorLine(burgsOverview, burg, 5000); return; @@ -339,8 +339,8 @@ function getRiverInfo(id) { } function getCellPopulation(i) { - const rural = pack.cells.pop[i] * populationRate.value; - const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate.value * urbanization.value : 0; + const rural = pack.cells.pop[i] * populationRate; + const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate * urbanization : 0; return [rural, urban]; } diff --git a/modules/ui/layers.js b/modules/ui/layers.js index 0b640309..7dc3953f 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -6,18 +6,18 @@ restoreCustomPresets(); // run on-load function getDefaultPresets() { return { - "political": ["toggleBorders", "toggleIcons", "toggleIce", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"], - "cultural": ["toggleBorders", "toggleCultures", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar"], - "religions": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleReligions", "toggleRivers", "toggleRoutes", "toggleScaleBar"], - "provinces": ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"], - "biomes": ["toggleBiomes", "toggleIce", "toggleRivers", "toggleScaleBar"], - "heightmap": ["toggleHeight", "toggleRivers"], - "physical": ["toggleCoordinates", "toggleHeight", "toggleIce", "toggleRivers", "toggleScaleBar"], - "poi": ["toggleBorders", "toggleHeight", "toggleIce", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"], - "military": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleMilitary", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"], - "emblems": ["toggleBorders", "toggleIcons", "toggleIce", "toggleEmblems", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"], - "landmass": ["toggleScaleBar"] - } + political: ["toggleBorders", "toggleIcons", "toggleIce", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"], + cultural: ["toggleBorders", "toggleCultures", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar"], + religions: ["toggleBorders", "toggleIcons", "toggleLabels", "toggleReligions", "toggleRivers", "toggleRoutes", "toggleScaleBar"], + provinces: ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"], + biomes: ["toggleBiomes", "toggleIce", "toggleRivers", "toggleScaleBar"], + heightmap: ["toggleHeight", "toggleRivers"], + physical: ["toggleCoordinates", "toggleHeight", "toggleIce", "toggleRivers", "toggleScaleBar"], + poi: ["toggleBorders", "toggleHeight", "toggleIce", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"], + military: ["toggleBorders", "toggleIcons", "toggleLabels", "toggleMilitary", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"], + emblems: ["toggleBorders", "toggleIcons", "toggleIce", "toggleEmblems", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"], + landmass: ["toggleScaleBar"] + }; } function restoreCustomPresets() { @@ -42,10 +42,14 @@ function applyPreset() { // toggle layers on preset change function changePreset(preset) { const layers = presets[preset]; // layers to be turned on - document.getElementById("mapLayers").querySelectorAll("li").forEach(function(e) { - if (layers.includes(e.id) && !layerIsOn(e.id)) e.click(); // turn on - else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off - }); + document + .getElementById("mapLayers") + .querySelectorAll("li") + .forEach(function (e) { + if (layers.includes(e.id) && !layerIsOn(e.id)) e.click(); + // turn on + else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off + }); layersPreset.value = preset; localStorage.setItem("preset", preset); @@ -56,8 +60,10 @@ function changePreset(preset) { } function savePreset() { - prompt("Please provide a preset name", {default:""}, preset => { - presets[preset] = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort(); + prompt("Please provide a preset name", {default: ""}, preset => { + presets[preset] = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")) + .map(node => node.id) + .sort(); layersPreset.add(new Option(preset, preset, false, true)); localStorage.setItem("presets", JSON.stringify(presets)); localStorage.setItem("preset", preset); @@ -80,7 +86,9 @@ function removePreset() { } function getCurrentPreset() { - const layers = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort(); + const layers = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")) + .map(node => node.id) + .sort(); const defaultPresets = getDefaultPresets(); for (const preset in presets) { @@ -115,7 +123,7 @@ function restoreLayers() { if (layerIsOn("toggleEmblems")) drawEmblems(); // states are getting rendered each time, if it's not required than layers should be hidden - if (!layerIsOn("toggleBorders")) $('#borders').fadeOut(); + if (!layerIsOn("toggleBorders")) $("#borders").fadeOut(); if (!layerIsOn("toggleStates")) regions.style("display", "none").selectAll("path").remove(); } @@ -130,7 +138,10 @@ function toggleHeight(event) { drawHeightmap(); if (event && isCtrlClick(event)) editStyle("terrs"); } else { - if (event && isCtrlClick(event)) {editStyle("terrs"); return;} + if (event && isCtrlClick(event)) { + editStyle("terrs"); + return; + } turnButtonOff("toggleHeight"); terrs.selectAll("*").remove(); } @@ -139,7 +150,9 @@ function toggleHeight(event) { function drawHeightmap() { TIME && console.time("drawHeightmap"); terrs.selectAll("*").remove(); - const cells = pack.cells, vertices = pack.vertices, n = cells.i.length; + const cells = pack.cells, + vertices = pack.vertices, + n = cells.i.length; const used = new Uint8Array(cells.i.length); const paths = new Array(101).fill(""); @@ -148,10 +161,17 @@ function drawHeightmap() { const skip = +terrs.attr("skip") + 1; const simplification = +terrs.attr("relax"); switch (+terrs.attr("curve")) { - case 0: lineGen.curve(d3.curveBasisClosed); break; - case 1: lineGen.curve(d3.curveLinear); break; - case 2: lineGen.curve(d3.curveStep); break; - default: lineGen.curve(d3.curveBasisClosed); + case 0: + lineGen.curve(d3.curveBasisClosed); + break; + case 1: + lineGen.curve(d3.curveLinear); + break; + case 2: + lineGen.curve(d3.curveStep); + break; + default: + lineGen.curve(d3.curveBasisClosed); } let currentLayer = 20; @@ -171,7 +191,7 @@ function drawHeightmap() { paths[h] += round(lineGen(points)); } - terrs.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight).attr("fill", scheme(.8)); // draw base layer + terrs.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight).attr("fill", scheme(0.8)); // draw base layer for (const i of d3.range(20, 101)) { if (paths[i].length < 10) continue; const color = getColor(i, scheme); @@ -182,11 +202,11 @@ function drawHeightmap() { // connect vertices to chain function connectVertices(start, h) { const chain = []; // vertices chain to form a path - for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { const prev = chain[chain.length - 1]; // previous vertex in chain chain.push(current); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.h[c] === h).forEach(c => used[c] = 1); + c.filter(c => cells.h[c] === h).forEach(c => (used[c] = 1)); const c0 = c[0] >= n || cells.h[c[0]] < h; const c1 = c[1] >= n || cells.h[c[1]] < h; const c2 = c[2] >= n || cells.h[c[2]] < h; @@ -194,7 +214,10 @@ function drawHeightmap() { if (v[0] !== prev && c0 !== c1) current = v[0]; else if (v[1] !== prev && c1 !== c2) current = v[1]; else if (v[2] !== prev && c0 !== c2) current = v[2]; - if (current === chain[chain.length - 1]) {ERROR && console.error("Next vertex is not found"); break;} + if (current === chain[chain.length - 1]) { + ERROR && console.error("Next vertex is not found"); + break; + } } return chain; } @@ -227,7 +250,10 @@ function toggleTemp(event) { drawTemp(); if (event && isCtrlClick(event)) editStyle("temperature"); } else { - if (event && isCtrlClick(event)) {editStyle("temperature"); return;} + if (event && isCtrlClick(event)) { + editStyle("temperature"); + return; + } turnButtonOff("toggleTemp"); temperature.selectAll("*").remove(); } @@ -238,14 +264,20 @@ function drawTemp() { temperature.selectAll("*").remove(); lineGen.curve(d3.curveBasisClosed); const scheme = d3.scaleSequential(d3.interpolateSpectral); - const tMax = +temperatureEquatorOutput.max, tMin = +temperatureEquatorOutput.min, delta = tMax - tMin; + const tMax = +temperatureEquatorOutput.max, + tMin = +temperatureEquatorOutput.min, + delta = tMax - tMin; - const cells = grid.cells, vertices = grid.vertices, n = cells.i.length; + const cells = grid.cells, + vertices = grid.vertices, + n = cells.i.length; const used = new Uint8Array(n); // to detect already passed cells - const min = d3.min(cells.temp), max = d3.max(cells.temp); + const min = d3.min(cells.temp), + max = d3.max(cells.temp); const step = Math.max(Math.round(Math.abs(min - max) / 5), 1); - const isolines = d3.range(min+step, max, step); - const chains = [], labels = []; // store label coordinates + const isolines = d3.range(min + step, max, step); + const chains = [], + labels = []; // store label coordinates for (const i of cells.i) { const t = cells.temp[i]; @@ -256,7 +288,7 @@ function drawTemp() { //debug.append("circle").attr("r", 3).attr("cx", vertices.p[start][0]).attr("cy", vertices.p[start][1]).attr("fill", "red").attr("stroke", "black").attr("stroke-width", .3); const chain = connectVertices(start, t); // vertices chain to form a path - const relaxed = chain.filter((v, i) => i%4 === 0 || vertices.c[v].some(c => c >= n)); + const relaxed = chain.filter((v, i) => i % 4 === 0 || vertices.c[v].some(c => c >= n)); if (relaxed.length < 6) continue; const points = relaxed.map(v => vertices.p[v]); chains.push([t, points]); @@ -264,17 +296,32 @@ function drawTemp() { } // min temp isoline covers all graph - temperature.append("path").attr("d", `M0,0 h${graphWidth} v${graphHeight} h${-graphWidth} Z`).attr("fill", scheme(1 - (min - tMin) / delta)).attr("stroke", "none"); + temperature + .append("path") + .attr("d", `M0,0 h${graphWidth} v${graphHeight} h${-graphWidth} Z`) + .attr("fill", scheme(1 - (min - tMin) / delta)) + .attr("stroke", "none"); for (const t of isolines) { - const path = chains.filter(c => c[0] === t).map(c => round(lineGen(c[1]))).join(""); + const path = chains + .filter(c => c[0] === t) + .map(c => round(lineGen(c[1]))) + .join(""); if (!path) continue; - const fill = scheme(1 - (t - tMin) / delta), stroke = d3.color(fill).darker(.2); + const fill = scheme(1 - (t - tMin) / delta), + stroke = d3.color(fill).darker(0.2); temperature.append("path").attr("d", path).attr("fill", fill).attr("stroke", stroke); } const tempLabels = temperature.append("g").attr("id", "tempLabels").attr("fill-opacity", 1); - tempLabels.selectAll("text").data(labels).enter().append("text").attr("x", d => d[0]).attr("y", d => d[1]).text(d => convertTemperature(d[2])); + tempLabels + .selectAll("text") + .data(labels) + .enter() + .append("text") + .attr("x", d => d[0]) + .attr("y", d => d[1]) + .text(d => convertTemperature(d[2])); // find cell with temp < isotherm and find vertex to start path detection function findStart(i, t) { @@ -285,12 +332,12 @@ function drawTemp() { function addLabel(points, t) { const c = svgWidth / 2; // map center x coordinate // add label on isoline top center - const tc = points[d3.scan(points, (a, b) => (a[1] - b[1]) + (Math.abs(a[0] - c) - Math.abs(b[0] - c)) / 2)]; + const tc = points[d3.scan(points, (a, b) => a[1] - b[1] + (Math.abs(a[0] - c) - Math.abs(b[0] - c)) / 2)]; pushLabel(tc[0], tc[1], t); // add label on isoline bottom center if (points.length > 20) { - const bc = points[d3.scan(points, (a, b) => (b[1] - a[1]) + (Math.abs(a[0] - c) - Math.abs(b[0] - c)) / 2)]; + const bc = points[d3.scan(points, (a, b) => b[1] - a[1] + (Math.abs(a[0] - c) - Math.abs(b[0] - c)) / 2)]; const dist2 = (tc[1] - bc[1]) ** 2 + (tc[0] - bc[0]) ** 2; // square distance between this and top point if (dist2 > 100) pushLabel(bc[0], bc[1], t); } @@ -305,11 +352,11 @@ function drawTemp() { // connect vertices to chain function connectVertices(start, t) { const chain = []; // vertices chain to form a path - for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { const prev = chain[chain.length - 1]; // previous vertex in chain chain.push(current); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.temp[c] === t).forEach(c => used[c] = 1); + c.filter(c => cells.temp[c] === t).forEach(c => (used[c] = 1)); const c0 = c[0] >= n || cells.temp[c[0]] < t; const c1 = c[1] >= n || cells.temp[c[1]] < t; const c2 = c[2] >= n || cells.temp[c[2]] < t; @@ -317,7 +364,10 @@ function drawTemp() { if (v[0] !== prev && c0 !== c1) current = v[0]; else if (v[1] !== prev && c1 !== c2) current = v[1]; else if (v[2] !== prev && c0 !== c2) current = v[2]; - if (current === chain[chain.length - 1]) {ERROR && console.error("Next vertex is not found"); break;} + if (current === chain[chain.length - 1]) { + ERROR && console.error("Next vertex is not found"); + break; + } } chain.push(start); return chain; @@ -331,7 +381,10 @@ function toggleBiomes(event) { drawBiomes(); if (event && isCtrlClick(event)) editStyle("biomes"); } else { - if (event && isCtrlClick(event)) {editStyle("biomes"); return;} + if (event && isCtrlClick(event)) { + editStyle("biomes"); + return; + } biomes.selectAll("path").remove(); turnButtonOff("toggleBiomes"); } @@ -339,7 +392,9 @@ function toggleBiomes(event) { function drawBiomes() { biomes.selectAll("path").remove(); - const cells = pack.cells, vertices = pack.vertices, n = cells.i.length; + const cells = pack.cells, + vertices = pack.vertices, + n = cells.i.length; const used = new Uint8Array(cells.i.length); const paths = new Array(biomesData.i.length).fill(""); @@ -352,23 +407,31 @@ function drawBiomes() { const edgeVerticle = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] !== b)); const chain = connectVertices(edgeVerticle, b); if (chain.length < 3) continue; - const points = clipPoly(chain.map(v => vertices.p[v]), 1); + const points = clipPoly( + chain.map(v => vertices.p[v]), + 1 + ); paths[b] += "M" + points.join("L") + "Z"; } - paths.forEach(function(d, i) { + paths.forEach(function (d, i) { if (d.length < 10) return; - biomes.append("path").attr("d", d).attr("fill", biomesData.color[i]).attr("stroke", biomesData.color[i]).attr("id", "biome"+i); + biomes + .append("path") + .attr("d", d) + .attr("fill", biomesData.color[i]) + .attr("stroke", biomesData.color[i]) + .attr("id", "biome" + i); }); // connect vertices to chain function connectVertices(start, b) { const chain = []; // vertices chain to form a path - for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { const prev = chain[chain.length - 1]; // previous vertex in chain chain.push(current); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.biome[c] === b).forEach(c => used[c] = 1); + c.filter(c => cells.biome[c] === b).forEach(c => (used[c] = 1)); const c0 = c[0] >= n || cells.biome[c[0]] !== b; const c1 = c[1] >= n || cells.biome[c[1]] !== b; const c2 = c[2] >= n || cells.biome[c[2]] !== b; @@ -376,7 +439,10 @@ function drawBiomes() { if (v[0] !== prev && c0 !== c1) current = v[0]; else if (v[1] !== prev && c1 !== c2) current = v[1]; else if (v[2] !== prev && c0 !== c2) current = v[2]; - if (current === chain[chain.length - 1]) {ERROR && console.error("Next vertex is not found"); break;} + if (current === chain[chain.length - 1]) { + ERROR && console.error("Next vertex is not found"); + break; + } } return chain; } @@ -388,7 +454,10 @@ function togglePrec(event) { drawPrec(); if (event && isCtrlClick(event)) editStyle("prec"); } else { - if (event && isCtrlClick(event)) {editStyle("prec"); return;} + if (event && isCtrlClick(event)) { + editStyle("prec"); + return; + } turnButtonOff("togglePrec"); const hide = d3.transition().duration(1000).ease(d3.easeSinIn); prec.selectAll("text").attr("opacity", 1).transition(hide).attr("opacity", 0); @@ -399,15 +468,23 @@ function togglePrec(event) { function drawPrec() { prec.selectAll("circle").remove(); - const cells = grid.cells, p = grid.points; + const cells = grid.cells, + p = grid.points; prec.style("display", "block"); const show = d3.transition().duration(800).ease(d3.easeSinIn); prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1); const data = cells.i.filter(i => cells.h[i] >= 20 && cells.prec[i]); - prec.selectAll("circle").data(data).enter().append("circle") - .attr("cx", d => p[d][0]).attr("cy", d => p[d][1]).attr("r", 0) - .transition(show).attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * .5), .8),2)); + prec + .selectAll("circle") + .data(data) + .enter() + .append("circle") + .attr("cx", d => p[d][0]) + .attr("cy", d => p[d][1]) + .attr("r", 0) + .transition(show) + .attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * 0.5), 0.8), 2)); } function togglePopulation(event) { @@ -416,7 +493,10 @@ function togglePopulation(event) { drawPopulation(); if (event && isCtrlClick(event)) editStyle("population"); } else { - if (event && isCtrlClick(event)) {editStyle("population"); return;} + if (event && isCtrlClick(event)) { + editStyle("population"); + return; + } turnButtonOff("togglePopulation"); const isD3data = population.select("line").datum(); if (!isD3data) { @@ -425,28 +505,61 @@ function togglePopulation(event) { } else { // remove with animation const hide = d3.transition().duration(1000).ease(d3.easeSinIn); - population.select("#rural").selectAll("line").transition(hide).attr("y2", d => d[1]).remove(); - population.select("#urban").selectAll("line").transition(hide).delay(1000).attr("y2", d => d[1]).remove(); + population + .select("#rural") + .selectAll("line") + .transition(hide) + .attr("y2", d => d[1]) + .remove(); + population + .select("#urban") + .selectAll("line") + .transition(hide) + .delay(1000) + .attr("y2", d => d[1]) + .remove(); } } } function drawPopulation(event) { population.selectAll("line").remove(); - const cells = pack.cells, p = cells.p, burgs = pack.burgs; + const cells = pack.cells, + p = cells.p, + burgs = pack.burgs; const show = d3.transition().duration(2000).ease(d3.easeSinIn); - const rural = Array.from(cells.i.filter(i => cells.pop[i] > 0), i => [p[i][0], p[i][1], p[i][1] - cells.pop[i] / 8]); - population.select("#rural").selectAll("line").data(rural).enter().append("line") - .attr("x1", d => d[0]).attr("y1", d => d[1]) - .attr("x2", d => d[0]).attr("y2", d => d[1]) - .transition(show).attr("y2", d => d[2]); + const rural = Array.from( + cells.i.filter(i => cells.pop[i] > 0), + i => [p[i][0], p[i][1], p[i][1] - cells.pop[i] / 8] + ); + population + .select("#rural") + .selectAll("line") + .data(rural) + .enter() + .append("line") + .attr("x1", d => d[0]) + .attr("y1", d => d[1]) + .attr("x2", d => d[0]) + .attr("y2", d => d[1]) + .transition(show) + .attr("y2", d => d[2]); - const urban = burgs.filter(b => b.i && !b.removed).map(b => [b.x, b.y, b.y - b.population / 8 * urbanization.value]); - population.select("#urban").selectAll("line").data(urban).enter().append("line") - .attr("x1", d => d[0]).attr("y1", d => d[1]) - .attr("x2", d => d[0]).attr("y2", d => d[1]) - .transition(show).delay(500).attr("y2", d => d[2]); + const urban = burgs.filter(b => b.i && !b.removed).map(b => [b.x, b.y, b.y - (b.population / 8) * urbanization]); + population + .select("#urban") + .selectAll("line") + .data(urban) + .enter() + .append("line") + .attr("x1", d => d[0]) + .attr("y1", d => d[1]) + .attr("x2", d => d[0]) + .attr("y2", d => d[1]) + .transition(show) + .delay(500) + .attr("y2", d => d[2]); } function toggleCells(event) { @@ -455,7 +568,10 @@ function toggleCells(event) { drawCells(); if (event && isCtrlClick(event)) editStyle("cells"); } else { - if (event && isCtrlClick(event)) {editStyle("cells"); return;} + if (event && isCtrlClick(event)) { + editStyle("cells"); + return; + } cells.selectAll("path").remove(); turnButtonOff("toggleCells"); } @@ -466,25 +582,32 @@ function drawCells() { const data = customization === 1 ? grid.cells.i : pack.cells.i; const polygon = customization === 1 ? getGridPolygon : getPackPolygon; let path = ""; - data.forEach(i => path += "M" + polygon(i)); + data.forEach(i => (path += "M" + polygon(i))); cells.append("path").attr("d", path); } function toggleIce(event) { if (!layerIsOn("toggleIce")) { turnButtonOn("toggleIce"); - $('#ice').fadeIn(); + $("#ice").fadeIn(); if (!ice.selectAll("*").size()) drawIce(); if (event && isCtrlClick(event)) editStyle("ice"); } else { - if (event && isCtrlClick(event)) {editStyle("ice"); return;} - $('#ice').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("ice"); + return; + } + $("#ice").fadeOut(); turnButtonOff("toggleIce"); } } function drawIce() { - const cells = grid.cells, vertices = grid.vertices, n = cells.i.length, temp = cells.temp, h = cells.h; + const cells = grid.cells, + vertices = grid.vertices, + n = cells.i.length, + temp = cells.temp, + h = cells.h; const used = new Uint8Array(cells.i.length); Math.random = aleaPRNG(seed); @@ -514,24 +637,28 @@ function drawIce() { if (grid.features[cells.f[i]].type === "lake") continue; // lake: no icebers let size = (6.5 + t) / 10; // iceberg size: 0 = full size, 1 = zero size if (cells.t[i] === -1) size *= 1.3; // coasline: smaller icebers - size = Math.min(size * (.4 + rand() * 1.2), .95); // randomize iceberg size + size = Math.min(size * (0.4 + rand() * 1.2), 0.95); // randomize iceberg size resizePolygon(i, size); } function resizePolygon(i, s) { const c = grid.points[i]; - const points = getGridPolygon(i).map(p => [(p[0] + (c[0]-p[0]) * s)|0, (p[1] + (c[1]-p[1]) * s)|0]); - ice.append("polygon").attr("points", points).attr("cell", i).attr("size", rn(1-s, 2)); + const points = getGridPolygon(i).map(p => [(p[0] + (c[0] - p[0]) * s) | 0, (p[1] + (c[1] - p[1]) * s) | 0]); + ice + .append("polygon") + .attr("points", points) + .attr("cell", i) + .attr("size", rn(1 - s, 2)); } // connect vertices to chain function connectVertices(start) { const chain = []; // vertices chain to form a path - for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { const prev = last(chain); // previous vertex in chain chain.push(current); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => temp[c] <= shieldMin).forEach(c => used[c] = 1); + c.filter(c => temp[c] <= shieldMin).forEach(c => (used[c] = 1)); const c0 = c[0] >= n || temp[c[0]] > shieldMin; const c1 = c[1] >= n || temp[c[1]] > shieldMin; const c2 = c[2] >= n || temp[c[2]] > shieldMin; @@ -539,7 +666,10 @@ function drawIce() { if (v[0] !== prev && c0 !== c1) current = v[0]; else if (v[1] !== prev && c1 !== c2) current = v[1]; else if (v[2] !== prev && c0 !== c2) current = v[2]; - if (current === chain[chain.length - 1]) {ERROR && console.error("Next vertex is not found"); break;} + if (current === chain[chain.length - 1]) { + ERROR && console.error("Next vertex is not found"); + break; + } } return chain; } @@ -553,7 +683,10 @@ function toggleCultures(event) { drawCultures(); if (event && isCtrlClick(event)) editStyle("cults"); } else { - if (event && isCtrlClick(event)) {editStyle("cults"); return;} + if (event && isCtrlClick(event)) { + editStyle("cults"); + return; + } cults.selectAll("path").remove(); turnButtonOff("toggleCultures"); } @@ -563,7 +696,10 @@ function drawCultures() { TIME && console.time("drawCultures"); cults.selectAll("path").remove(); - const cells = pack.cells, vertices = pack.vertices, cultures = pack.cultures, n = cells.i.length; + const cells = pack.cells, + vertices = pack.vertices, + cultures = pack.cultures, + n = cells.i.length; const used = new Uint8Array(cells.i.length); const paths = new Array(cultures.length).fill(""); @@ -582,16 +718,23 @@ function drawCultures() { } const data = paths.map((p, i) => [p, i]).filter(d => d[0].length > 10); - cults.selectAll("path").data(data).enter().append("path").attr("d", d => d[0]).attr("fill", d => cultures[d[1]].color).attr("id", d => "culture"+d[1]); + cults + .selectAll("path") + .data(data) + .enter() + .append("path") + .attr("d", d => d[0]) + .attr("fill", d => cultures[d[1]].color) + .attr("id", d => "culture" + d[1]); // connect vertices to chain function connectVertices(start, t) { const chain = []; // vertices chain to form a path - for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { const prev = chain[chain.length - 1]; // previous vertex in chain chain.push(current); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.culture[c] === t).forEach(c => used[c] = 1); + c.filter(c => cells.culture[c] === t).forEach(c => (used[c] = 1)); const c0 = c[0] >= n || cells.culture[c[0]] !== t; const c1 = c[1] >= n || cells.culture[c[1]] !== t; const c2 = c[2] >= n || cells.culture[c[2]] !== t; @@ -599,7 +742,10 @@ function drawCultures() { if (v[0] !== prev && c0 !== c1) current = v[0]; else if (v[1] !== prev && c1 !== c2) current = v[1]; else if (v[2] !== prev && c0 !== c2) current = v[2]; - if (current === chain[chain.length - 1]) {ERROR && console.error("Next vertex is not found"); break;} + if (current === chain[chain.length - 1]) { + ERROR && console.error("Next vertex is not found"); + break; + } } return chain; } @@ -613,7 +759,10 @@ function toggleReligions(event) { drawReligions(); if (event && isCtrlClick(event)) editStyle("relig"); } else { - if (event && isCtrlClick(event)) {editStyle("relig"); return;} + if (event && isCtrlClick(event)) { + editStyle("relig"); + return; + } relig.selectAll("path").remove(); turnButtonOff("toggleReligions"); } @@ -623,7 +772,11 @@ function drawReligions() { TIME && console.time("drawReligions"); relig.selectAll("path").remove(); - const cells = pack.cells, vertices = pack.vertices, religions = pack.religions, features = pack.features, n = cells.i.length; + const cells = pack.cells, + vertices = pack.vertices, + religions = pack.religions, + features = pack.features, + n = cells.i.length; const used = new Uint8Array(cells.i.length); const vArray = new Array(religions.length); // store vertices array const body = new Array(religions.length).fill(""); // store path around each religion @@ -644,34 +797,62 @@ function drawReligions() { if (!vArray[r]) vArray[r] = []; vArray[r].push(points); body[r] += "M" + points.join("L"); - gap[r] += "M" + vertices.p[chain[0][0]] + chain.reduce((r2,v,i,d) => !i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i+1] && !d[i+1][2] ? r2 + "M" + vertices.p[v[0]] : r2, ""); + gap[r] += "M" + vertices.p[chain[0][0]] + chain.reduce((r2, v, i, d) => (!i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r2 + "M" + vertices.p[v[0]] : r2), ""); } const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]); - relig.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("id", d => "religion"+d[1]); + relig + .selectAll("path") + .data(bodyData) + .enter() + .append("path") + .attr("d", d => d[0]) + .attr("fill", d => d[2]) + .attr("id", d => "religion" + d[1]); const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]); - relig.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "religion-gap"+d[1]).attr("stroke-width", "10px"); + relig + .selectAll(".path") + .data(gapData) + .enter() + .append("path") + .attr("d", d => d[0]) + .attr("fill", "none") + .attr("stroke", d => d[2]) + .attr("id", d => "religion-gap" + d[1]) + .attr("stroke-width", "10px"); // connect vertices to chain function connectVertices(start, t, religion) { const chain = []; // vertices chain to form a path let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.religion[c] !== t); - function check(i) {religion = cells.religion[i]; land = cells.h[i] >= 20;} + function check(i) { + religion = cells.religion[i]; + land = cells.h[i] >= 20; + } - for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain chain.push([current, religion, land]); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.religion[c] === t).forEach(c => used[c] = 1); + c.filter(c => cells.religion[c] === t).forEach(c => (used[c] = 1)); const c0 = c[0] >= n || cells.religion[c[0]] !== t; const c1 = c[1] >= n || cells.religion[c[1]] !== t; const c2 = c[2] >= n || cells.religion[c[2]] !== t; const v = vertices.v[current]; // neighboring vertices - if (v[0] !== prev && c0 !== c1) {current = v[0]; check(c0 ? c[0] : c[1]);} else - if (v[1] !== prev && c1 !== c2) {current = v[1]; check(c1 ? c[1] : c[2]);} else - if (v[2] !== prev && c0 !== c2) {current = v[2]; check(c2 ? c[2] : c[0]);} - if (current === chain[chain.length - 1][0]) {ERROR && console.error("Next vertex is not found"); break;} - + if (v[0] !== prev && c0 !== c1) { + current = v[0]; + check(c0 ? c[0] : c[1]); + } else if (v[1] !== prev && c1 !== c2) { + current = v[1]; + check(c1 ? c[1] : c[2]); + } else if (v[2] !== prev && c0 !== c2) { + current = v[2]; + check(c2 ? c[2] : c[0]); + } + if (current === chain[chain.length - 1][0]) { + ERROR && console.error("Next vertex is not found"); + break; + } } return chain; } @@ -685,7 +866,10 @@ function toggleStates(event) { drawStates(); if (event && isCtrlClick(event)) editStyle("regions"); } else { - if (event && isCtrlClick(event)) {editStyle("regions"); return;} + if (event && isCtrlClick(event)) { + editStyle("regions"); + return; + } regions.style("display", "none").selectAll("path").remove(); turnButtonOff("toggleStates"); } @@ -696,7 +880,10 @@ function drawStates() { TIME && console.time("drawStates"); regions.selectAll("path").remove(); - const cells = pack.cells, vertices = pack.vertices, states = pack.states, n = cells.i.length; + const cells = pack.cells, + vertices = pack.vertices, + states = pack.states, + n = cells.i.length; const used = new Uint8Array(cells.i.length); const vArray = new Array(states.length); // store vertices array const body = new Array(states.length).fill(""); // store path around each state @@ -716,7 +903,7 @@ function drawStates() { if (!vArray[s]) vArray[s] = []; vArray[s].push(points); body[s] += "M" + points.join("L"); - gap[s] += "M" + vertices.p[chain[0][0]] + chain.reduce((r,v,i,d) => !i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i+1] && !d[i+1][2] ? r + "M" + vertices.p[v[0]] : r, ""); + gap[s] += "M" + vertices.p[chain[0][0]] + chain.reduce((r, v, i, d) => (!i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + "M" + vertices.p[v[0]] : r), ""); } // find state visual center @@ -731,7 +918,7 @@ function drawStates() { const bodyString = bodyData.map(d => ``).join(""); const gapString = gapData.map(d => ``).join(""); const clipString = bodyData.map(d => ``).join(""); - const haloString = bodyData.map(d => ``).join(""); + const haloString = bodyData.map(d => ``).join(""); statesBody.html(bodyString + gapString); defs.select("#statePaths").html(clipString); @@ -741,21 +928,34 @@ function drawStates() { function connectVertices(start, t, state) { const chain = []; // vertices chain to form a path let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.state[c] !== t); - function check(i) {state = cells.state[i]; land = cells.h[i] >= 20;} + function check(i) { + state = cells.state[i]; + land = cells.h[i] >= 20; + } - for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain chain.push([current, state, land]); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.state[c] === t).forEach(c => used[c] = 1); + c.filter(c => cells.state[c] === t).forEach(c => (used[c] = 1)); const c0 = c[0] >= n || cells.state[c[0]] !== t; const c1 = c[1] >= n || cells.state[c[1]] !== t; const c2 = c[2] >= n || cells.state[c[2]] !== t; const v = vertices.v[current]; // neighboring vertices - if (v[0] !== prev && c0 !== c1) {current = v[0]; check(c0 ? c[0] : c[1]);} else - if (v[1] !== prev && c1 !== c2) {current = v[1]; check(c1 ? c[1] : c[2]);} else - if (v[2] !== prev && c0 !== c2) {current = v[2]; check(c2 ? c[2] : c[0]);} - if (current === chain[chain.length - 1][0]) {ERROR && console.error("Next vertex is not found"); break;} + if (v[0] !== prev && c0 !== c1) { + current = v[0]; + check(c0 ? c[0] : c[1]); + } else if (v[1] !== prev && c1 !== c2) { + current = v[1]; + check(c1 ? c[1] : c[2]); + } else if (v[2] !== prev && c0 !== c2) { + current = v[2]; + check(c2 ? c[2] : c[0]); + } + if (current === chain[chain.length - 1][0]) { + ERROR && console.error("Next vertex is not found"); + break; + } } chain.push([start, state, land]); // add starting vertex to sequence to close the path return chain; @@ -769,12 +969,15 @@ function drawBorders() { TIME && console.time("drawBorders"); borders.selectAll("path").remove(); - const cells = pack.cells, vertices = pack.vertices, n = cells.i.length; - const sPath = [], pPath = []; + const cells = pack.cells, + vertices = pack.vertices, + n = cells.i.length; + const sPath = [], + pPath = []; const sUsed = new Array(pack.states.length).fill("").map(a => []); const pUsed = new Array(pack.provinces.length).fill("").map(a => []); - for (let i=0; i < cells.i.length; i++) { + for (let i = 0; i < cells.i.length; i++) { if (!cells.state[i]) continue; const p = cells.province[i]; const s = cells.state[i]; @@ -820,10 +1023,11 @@ function drawBorders() { const checkVertex = v => vertices.c[v].some(c => array[c] === f) && vertices.c[v].some(c => array[c] === t && cells.h[c] >= 20); // find starting vertex - for (let i=0; i < 1000; i++) { + for (let i = 0; i < 1000; i++) { if (i === 999) ERROR && console.error("Find starting vertex: limit is reached", current, f, t); - const p = chain[chain.length-2] || -1; // previous vertex - const v = vertices.v[current], c = vertices.c[current]; + const p = chain[chain.length - 2] || -1; // previous vertex + const v = vertices.v[current], + c = vertices.c[current]; const v0 = checkCell(c[0]) !== checkCell(c[1]) && checkVertex(v[0]); const v1 = checkCell(c[1]) !== checkCell(c[2]) && checkVertex(v[1]); @@ -838,11 +1042,12 @@ function drawBorders() { chain = [current]; // vertices chain to form a path // find path - for (let i=0; i < 1000; i++) { + for (let i = 0; i < 1000; i++) { if (i === 999) ERROR && console.error("Find path: limit is reached", current, f, t); - const p = chain[chain.length-2] || -1; // previous vertex - const v = vertices.v[current], c = vertices.c[current]; - c.filter(c => array[c] === t).forEach(c => used[f][c] = t); + const p = chain[chain.length - 2] || -1; // previous vertex + const v = vertices.v[current], + c = vertices.c[current]; + c.filter(c => array[c] === t).forEach(c => (used[f][c] = t)); const v0 = checkCell(c[0]) !== checkCell(c[1]) && checkVertex(v[0]); const v1 = checkCell(c[1]) !== checkCell(c[2]) && checkVertex(v[1]); @@ -850,7 +1055,7 @@ function drawBorders() { current = v0 && p !== v[0] ? v[0] : v1 && p !== v[1] ? v[1] : v[2]; if (current === p) break; - if (current === chain[chain.length-1]) break; + if (current === chain[chain.length - 1]) break; if (chain.length > 1 && v0 + v1 + v2 < 2) break; chain.push(current); if (current === chain[0]) break; @@ -865,12 +1070,15 @@ function drawBorders() { function toggleBorders(event) { if (!layerIsOn("toggleBorders")) { turnButtonOn("toggleBorders"); - $('#borders').fadeIn(); + $("#borders").fadeIn(); if (event && isCtrlClick(event)) editStyle("borders"); } else { - if (event && isCtrlClick(event)) {editStyle("borders"); return;} + if (event && isCtrlClick(event)) { + editStyle("borders"); + return; + } turnButtonOff("toggleBorders"); - $('#borders').fadeOut(); + $("#borders").fadeOut(); } } @@ -880,7 +1088,10 @@ function toggleProvinces(event) { drawProvinces(); if (event && isCtrlClick(event)) editStyle("provs"); } else { - if (event && isCtrlClick(event)) {editStyle("provs"); return;} + if (event && isCtrlClick(event)) { + editStyle("provs"); + return; + } provs.selectAll("*").remove(); turnButtonOff("toggleProvinces"); } @@ -896,22 +1107,45 @@ function drawProvinces() { const g = provs.append("g").attr("id", "provincesBody"); const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]); - g.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("stroke", "none").attr("id", d => "province"+d[1]); + g.selectAll("path") + .data(bodyData) + .enter() + .append("path") + .attr("d", d => d[0]) + .attr("fill", d => d[2]) + .attr("stroke", "none") + .attr("id", d => "province" + d[1]); const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]); - g.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "province-gap"+d[1]); + g.selectAll(".path") + .data(gapData) + .enter() + .append("path") + .attr("d", d => d[0]) + .attr("fill", "none") + .attr("stroke", d => d[2]) + .attr("id", d => "province-gap" + d[1]); const labels = provs.append("g").attr("id", "provinceLabels"); labels.style("display", `${labelsOn ? "block" : "none"}`); const labelData = provinces.filter(p => p.i && !p.removed && p.pole); - labels.selectAll(".path").data(labelData).enter().append("text") - .attr("x", d => d.pole[0]).attr("y", d => d.pole[1]) - .attr("id", d => "provinceLabel"+d.i).text(d => d.name); + labels + .selectAll(".path") + .data(labelData) + .enter() + .append("text") + .attr("x", d => d.pole[0]) + .attr("y", d => d.pole[1]) + .attr("id", d => "provinceLabel" + d.i) + .text(d => d.name); TIME && console.timeEnd("drawProvinces"); } function getProvincesVertices() { - const cells = pack.cells, vertices = pack.vertices, provinces = pack.provinces, n = cells.i.length; + const cells = pack.cells, + vertices = pack.vertices, + provinces = pack.provinces, + n = cells.i.length; const used = new Uint8Array(cells.i.length); const vArray = new Array(provinces.length); // store vertices array const body = new Array(provinces.length).fill(""); // store path around each province @@ -931,7 +1165,7 @@ function getProvincesVertices() { if (!vArray[p]) vArray[p] = []; vArray[p].push(points); body[p] += "M" + points.join("L"); - gap[p] += "M" + vertices.p[chain[0][0]] + chain.reduce((r,v,i,d) => !i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i+1] && !d[i+1][2] ? r + "M" + vertices.p[v[0]] : r, ""); + gap[p] += "M" + vertices.p[chain[0][0]] + chain.reduce((r, v, i, d) => (!i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + "M" + vertices.p[v[0]] : r), ""); } // find province visual center @@ -946,21 +1180,34 @@ function getProvincesVertices() { function connectVertices(start, t, province) { const chain = []; // vertices chain to form a path let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.province[c] !== t); - function check(i) {province = cells.province[i]; land = cells.h[i] >= 20;} + function check(i) { + province = cells.province[i]; + land = cells.h[i] >= 20; + } - for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain chain.push([current, province, land]); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex - c.filter(c => cells.province[c] === t).forEach(c => used[c] = 1); + c.filter(c => cells.province[c] === t).forEach(c => (used[c] = 1)); const c0 = c[0] >= n || cells.province[c[0]] !== t; const c1 = c[1] >= n || cells.province[c[1]] !== t; const c2 = c[2] >= n || cells.province[c[2]] !== t; const v = vertices.v[current]; // neighboring vertices - if (v[0] !== prev && c0 !== c1) {current = v[0]; check(c0 ? c[0] : c[1]);} else - if (v[1] !== prev && c1 !== c2) {current = v[1]; check(c1 ? c[1] : c[2]);} else - if (v[2] !== prev && c0 !== c2) {current = v[2]; check(c2 ? c[2] : c[0]);} - if (current === chain[chain.length-1][0]) {ERROR && console.error("Next vertex is not found"); break;} + if (v[0] !== prev && c0 !== c1) { + current = v[0]; + check(c0 ? c[0] : c[1]); + } else if (v[1] !== prev && c1 !== c2) { + current = v[1]; + check(c1 ? c[1] : c[2]); + } else if (v[2] !== prev && c0 !== c2) { + current = v[2]; + check(c2 ? c[2] : c[0]); + } + if (current === chain[chain.length - 1][0]) { + ERROR && console.error("Next vertex is not found"); + break; + } } chain.push([start, province, land]); // add starting vertex to sequence to close the path return chain; @@ -973,10 +1220,12 @@ function toggleGrid(event) { drawGrid(); calculateFriendlyGridSize(); - if (event && isCtrlClick(event)) editStyle("gridOverlay"); } else { - if (event && isCtrlClick(event)) {editStyle("gridOverlay"); return;} + if (event && isCtrlClick(event)) { + editStyle("gridOverlay"); + return; + } turnButtonOff("toggleGrid"); gridOverlay.selectAll("*").remove(); } @@ -986,7 +1235,7 @@ function drawGrid() { gridOverlay.selectAll("*").remove(); const pattern = "#pattern_" + (gridOverlay.attr("type") || "pointyHex"); const stroke = gridOverlay.attr("stroke") || "#808080"; - const width = gridOverlay.attr("stroke-width") || .5; + const width = gridOverlay.attr("stroke-width") || 0.5; const dasharray = gridOverlay.attr("stroke-dasharray") || null; const linecap = gridOverlay.attr("stroke-linecap") || null; const scale = gridOverlay.attr("scale") || 1; @@ -998,7 +1247,12 @@ function drawGrid() { const maxHeight = Math.max(+mapHeightInput.value, graphHeight); d3.select(pattern).attr("stroke", stroke).attr("stroke-width", width).attr("stroke-dasharray", dasharray).attr("stroke-linecap", linecap).attr("patternTransform", tr); - gridOverlay.append("rect").attr("width", maxWidth).attr("height", maxHeight).attr("fill", "url(" + pattern + ")").attr("stroke", "none"); + gridOverlay + .append("rect") + .attr("width", maxWidth) + .attr("height", maxHeight) + .attr("fill", "url(" + pattern + ")") + .attr("stroke", "none"); } function toggleCoordinates(event) { @@ -1007,7 +1261,10 @@ function toggleCoordinates(event) { drawCoordinates(); if (event && isCtrlClick(event)) editStyle("coordinates"); } else { - if (event && isCtrlClick(event)) {editStyle("coordinates"); return;} + if (event && isCtrlClick(event)) { + editStyle("coordinates"); + return; + } turnButtonOff("toggleCoordinates"); coordinates.selectAll("*").remove(); } @@ -1016,14 +1273,20 @@ function toggleCoordinates(event) { function drawCoordinates() { if (!layerIsOn("toggleCoordinates")) return; coordinates.selectAll("*").remove(); // remove every time - const steps = [.5, 1, 2, 5, 10, 15, 30]; // possible steps + const steps = [0.5, 1, 2, 5, 10, 15, 30]; // possible steps const goal = mapCoordinates.lonT / scale / 10; - const step = steps.reduce((p, c) => Math.abs(c - goal) < Math.abs(p - goal) ? c : p); + const step = steps.reduce((p, c) => (Math.abs(c - goal) < Math.abs(p - goal) ? c : p)); const desired = +coordinates.attr("data-size"); // desired label size - coordinates.attr("font-size", Math.max(rn(desired / scale ** .8, 2), .1)); // actual label size - const graticule = d3.geoGraticule().extent([[mapCoordinates.lonW, mapCoordinates.latN], [mapCoordinates.lonE+.1, mapCoordinates.latS+.1]]) - .stepMajor([400, 400]).stepMinor([step, step]); + coordinates.attr("font-size", Math.max(rn(desired / scale ** 0.8, 2), 0.1)); // actual label size + const graticule = d3 + .geoGraticule() + .extent([ + [mapCoordinates.lonW, mapCoordinates.latN], + [mapCoordinates.lonE + 0.1, mapCoordinates.latS + 0.1] + ]) + .stepMajor([400, 400]) + .stepMinor([step, step]); const projection = d3.geoEquirectangular().fitSize([graphWidth, graphHeight], graticule()); const grid = coordinates.append("g").attr("id", "coordinateGrid"); @@ -1032,39 +1295,50 @@ function drawCoordinates() { const p = getViewPoint(scale + desired + 2, scale + desired / 2); // on border point on viexBox const data = graticule.lines().map(d => { const lat = d.coordinates[0][1] === d.coordinates[1][1]; // check if line is latitude or longitude - const c = d.coordinates[0], pos = projection(c); // map coordinates + const c = d.coordinates[0], + pos = projection(c); // map coordinates const [x, y] = lat ? [rn(p.x, 2), rn(pos[1], 2)] : [rn(pos[0], 2), rn(p.y, 2)]; // labels position const v = lat ? c[1] : c[0]; // label - const text = !v ? v : Number.isInteger(v) ? lat ? c[1] < 0 ? -c[1] + "°S" : c[1] + "°N" : c[0] < 0 ? -c[0] + "°W" : c[0] + "°E" : ""; + const text = !v ? v : Number.isInteger(v) ? (lat ? (c[1] < 0 ? -c[1] + "°S" : c[1] + "°N") : c[0] < 0 ? -c[0] + "°W" : c[0] + "°E") : ""; return {lat, x, y, text}; }); const d = round(d3.geoPath(projection)(graticule())); grid.append("path").attr("d", d).attr("vector-effect", "non-scaling-stroke"); - labels.selectAll('text').data(data).enter().append("text").attr("x", d => d.x).attr("y", d => d.y).text(d => d.text); + labels + .selectAll("text") + .data(data) + .enter() + .append("text") + .attr("x", d => d.x) + .attr("y", d => d.y) + .text(d => d.text); } // conver svg point into viewBox point function getViewPoint(x, y) { - const view = document.getElementById('viewbox'); - const svg = document.getElementById('map'); + const view = document.getElementById("viewbox"); + const svg = document.getElementById("map"); const pt = svg.createSVGPoint(); - pt.x = x, pt.y = y; + (pt.x = x), (pt.y = y); return pt.matrixTransform(view.getScreenCTM().inverse()); } function toggleCompass(event) { if (!layerIsOn("toggleCompass")) { turnButtonOn("toggleCompass"); - $('#compass').fadeIn(); + $("#compass").fadeIn(); if (!compass.selectAll("*").size()) { - compass.append("use").attr("xlink:href","#rose"); + compass.append("use").attr("xlink:href", "#rose"); shiftCompass(); } if (event && isCtrlClick(event)) editStyle("compass"); } else { - if (event && isCtrlClick(event)) {editStyle("compass"); return;} - $('#compass').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("compass"); + return; + } + $("#compass").fadeOut(); turnButtonOff("toggleCompass"); } } @@ -1073,11 +1347,14 @@ function toggleRelief(event) { if (!layerIsOn("toggleRelief")) { turnButtonOn("toggleRelief"); if (!terrain.selectAll("*").size()) ReliefIcons(); - $('#terrain').fadeIn(); + $("#terrain").fadeIn(); if (event && isCtrlClick(event)) editStyle("terrain"); } else { - if (event && isCtrlClick(event)) {editStyle("terrain"); return;} - $('#terrain').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("terrain"); + return; + } + $("#terrain").fadeOut(); turnButtonOff("toggleRelief"); } } @@ -1087,18 +1364,28 @@ function toggleTexture(event) { turnButtonOn("toggleTexture"); // append default texture image selected by default. Don't append on load to not harm performance if (!texture.selectAll("*").size()) { - const x = +styleTextureShiftX.value, y = +styleTextureShiftY.value; - const image = texture.append("image").attr("id", "textureImage") - .attr("x", x).attr("y", y).attr("width", graphWidth - x).attr("height", graphHeight - y) - .attr("xlink:href", getDefaultTexture()).attr("preserveAspectRatio", "xMidYMid slice"); + const x = +styleTextureShiftX.value, + y = +styleTextureShiftY.value; + const image = texture + .append("image") + .attr("id", "textureImage") + .attr("x", x) + .attr("y", y) + .attr("width", graphWidth - x) + .attr("height", graphHeight - y) + .attr("xlink:href", getDefaultTexture()) + .attr("preserveAspectRatio", "xMidYMid slice"); if (styleTextureInput.value !== "default") getBase64(styleTextureInput.value, base64 => image.attr("xlink:href", base64)); } - $('#texture').fadeIn(); + $("#texture").fadeIn(); zoom.scaleBy(svg, 1.00001); // enforce browser re-draw if (event && isCtrlClick(event)) editStyle("texture"); } else { - if (event && isCtrlClick(event)) {editStyle("texture"); return;} - $('#texture').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("texture"); + return; + } + $("#texture").fadeOut(); turnButtonOff("toggleTexture"); } } @@ -1106,11 +1393,14 @@ function toggleTexture(event) { function toggleRivers(event) { if (!layerIsOn("toggleRivers")) { turnButtonOn("toggleRivers"); - $('#rivers').fadeIn(); + $("#rivers").fadeIn(); if (event && isCtrlClick(event)) editStyle("rivers"); } else { - if (event && isCtrlClick(event)) {editStyle("rivers"); return;} - $('#rivers').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("rivers"); + return; + } + $("#rivers").fadeOut(); turnButtonOff("toggleRivers"); } } @@ -1118,11 +1408,14 @@ function toggleRivers(event) { function toggleRoutes(event) { if (!layerIsOn("toggleRoutes")) { turnButtonOn("toggleRoutes"); - $('#routes').fadeIn(); + $("#routes").fadeIn(); if (event && isCtrlClick(event)) editStyle("routes"); } else { - if (event && isCtrlClick(event)) {editStyle("routes"); return;} - $('#routes').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("routes"); + return; + } + $("#routes").fadeOut(); turnButtonOff("toggleRoutes"); } } @@ -1130,11 +1423,14 @@ function toggleRoutes(event) { function toggleMilitary() { if (!layerIsOn("toggleMilitary")) { turnButtonOn("toggleMilitary"); - $('#armies').fadeIn(); + $("#armies").fadeIn(); if (event && isCtrlClick(event)) editStyle("armies"); } else { - if (event && isCtrlClick(event)) {editStyle("armies"); return;} - $('#armies').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("armies"); + return; + } + $("#armies").fadeOut(); turnButtonOff("toggleMilitary"); } } @@ -1142,11 +1438,14 @@ function toggleMilitary() { function toggleMarkers(event) { if (!layerIsOn("toggleMarkers")) { turnButtonOn("toggleMarkers"); - $('#markers').fadeIn(); + $("#markers").fadeIn(); if (event && isCtrlClick(event)) editStyle("markers"); } else { - if (event && isCtrlClick(event)) {editStyle("markers"); return;} - $('#markers').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("markers"); + return; + } + $("#markers").fadeOut(); turnButtonOff("toggleMarkers"); } } @@ -1154,11 +1453,14 @@ function toggleMarkers(event) { function toggleLabels(event) { if (!layerIsOn("toggleLabels")) { turnButtonOn("toggleLabels"); - labels.style("display", null) + labels.style("display", null); invokeActiveZooming(); if (event && isCtrlClick(event)) editStyle("labels"); } else { - if (event && isCtrlClick(event)) {editStyle("labels"); return;} + if (event && isCtrlClick(event)) { + editStyle("labels"); + return; + } turnButtonOff("toggleLabels"); labels.style("display", "none"); } @@ -1167,12 +1469,15 @@ function toggleLabels(event) { function toggleIcons(event) { if (!layerIsOn("toggleIcons")) { turnButtonOn("toggleIcons"); - $('#icons').fadeIn(); + $("#icons").fadeIn(); if (event && isCtrlClick(event)) editStyle("burgIcons"); } else { - if (event && isCtrlClick(event)) {editStyle("burgIcons"); return;} + if (event && isCtrlClick(event)) { + editStyle("burgIcons"); + return; + } turnButtonOff("toggleIcons"); - $('#icons').fadeOut(); + $("#icons").fadeOut(); } } @@ -1183,7 +1488,10 @@ function toggleRulers(event) { rulers.draw(); ruler.style("display", null); } else { - if (event && isCtrlClick(event)) {editStyle("ruler"); return;} + if (event && isCtrlClick(event)) { + editStyle("ruler"); + return; + } turnButtonOff("toggleRulers"); ruler.selectAll("*").remove(); ruler.style("display", "none"); @@ -1193,11 +1501,14 @@ function toggleRulers(event) { function toggleScaleBar(event) { if (!layerIsOn("toggleScaleBar")) { turnButtonOn("toggleScaleBar"); - $('#scaleBar').fadeIn(); + $("#scaleBar").fadeIn(); if (event && isCtrlClick(event)) editUnits(); } else { - if (event && isCtrlClick(event)) {editUnits(); return;} - $('#scaleBar').fadeOut(); + if (event && isCtrlClick(event)) { + editUnits(); + return; + } + $("#scaleBar").fadeOut(); turnButtonOff("toggleScaleBar"); } } @@ -1205,12 +1516,15 @@ function toggleScaleBar(event) { function toggleZones(event) { if (!layerIsOn("toggleZones")) { turnButtonOn("toggleZones"); - $('#zones').fadeIn(); + $("#zones").fadeIn(); if (event && isCtrlClick(event)) editStyle("zones"); } else { - if (event && isCtrlClick(event)) {editStyle("zones"); return;} + if (event && isCtrlClick(event)) { + editStyle("zones"); + return; + } turnButtonOff("toggleZones"); - $('#zones').fadeOut(); + $("#zones").fadeOut(); } } @@ -1218,11 +1532,14 @@ function toggleEmblems(event) { if (!layerIsOn("toggleEmblems")) { turnButtonOn("toggleEmblems"); if (!emblems.selectAll("use").size()) drawEmblems(); - $('#emblems').fadeIn(); + $("#emblems").fadeIn(); if (event && isCtrlClick(event)) editStyle("emblems"); } else { - if (event && isCtrlClick(event)) {editStyle("emblems"); return;} - $('#emblems').fadeOut(); + if (event && isCtrlClick(event)) { + editStyle("emblems"); + return; + } + $("#emblems").fadeOut(); turnButtonOff("toggleEmblems"); } } @@ -1237,30 +1554,30 @@ function drawEmblems() { const getStateEmblemsSize = () => { const startSize = Math.min(Math.max((graphHeight + graphWidth) / 40, 10), 100); - const statesMod = (1 + validStates.length / 100) - (15 - validStates.length) / 200; // states number modifier + const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier const sizeMod = +document.getElementById("styleEmblemsStateSizeInput").value || 1; - return rn(startSize / statesMod * sizeMod); // target size ~50px on 1536x754 map with 15 states + return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states }; const getProvinceEmblemsSize = () => { const startSize = Math.min(Math.max((graphHeight + graphWidth) / 100, 5), 70); - const provincesMod = (1 + validProvinces.length / 1000) - (115 - validProvinces.length) / 1000; // states number modifier + const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier const sizeMod = +document.getElementById("styleEmblemsProvinceSizeInput").value || 1; - return rn(startSize / provincesMod * sizeMod); // target size ~20px on 1536x754 map with 115 provinces - } + return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces + }; const getBurgEmblemSize = () => { const startSize = Math.min(Math.max((graphHeight + graphWidth) / 185, 2), 50); - const burgsMod = (1 + validBurgs.length / 1000) - (450 - validBurgs.length) / 1000; // states number modifier + const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier const sizeMod = +document.getElementById("styleEmblemsBurgSizeInput").value || 1; - return rn(startSize / burgsMod * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs - } + return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs + }; const sizeBurgs = getBurgEmblemSize(); const burgCOAs = validBurgs.map(burg => { const {x, y} = burg; const size = burg.coaSize || 1; - const shift = sizeBurgs * size / 2; + const shift = (sizeBurgs * size) / 2; return {type: "burg", i: burg.i, x, y, size, shift}; }); @@ -1269,7 +1586,7 @@ function drawEmblems() { if (!province.pole) getProvincesVertices(); const [x, y] = province.pole || pack.cells.p[province.center]; const size = province.coaSize || 1; - const shift = sizeProvinces * size / 2; + const shift = (sizeProvinces * size) / 2; return {type: "province", i: province.i, x, y, size, shift}; }); @@ -1277,17 +1594,23 @@ function drawEmblems() { const stateCOAs = validStates.map(state => { const [x, y] = state.pole || pack.cells.p[state.center]; const size = state.coaSize || 1; - const shift = sizeStates * size / 2; + const shift = (sizeStates * size) / 2; return {type: "state", i: state.i, x, y, size, shift}; }); const nodes = burgCOAs.concat(provinceCOAs).concat(stateCOAs); - const simulation = d3.forceSimulation(nodes) - .alphaMin(.6).alphaDecay(.2).velocityDecay(.6) - .force('collision', d3.forceCollide().radius(d => d.shift)) + const simulation = d3 + .forceSimulation(nodes) + .alphaMin(0.6) + .alphaDecay(0.2) + .velocityDecay(0.6) + .force( + "collision", + d3.forceCollide().radius(d => d.shift) + ) .stop(); - d3.timeout(function() { + d3.timeout(function () { const n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); for (let i = 0; i < n; ++i) { simulation.tick(); @@ -1333,7 +1656,8 @@ function moveLayer(event, ui) { if (!el) return; const prev = getLayer(ui.item.prev().attr("id")); const next = getLayer(ui.item.next().attr("id")); - if (prev) el.insertAfter(prev); else if (next) el.insertBefore(next); + if (prev) el.insertAfter(prev); + else if (next) el.insertBefore(next); } // define connection between option layer buttons and actual svg groups to move the element diff --git a/modules/ui/military-overview.js b/modules/ui/military-overview.js index cc1ff082..95f897cb 100644 --- a/modules/ui/military-overview.js +++ b/modules/ui/military-overview.js @@ -15,7 +15,9 @@ function overviewMilitary() { updateHeaders(); $("#militaryOverview").dialog({ - title: "Military Overview", resizable: false, width: fitContent(), + title: "Military Overview", + resizable: false, + width: fitContent(), position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -28,13 +30,17 @@ function overviewMilitary() { document.getElementById("militaryExport").addEventListener("click", downloadMilitaryData); document.getElementById("militaryWiki").addEventListener("click", () => wiki("Military-Forces")); - body.addEventListener("change", function(ev) { - const el = ev.target, line = el.parentNode, state = +line.dataset.id; + body.addEventListener("change", function (ev) { + const el = ev.target, + line = el.parentNode, + state = +line.dataset.id; changeAlert(state, line, +el.value); }); - body.addEventListener("click", function(ev) { - const el = ev.target, line = el.parentNode, state = +line.dataset.id; + body.addEventListener("click", function (ev) { + const el = ev.target, + line = el.parentNode, + state = +line.dataset.id; if (el.tagName === "SPAN") overviewRegiments(state); }); @@ -44,11 +50,13 @@ function overviewMilitary() { header.querySelectorAll(".removable").forEach(el => el.remove()); const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html); for (const u of options.military) { - const label = capitalize(u.name.replace(/_/g, ' ')); + const label = capitalize(u.name.replace(/_/g, " ")); insert(`
${label} 
`); } - header.querySelectorAll(".removable").forEach(function(e) { - e.addEventListener("click", function() {sortLines(this);}); + header.querySelectorAll(".removable").forEach(function (e) { + e.addEventListener("click", function () { + sortLines(this); + }); }); } @@ -59,10 +67,10 @@ function overviewMilitary() { const states = pack.states.filter(s => s.i && !s.removed); for (const s of states) { - const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value); - const getForces = u => s.military.reduce((s, r) => s+(r.u[u.name]||0), 0); + const population = rn((s.rural + s.urban * urbanization) * populationRate); + const getForces = u => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0); const total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0); - const rate = total / population * 100; + const rate = (total / population) * 100; const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); const lineData = options.military.map(u => `
${getForces(u)}
`).join(" "); @@ -85,7 +93,10 @@ function overviewMilitary() { body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev))); - if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} + if (body.dataset.type === "percentage") { + body.dataset.type = "absolute"; + togglePercentageMode(); + } applySorting(militaryHeader); } @@ -95,17 +106,17 @@ function overviewMilitary() { s.alert = line.dataset.alert = alert; s.military.forEach(r => { - Object.keys(r.u).forEach(u => r.u[u] = rn(r.u[u] * dif)); // change units value + Object.keys(r.u).forEach(u => (r.u[u] = rn(r.u[u] * dif))); // change units value r.a = d3.sum(Object.values(r.u)); // change total armies.select(`g>g#regiment${s.i}-${r.i}>text`).text(Military.getTotal(r)); // change icon text }); - const getForces = u => s.military.reduce((s, r) => s+(r.u[u.name]||0), 0); - options.military.forEach(u => line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u)); + const getForces = u => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0); + options.military.forEach(u => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u))); - const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value); - const total = line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0); - const rate = line.dataset.rate = total / population * 100; + const population = rn((s.rural + s.urban * urbanization) * populationRate); + const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0)); + const rate = (line.dataset.rate = (total / population) * 100); line.querySelector("div[data-type='total']").innerHTML = si(total); line.querySelector("div[data-type='rate']").innerHTML = rn(rate, 2) + "%"; @@ -114,7 +125,7 @@ function overviewMilitary() { function updateFooter() { const lines = Array.from(body.querySelectorAll(":scope > div")); - const statesNumber = militaryFooterStates.innerHTML = pack.states.filter(s => s.i && !s.removed).length; + const statesNumber = (militaryFooterStates.innerHTML = pack.states.filter(s => s.i && !s.removed).length); const total = d3.sum(lines.map(el => el.dataset.total)); militaryFooterForcesTotal.innerHTML = si(total); militaryFooterForces.innerHTML = si(total / statesNumber); @@ -125,46 +136,59 @@ function overviewMilitary() { function stateHighlightOn(event) { const state = +event.target.dataset.id; if (customization || !state) return; - armies.select("#army"+state).transition().duration(2000).style("fill", "#ff0000"); + armies + .select("#army" + state) + .transition() + .duration(2000) + .style("fill", "#ff0000"); if (!layerIsOn("toggleStates")) return; - const d = regions.select("#state"+state).attr("d"); + const d = regions.select("#state" + state).attr("d"); - const path = debug.append("path").attr("class", "highlight").attr("d", d) - .attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1) - .attr("filter", "url(#blur1)"); + const path = debug.append("path").attr("class", "highlight").attr("d", d).attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1).attr("filter", "url(#blur1)"); - const l = path.node().getTotalLength(), dur = (l + 5000) / 2; + const l = path.node().getTotalLength(), + dur = (l + 5000) / 2; const i = d3.interpolateString("0," + l, l + "," + l); - path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)}); + path + .transition() + .duration(dur) + .attrTween("stroke-dasharray", function () { + return t => i(t); + }); } function stateHighlightOff(event) { - debug.selectAll(".highlight").each(function() { + debug.selectAll(".highlight").each(function () { d3.select(this).transition().duration(1000).attr("opacity", 0).remove(); }); const state = +event.target.dataset.id; - armies.select("#army"+state).transition().duration(1000).style("fill", null); + armies + .select("#army" + state) + .transition() + .duration(1000) + .style("fill", null); } function togglePercentageMode() { if (body.dataset.type === "absolute") { body.dataset.type = "percentage"; const lines = body.querySelectorAll(":scope > div"); - const array = Array.from(lines), cache = []; + const array = Array.from(lines), + cache = []; - const total = function(type) { + const total = function (type) { if (cache[type]) cache[type]; cache[type] = d3.sum(array.map(el => +el.dataset[type])); return cache[type]; - } + }; - lines.forEach(function(el) { - el.querySelectorAll("div").forEach(function(div) { + lines.forEach(function (el) { + el.querySelectorAll("div").forEach(function (div) { const type = div.dataset.type; if (type === "rate") return; - div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%"; + div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + "%" : "0%"; }); }); } else { @@ -180,14 +204,19 @@ function overviewMilitary() { options.military.map(u => addUnitLine(u)); $("#militaryOptions").dialog({ - title: "Edit Military Units", resizable: false, width: fitContent(), + title: "Edit Military Units", + resizable: false, + width: fitContent(), position: {my: "center", at: "center", of: "svg"}, buttons: { Apply: applyMilitaryOptions, - Add: () => addUnitLine({icon: "🛡️", name: "custom"+militaryOptionsTable.rows.length, rural: .2, urban: .5, crew: 1, power: 1, type: "melee"}), + Add: () => addUnitLine({icon: "🛡️", name: "custom" + militaryOptionsTable.rows.length, rural: 0.2, urban: 0.5, crew: 1, power: 1, type: "melee"}), Restore: restoreDefaultUnits, - Cancel: function() {$(this).dialog("close");} - }, open: function() { + Cancel: function () { + $(this).dialog("close"); + } + }, + open: function () { const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button"); buttons[0].addEventListener("mousemove", () => tip("Apply military units settings. All forces will be recalculated!")); buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table")); @@ -202,7 +231,7 @@ function overviewMilitary() { function addUnitLine(u) { const row = document.createElement("tr"); - row.innerHTML = ` + row.innerHTML = ` @@ -213,7 +242,9 @@ function overviewMilitary() { `; - row.querySelector("button").addEventListener("click", function(e) {selectIcon(this.innerHTML, v => this.innerHTML = v)}); + row.querySelector("button").addEventListener("click", function (e) { + selectIcon(this.innerHTML, v => (this.innerHTML = v)); + }); table.appendChild(row); } @@ -224,7 +255,7 @@ function overviewMilitary() { function applyMilitaryOptions() { const unitLines = Array.from(table.querySelectorAll("tr")); - const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_')); + const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_")); if (new Set(names).size !== names.length) { tip("All units should have unique names", false, "error"); return; @@ -239,46 +270,48 @@ function overviewMilitary() { if (d.type === "button") value = d.innerHTML || "⠀"; return value; }); - return {icon, name:names[i], rural, urban, crew, power, type, separate}; + return {icon, name: names[i], rural, urban, crew, power, type, separate}; }); localStorage.setItem("military", JSON.stringify(options.military)); Military.generate(); updateHeaders(); addLines(); } - } function militaryRecalculate() { alertMessage.innerHTML = "Are you sure you want to recalculate military forces for all states?
Regiments for all states will be regenerated"; - $("#alert").dialog({resizable: false, title: "Remove regiment", + $("#alert").dialog({ + resizable: false, + title: "Remove regiment", buttons: { - Recalculate: function() { + Recalculate: function () { $(this).dialog("close"); Military.generate(); addLines(); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } function downloadMilitaryData() { const units = options.military.map(u => u.name); - let data = "Id,State,"+units.map(u => capitalize(u)).join(",")+",Total,Population,Rate,War Alert\n"; // headers + let data = "Id,State," + units.map(u => capitalize(u)).join(",") + ",Total,Population,Rate,War Alert\n"; // headers - body.querySelectorAll(":scope > div").forEach(function(el) { + body.querySelectorAll(":scope > div").forEach(function (el) { data += el.dataset.id + ","; data += el.dataset.state + ","; data += units.map(u => el.dataset[u]).join(",") + ","; data += el.dataset.total + ","; data += el.dataset.population + ","; - data += rn(el.dataset.rate,2) + "%,"; + data += rn(el.dataset.rate, 2) + "%,"; data += el.dataset.alert + "\n"; }); const name = getFileName("Military") + ".csv"; downloadFile(data, name); } - -} \ No newline at end of file +} diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js index 801ee003..b5790ec0 100644 --- a/modules/ui/provinces-editor.js +++ b/modules/ui/provinces-editor.js @@ -15,7 +15,10 @@ function editProvinces() { modules.editProvinces = true; $("#provincesEditor").dialog({ - title: "Provinces Editor", resizable: false, width: fitContent(), close: closeProvincesEditor, + title: "Provinces Editor", + resizable: false, + width: fitContent(), + close: closeProvincesEditor, position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -34,21 +37,27 @@ function editProvinces() { document.getElementById("provincesAdd").addEventListener("click", enterAddProvinceMode); document.getElementById("provincesRecolor").addEventListener("click", recolorProvinces); - body.addEventListener("click", function(ev) { + body.addEventListener("click", function (ev) { if (customization) return; - const el = ev.target, cl = el.classList, line = el.parentNode, p = +line.dataset.id; - if (cl.contains("fillRect")) changeFill(el); else - if (cl.contains("name")) editProvinceName(p); else - if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA"+p, pack.provinces[p]); else - if (cl.contains("icon-star-empty")) capitalZoomIn(p); else - if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p); else - if (cl.contains("culturePopulation")) changePopulation(p); else - if (cl.contains("icon-pin")) toggleFog(p, cl); else - if (cl.contains("icon-trash-empty")) removeProvince(p); + const el = ev.target, + cl = el.classList, + line = el.parentNode, + p = +line.dataset.id; + if (cl.contains("fillRect")) changeFill(el); + else if (cl.contains("name")) editProvinceName(p); + else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]); + else if (cl.contains("icon-star-empty")) capitalZoomIn(p); + else if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p); + else if (cl.contains("culturePopulation")) changePopulation(p); + else if (cl.contains("icon-pin")) toggleFog(p, cl); + else if (cl.contains("icon-trash-empty")) removeProvince(p); }); - body.addEventListener("change", function(ev) { - const el = ev.target, cl = el.classList, line = el.parentNode, p = +line.dataset.id; + body.addEventListener("change", function (ev) { + const el = ev.target, + cl = el.classList, + line = el.parentNode, + p = +line.dataset.id; if (cl.contains("cultureBase")) changeCapital(p, line, el.value); }); @@ -59,12 +68,14 @@ function editProvinces() { } function collectStatistics() { - const cells = pack.cells, provinces = pack.provinces, burgs = pack.burgs; + const cells = pack.cells, + provinces = pack.provinces, + burgs = pack.burgs; provinces.forEach(p => { if (!p.i || p.removed) return; p.area = p.rural = p.urban = 0; p.burgs = []; - if (p.burg && !burgs[p.burg] || burgs[p.burg].removed) p.burg = 0; + if ((p.burg && !burgs[p.burg]) || burgs[p.burg].removed) p.burg = 0; }); for (const i of cells.i) { @@ -89,7 +100,7 @@ function editProvinces() { const selectedState = stateFilter.value || 1; stateFilter.options.length = 0; // remove all options stateFilter.options.add(new Option(`all`, -1, false, selectedState == -1)); - const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1); + const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1)); statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState))); } @@ -100,36 +111,38 @@ function editProvinces() { let filtered = pack.provinces.filter(p => p.i && !p.removed); // all valid burgs if (selectedState != -1) filtered = filtered.filter(p => p.state === selectedState); // filtered by state body.innerHTML = ""; - let lines = "", totalArea = 0, totalPopulation = 0; + let lines = "", + totalArea = 0, + totalPopulation = 0; for (const p of filtered) { - const area = p.area * (distanceScaleInput.value ** 2); + const area = p.area * distanceScaleInput.value ** 2; totalArea += area; - const rural = p.rural * populationRate.value; - const urban = p.urban * populationRate.value * urbanization.value; + const rural = p.rural * populationRate; + const urban = p.urban * populationRate * urbanization; const population = rn(rural + urban); const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`; totalPopulation += population; const stateName = pack.states[p.state].name; - const capital = p.burg ? pack.burgs[p.burg].name : ''; + const capital = p.burg ? pack.burgs[p.burg].name : ""; const separable = p.burg && p.burg !== pack.states[p.state].capital; - const focused = defs.select("#fog #focusProvince"+p.i).size(); - COArenderer.trigger("provinceCOA"+p.i, p.coa); + const focused = defs.select("#fog #focusProvince" + p.i).size(); + COArenderer.trigger("provinceCOA" + p.i, p.coa); lines += `
- - + +
${si(area) + unit}
${si(population)}
- - + +
`; } @@ -148,14 +161,17 @@ function editProvinces() { el.addEventListener("mouseleave", ev => provinceHighlightOff(ev)); }); - if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} + if (body.dataset.type === "percentage") { + body.dataset.type = "absolute"; + togglePercentageMode(); + } applySorting(provincesHeader); $("#provincesEditor").dialog({width: fitContent()}); } function getCapitalOptions(burgs, capital) { let options = ""; - burgs.forEach(b => options += ``); + burgs.forEach(b => (options += ``)); return options; } @@ -167,7 +183,12 @@ function editProvinces() { if (!layerIsOn("toggleProvinces")) return; if (customization) return; const animate = d3.transition().duration(2000).ease(d3.easeSinIn); - provs.select("#province"+province).raise().transition(animate).attr("stroke-width", 2.5).attr("stroke", "#d0240f"); + provs + .select("#province" + province) + .raise() + .transition(animate) + .attr("stroke-width", 2.5) + .attr("stroke", "#d0240f"); } function provinceHighlightOff(event) { @@ -176,20 +197,24 @@ function editProvinces() { if (el) el.classList.remove("active"); if (!layerIsOn("toggleProvinces")) return; - provs.select("#province"+province).transition().attr("stroke-width", null).attr("stroke", null); + provs + .select("#province" + province) + .transition() + .attr("stroke-width", null) + .attr("stroke", null); } function changeFill(el) { const currentFill = el.getAttribute("fill"); const p = +el.parentNode.parentNode.dataset.id; - const callback = function(fill) { + const callback = function (fill) { el.setAttribute("fill", fill); pack.provinces[p].color = fill; const g = provs.select("#provincesBody"); - g.select("#province"+p).attr("fill", fill); - g.select("#province-gap"+p).attr("stroke", fill); - } + g.select("#province" + p).attr("fill", fill); + g.select("#province-gap" + p).attr("stroke", fill); + }; openPicker(currentFill, callback); } @@ -197,26 +222,36 @@ function editProvinces() { function capitalZoomIn(p) { const capital = pack.provinces[p].burg; const l = burgLabels.select("[data-id='" + capital + "']"); - const x = +l.attr("x"), y = +l.attr("y"); + const x = +l.attr("x"), + y = +l.attr("y"); zoomTo(x, y, 8, 2000); } function triggerIndependencePromps(p) { alertMessage.innerHTML = "Are you sure you want to declare province independence?
It will turn province into a new state"; - $("#alert").dialog({resizable: false, title: "Declare independence", + $("#alert").dialog({ + resizable: false, + title: "Declare independence", buttons: { - Declare: function() { + Declare: function () { declareProvinceIndependence(p); $(this).dialog("close"); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } function declareProvinceIndependence(p) { - const states = pack.states, provinces = pack.provinces, cells = pack.cells; - if (provinces[p].burgs.some(b => pack.burgs[b].capital)) {tip("Cannot declare independence of a province having capital burg. Please change capital first", false, "error"); return;} + const states = pack.states, + provinces = pack.provinces, + cells = pack.cells; + if (provinces[p].burgs.some(b => pack.burgs[b].capital)) { + tip("Cannot declare independence of a province having capital burg. Please change capital first", false, "error"); + return; + } const oldState = pack.provinces[p].state; const newState = pack.states.length; @@ -228,7 +263,7 @@ function editProvinces() { moveBurgToGroup(burg, "cities"); // move all burgs to a new state - provinces[p].burgs.forEach(b => pack.burgs[b].state = newState); + provinces[p].burgs.forEach(b => (pack.burgs[b].state = newState)); // difine new state attributes const center = pack.burgs[burg].cell; @@ -237,21 +272,24 @@ function editProvinces() { const color = getRandomColor(); const coa = provinces[p].coa; - const coaEl = document.getElementById("provinceCOA"+p); - if (coaEl) coaEl.id = "stateCOA"+newState; + const coaEl = document.getElementById("provinceCOA" + p); + if (coaEl) coaEl.id = "stateCOA" + newState; emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove(); // update cells - cells.i.filter(i => cells.province[i] === p).forEach(i => { - cells.province[i] = 0; - cells.state[i] = newState; - }); + cells.i + .filter(i => cells.province[i] === p) + .forEach(i => { + cells.province[i] = 0; + cells.state[i] = newState; + }); // update diplomacy and reverse relations const diplomacy = states.map(s => { if (!s.i || s.removed) return "x"; let relations = states[oldState].diplomacy[s.i]; // relations between Nth state and old overlord - if (s.i === oldState) relations = "Enemy"; // new state is Enemy to its old overlord + if (s.i === oldState) relations = "Enemy"; + // new state is Enemy to its old overlord else if (relations === "Ally") relations = "Suspicion"; else if (relations === "Friendly") relations = "Suspicion"; else if (relations === "Suspicion") relations = "Neutral"; @@ -266,19 +304,21 @@ function editProvinces() { states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldState].name}`]); // create new state - states.push({i:newState, name, diplomacy, provinces:[], color, expansionism:.5, capital:burg, type:"Generic", center, culture, military:[], alert:1, coa}); + states.push({i: newState, name, diplomacy, provinces: [], color, expansionism: 0.5, capital: burg, type: "Generic", center, culture, military: [], alert: 1, coa}); BurgsAndStates.collectStatistics(); BurgsAndStates.defineStateForms([newState]); if (layerIsOn("toggleProvinces")) toggleProvinces(); - if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); + if (!layerIsOn("toggleStates")) toggleStates(); + else drawStates(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); BurgsAndStates.drawStateLabels([newState, oldState]); // remove old province - unfog("focusProvince"+p); + unfog("focusProvince" + p); if (states[oldState].provinces.includes(p)) states[oldState].provinces.splice(states[oldState].provinces.indexOf(p), 1); - provinces[p] = {i:p, removed: true}; + provinces[p] = {i: p, removed: true}; // draw emblem COArenderer.add("state", newState, coa, pack.states[newState].pole[0], pack.states[newState].pole[1]); @@ -290,53 +330,65 @@ function editProvinces() { function changePopulation(province) { const p = pack.provinces[province]; const cells = pack.cells.i.filter(i => pack.cells.province[i] === province); - if (!cells.length) {tip("Province does not have any cells, cannot change population", false, "error"); return;} - const rural = rn(p.rural * populationRate.value); - const urban = rn(p.urban * populationRate.value * urbanization.value); + if (!cells.length) { + tip("Province does not have any cells, cannot change population", false, "error"); + return; + } + const rural = rn(p.rural * populationRate); + const urban = rn(p.urban * populationRate * urbanization); const total = rural + urban; const l = n => Number(n).toLocaleString(); alertMessage.innerHTML = ` Rural: - Urban: + Urban:

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

`; - const update = function() { + const update = function () { const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; if (isNaN(totalNew)) return; totalPop.innerHTML = l(totalNew); - totalPopPerc.innerHTML = rn(totalNew / total * 100); - } + totalPopPerc.innerHTML = rn((totalNew / total) * 100); + }; ruralPop.oninput = () => update(); urbanPop.oninput = () => update(); $("#alert").dialog({ - resizable: false, title: "Change province population", width: "24em", buttons: { - Apply: function() {applyPopulationChange(); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Change province population", + width: "24em", + buttons: { + Apply: function () { + applyPopulationChange(); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); function applyPopulationChange() { const ruralChange = ruralPop.value / rural; if (isFinite(ruralChange) && ruralChange !== 1) { - cells.forEach(i => pack.cells.pop[i] *= ruralChange); + cells.forEach(i => (pack.cells.pop[i] *= ruralChange)); } if (!isFinite(ruralChange) && +ruralPop.value > 0) { - const points = ruralPop.value / populationRate.value; + const points = ruralPop.value / populationRate; const pop = rn(points / cells.length); - cells.forEach(i => pack.cells.pop[i] = pop); + cells.forEach(i => (pack.cells.pop[i] = pop)); } const urbanChange = urbanPop.value / urban; if (isFinite(urbanChange) && urbanChange !== 1) { - p.burgs.forEach(b => pack.burgs[b].population = rn(pack.burgs[b].population * urbanChange, 4)); + p.burgs.forEach(b => (pack.burgs[b].population = rn(pack.burgs[b].population * urbanChange, 4))); } if (!isFinite(urbanChange) && +urbanPop.value > 0) { - const points = urbanPop.value / populationRate.value / urbanization.value; + const points = urbanPop.value / populationRate / urbanization; const population = rn(points / burgs.length, 4); - p.burgs.forEach(b => pack.burgs[b].population = population); + p.burgs.forEach(b => (pack.burgs[b].population = population)); } refreshProvincesEditor(); @@ -344,23 +396,27 @@ function editProvinces() { } function toggleFog(p, cl) { - const path = provs.select("#province"+p).attr("d"), id = "focusProvince"+p; + const path = provs.select("#province" + p).attr("d"), + id = "focusProvince" + p; cl.contains("inactive") ? fog(id, path) : unfog(id); cl.toggle("inactive"); } function removeProvince(p) { alertMessage.innerHTML = `Are you sure you want to remove the province?
This action cannot be reverted`; - $("#alert").dialog({resizable: false, title: "Remove province", + $("#alert").dialog({ + resizable: false, + title: "Remove province", buttons: { - Remove: function() { + Remove: function () { pack.cells.province.forEach((province, i) => { - if(province === p) pack.cells.province[i] = 0; + if (province === p) pack.cells.province[i] = 0; }); - const s = pack.provinces[p].state, state = pack.states[s]; + const s = pack.provinces[p].state, + state = pack.states[s]; if (state.provinces.includes(p)) state.provinces.splice(state.provinces.indexOf(p), 1); - unfog("focusProvince"+p); + unfog("focusProvince" + p); const coaId = "provinceCOA" + p; if (document.getElementById(coaId)) document.getElementById(coaId).remove(); @@ -369,13 +425,16 @@ function editProvinces() { pack.provinces[p] = {i: p, removed: true}; const g = provs.select("#provincesBody"); - g.select("#province"+p).remove(); - g.select("#province-gap"+p).remove(); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); + g.select("#province" + p).remove(); + g.select("#province-gap" + p).remove(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); refreshProvincesEditor(); $(this).dialog("close"); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } @@ -384,14 +443,22 @@ function editProvinces() { const p = pack.provinces[province]; document.getElementById("provinceNameEditor").dataset.province = province; document.getElementById("provinceNameEditorShort").value = p.name; - applyOption(provinceNameEditorSelectForm, p.formName) + applyOption(provinceNameEditorSelectForm, p.formName); document.getElementById("provinceNameEditorFull").value = p.fullName; $("#provinceNameEditor").dialog({ - resizable: false, title: "Change province name", buttons: { - Apply: function() {applyNameChange(p); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Change province name", + buttons: { + Apply: function () { + applyNameChange(p); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); if (modules.editProvinceName) return; @@ -411,7 +478,7 @@ function editProvinces() { } function regenerateShortNameRandom() { - const base = rand(nameBases.length-1); + const base = rand(nameBases.length - 1); const name = Names.getState(Names.getBase(base), undefined, base); document.getElementById("provinceNameEditorShort").value = name; } @@ -440,7 +507,7 @@ function editProvinces() { p.name = document.getElementById("provinceNameEditorShort").value; p.formName = document.getElementById("provinceNameEditorSelectForm").value; p.fullName = document.getElementById("provinceNameEditorFull").value; - provs.select("#provinceLabel"+p.i).text(p.name); + provs.select("#provinceLabel" + p.i).text(p.name); refreshProvincesEditor(); } } @@ -457,9 +524,9 @@ function editProvinces() { const totalArea = +provincesFooterArea.dataset.area; const totalPopulation = +provincesFooterPopulation.dataset.population; - body.querySelectorAll(":scope > div").forEach(function(el) { - el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100) + "%"; - el.querySelector(".culturePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100) + "%"; + body.querySelectorAll(":scope > div").forEach(function (el) { + el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%"; + el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%"; }); } else { body.dataset.type = "absolute"; @@ -469,16 +536,21 @@ function editProvinces() { function showChart() { // build hierarchy tree - const getColor = (s) => !s.i || s.removed || s.color[0] !== "#" ? "#666" : d3.color(s.color).darker(); + const getColor = s => (!s.i || s.removed || s.color[0] !== "#" ? "#666" : d3.color(s.color).darker()); const states = pack.states.map(s => ({id: s.i, state: s.i ? 0 : null, color: getColor(s)})); - const provinces = pack.provinces.filter(p => p.i && !p.removed).map(p => { - return {id:p.i+states.length-1, i:p.i, state:p.state, color:p.color, - name:p.name, fullName:p.fullName, area:p.area, urban:p.urban, rural:p.rural} - }); + const provinces = pack.provinces + .filter(p => p.i && !p.removed) + .map(p => { + return {id: p.i + states.length - 1, i: p.i, state: p.state, color: p.color, name: p.name, fullName: p.fullName, area: p.area, urban: p.urban, rural: p.rural}; + }); const data = states.concat(provinces); - const root = d3.stratify().parentId(d => d.state)(data).sum(d => d.area); + const root = d3 + .stratify() + .parentId(d => d.state)(data) + .sum(d => d.area); - const width = 300 + 300 * uiSizeOutput.value, height = 90 + 90 * uiSizeOutput.value; + const width = 300 + 300 * uiSizeOutput.value, + height = 90 + 90 * uiSizeOutput.value; const margin = {top: 10, right: 10, bottom: 0, left: 10}; const w = width - margin.left - margin.right; const h = height - margin.top - margin.bottom; @@ -492,15 +564,18 @@ function editProvinces() { `; alertMessage.innerHTML += `
`; - const svg = d3.select("#alertMessage").insert("svg", "#provinceInfo").attr("id", "provincesTree") - .attr("width", width).attr("height", height).attr("font-size", "10px"); + const svg = d3.select("#alertMessage").insert("svg", "#provinceInfo").attr("id", "provincesTree").attr("width", width).attr("height", height).attr("font-size", "10px"); const graph = svg.append("g").attr("transform", `translate(10, 0)`); document.getElementById("provincesTreeType").addEventListener("change", updateChart); treeLayout(root); - const node = graph.selectAll("g").data(root.leaves()).enter() - .append("g").attr("data-id", d => d.data.i) + const node = graph + .selectAll("g") + .data(root.leaves()) + .enter() + .append("g") + .attr("data-id", d => d.data.i) .on("mouseenter", d => showInfo(event, d)) .on("mouseleave", d => hideInfo(event, d)); @@ -510,14 +585,11 @@ function editProvinces() { const state = pack.states[d.data.state].fullName; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; - const area = d.data.area * (distanceScaleInput.value ** 2) + unit; - const rural = rn(d.data.rural * populationRate.value); - const urban = rn(d.data.urban * populationRate.value * urbanization.value); + const area = d.data.area * distanceScaleInput.value ** 2 + unit; + const rural = rn(d.data.rural * populationRate); + const urban = rn(d.data.urban * populationRate * urbanization); - const value = provincesTreeType.value === "area" ? "Area: " + area - : provincesTreeType.value === "rural" ? "Rural population: " + si(rural) - : provincesTreeType.value === "urban" ? "Urban population: " + si(urban) - : "Population: " + si(rural + urban); + const value = provincesTreeType.value === "area" ? "Area: " + area : provincesTreeType.value === "rural" ? "Rural population: " + si(rural) : provincesTreeType.value === "urban" ? "Urban population: " + si(urban) : "Population: " + si(rural + urban); provinceInfo.innerHTML = `${name}. ${state}. ${value}`; provinceHighlightOn(ev); @@ -530,52 +602,73 @@ function editProvinces() { d3.select(ev.target).select("rect").classed("selected", 0); } - node.append("rect").attr("stroke", d => d.parent.data.color) - .attr("stroke-width", 1).attr("fill", d => d.data.color) - .attr("x", d => d.x0).attr("y", d => d.y0) - .attr("width", d => d.x1 - d.x0).attr("height", d => d.y1 - d.y0); + node + .append("rect") + .attr("stroke", d => d.parent.data.color) + .attr("stroke-width", 1) + .attr("fill", d => d.data.color) + .attr("x", d => d.x0) + .attr("y", d => d.y0) + .attr("width", d => d.x1 - d.x0) + .attr("height", d => d.y1 - d.y0); - node.append("text").attr("dx", ".2em").attr("dy", "1em") - .attr("x", d => d.x0).attr("y", d => d.y0); + node + .append("text") + .attr("dx", ".2em") + .attr("dy", "1em") + .attr("x", d => d.x0) + .attr("y", d => d.y0); function hideNonfittingLabels() { - node.select("text").each(function(d) { + node.select("text").each(function (d) { this.innerHTML = d.data.name; let b = this.getBBox(); if (b.y + b.height > d.y1 + 1) this.innerHTML = ""; - for(let i=0; i < 15 && b.width > 0 && b.x + b.width > d.x1; i++) { - if (this.innerHTML.length < 3) {this.innerHTML = ""; break;} + for (let i = 0; i < 15 && b.width > 0 && b.x + b.width > d.x1; i++) { + if (this.innerHTML.length < 3) { + this.innerHTML = ""; + break; + } this.innerHTML = this.innerHTML.slice(0, -2) + "…"; b = this.getBBox(); } - }) - + }); } function updateChart() { - const value = this.value === "area" ? d => d.area - : this.value === "rural" ? d => d.rural - : this.value === "urban" ? d => d.urban - : d => d.rural + d.urban; + const value = this.value === "area" ? d => d.area : this.value === "rural" ? d => d.rural : this.value === "urban" ? d => d.urban : d => d.rural + d.urban; root.sum(value); node.data(treeLayout(root).leaves()); - node.select("rect").transition().duration(1500) - .attr("x", d => d.x0).attr("y", d => d.y0) - .attr("width", d => d.x1 - d.x0).attr("height", d => d.y1 - d.y0); + node + .select("rect") + .transition() + .duration(1500) + .attr("x", d => d.x0) + .attr("y", d => d.y0) + .attr("width", d => d.x1 - d.x0) + .attr("height", d => d.y1 - d.y0); - node.select("text").transition().duration(1500) - .attr("x", d => d.x0).attr("y", d => d.y0); + node + .select("text") + .transition() + .duration(1500) + .attr("x", d => d.x0) + .attr("y", d => d.y0); setTimeout(hideNonfittingLabels, 2000); } $("#alert").dialog({ - title: "Provinces chart", width: fitContent(), - position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, buttons: {}, - close: () => {alertMessage.innerHTML = "";} + title: "Provinces chart", + width: fitContent(), + position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, + buttons: {}, + close: () => { + alertMessage.innerHTML = ""; + } }); hideNonfittingLabels(); @@ -593,28 +686,24 @@ function editProvinces() { if (!layerIsOn("toggleBorders")) toggleBorders(); // make province and state borders more visible - provinceBorders.select("path").attr("stroke", "#000").attr("stroke-width", .5); + provinceBorders.select("path").attr("stroke", "#000").attr("stroke-width", 0.5); stateBorders.select("path").attr("stroke", "#000").attr("stroke-width", 1.2); customization = 11; provs.select("g#provincesBody").append("g").attr("id", "temp"); - provs.select("g#provincesBody").append("g").attr("id", "centers") - .attr("fill", "none").attr("stroke", "#ff0000").attr("stroke-width", 1); + provs.select("g#provincesBody").append("g").attr("id", "centers").attr("fill", "none").attr("stroke", "#ff0000").attr("stroke-width", 1); - document.querySelectorAll("#provincesBottom > *").forEach(el => el.style.display = "none"); + document.querySelectorAll("#provincesBottom > *").forEach(el => (el.style.display = "none")); document.getElementById("provincesManuallyButtons").style.display = "inline-block"; provincesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); provincesHeader.querySelector("div[data-sortby='state']").style.left = "7.7em"; provincesFooter.style.display = "none"; - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none")); $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); tip("Click on a province to select, drag the circle to change province", true); - viewbox.style("cursor", "crosshair") - .on("click", selectProvinceOnMapClick) - .call(d3.drag().on("start", dragBrush)) - .on("touchmove mousemove", moveBrush); + viewbox.style("cursor", "crosshair").on("click", selectProvinceOnMapClick).call(d3.drag().on("start", dragBrush)).on("touchmove mousemove", moveBrush); body.querySelector("div").classList.add("selected"); selectProvince(+body.querySelector("div").dataset.id); @@ -633,11 +722,14 @@ function editProvinces() { const i = findCell(point[0], point[1]); if (pack.cells.h[i] < 20 || !pack.cells.state[i]) return; - const assigned = provs.select("g#temp").select("polygon[data-cell='"+i+"']"); + const assigned = provs.select("g#temp").select("polygon[data-cell='" + i + "']"); const province = assigned.size() ? +assigned.attr("data-province") : pack.cells.province[i]; - const editorLine = body.querySelector("div[data-id='"+province+"']"); - if (!editorLine) {tip("You cannot select a province if it is not in the Editor list", false, "error"); return;} + const editorLine = body.querySelector("div[data-id='" + province + "']"); + if (!editorLine) { + tip("You cannot select a province if it is not in the Editor list", false, "error"); + return; + } body.querySelector("div.selected").classList.remove("selected"); editorLine.classList.add("selected"); @@ -646,7 +738,7 @@ function editProvinces() { function selectProvince(p) { debug.selectAll("path.selected").remove(); - const path = provs.select("#province"+p).attr("d"); + const path = provs.select("#province" + p).attr("d"); debug.append("path").attr("class", "selected").attr("d", path); } @@ -666,7 +758,8 @@ function editProvinces() { // change province within selection function changeForSelection(selection) { - const temp = provs.select("#temp"), centers = provs.select("#centers"); + const temp = provs.select("#temp"), + centers = provs.select("#centers"); const selected = body.querySelector("div.selected"); const provinceNew = +selected.dataset.id; @@ -675,13 +768,13 @@ function editProvinces() { selection.forEach(i => { if (!pack.cells.state[i] || pack.cells.state[i] !== state) return; - const exists = temp.select("polygon[data-cell='"+i+"']"); + const exists = temp.select("polygon[data-cell='" + i + "']"); const provinceOld = exists.size() ? +exists.attr("data-province") : pack.cells.province[i]; if (provinceNew === provinceOld) return; if (i === pack.provinces[provinceOld].center) { - const center = centers.select("polygon[data-center='"+i+"']"); + const center = centers.select("polygon[data-center='" + i + "']"); if (!center.size()) centers.append("polygon").attr("data-center", i).attr("points", getPackPolygon(i)); - tip("Province center cannot be assigned to a different region. Please remove the province first", false, "error"); + tip("Province center cannot be assigned to a different region. Please remove the province first", false, "error"); return; } @@ -690,9 +783,7 @@ function editProvinces() { if (pack.cells.province[i] === provinceNew) exists.remove(); else exists.attr("data-province", provinceNew).attr("fill", fill); } else { - temp.append("polygon").attr("points", getPackPolygon(i)) - .attr("data-cell", i).attr("data-province", provinceNew) - .attr("fill", fill).attr("stroke", "#555"); + temp.append("polygon").attr("points", getPackPolygon(i)).attr("data-cell", i).attr("data-province", provinceNew).attr("fill", fill).attr("stroke", "#555"); } }); } @@ -705,13 +796,18 @@ function editProvinces() { } function applyProvincesManualAssignent() { - provs.select("#temp").selectAll("polygon").each(function() { - const i = +this.dataset.cell; - pack.cells.province[i] = +this.dataset.province;; - }); + provs + .select("#temp") + .selectAll("polygon") + .each(function () { + const i = +this.dataset.cell; + pack.cells.province[i] = +this.dataset.province; + }); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); - if (!layerIsOn("toggleProvinces")) toggleProvinces(); else drawProvinces(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); + if (!layerIsOn("toggleProvinces")) toggleProvinces(); + else drawProvinces(); exitProvincesManualAssignment(); refreshProvincesEditor(); } @@ -727,14 +823,14 @@ function editProvinces() { stateBorders.select("path").attr("stroke", null).attr("stroke-width", null); debug.selectAll("path.selected").remove(); - document.querySelectorAll("#provincesBottom > *").forEach(el => el.style.display = "inline-block"); + document.querySelectorAll("#provincesBottom > *").forEach(el => (el.style.display = "inline-block")); document.getElementById("provincesManuallyButtons").style.display = "none"; provincesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden")); provincesHeader.querySelector("div[data-sortby='state']").style.left = "22em"; provincesFooter.style.display = "block"; - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); - if(!close) $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); + if (!close) $("#provincesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); restoreDefaultEvents(); clearMainTip(); @@ -743,23 +839,36 @@ function editProvinces() { } function enterAddProvinceMode() { - if (this.classList.contains("pressed")) {exitAddProvinceMode(); return;}; + if (this.classList.contains("pressed")) { + exitAddProvinceMode(); + return; + } customization = 12; this.classList.add("pressed"); tip("Click on the map to place a new province center", true); viewbox.style("cursor", "crosshair").on("click", addProvince); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none")); } function addProvince() { - const cells = pack.cells, provinces = pack.provinces; + const cells = pack.cells, + provinces = pack.provinces; const point = d3.mouse(this); const center = findCell(point[0], point[1]); - if (cells.h[center] < 20) {tip("You cannot place province into the water. Please click on a land cell", false, "error"); return;} + if (cells.h[center] < 20) { + tip("You cannot place province into the water. Please click on a land cell", false, "error"); + return; + } const oldProvince = cells.province[center]; - if (oldProvince && provinces[oldProvince].center === center) {tip("The cell is already a center of a different province. Select other cell", false, "error"); return;} + if (oldProvince && provinces[oldProvince].center === center) { + tip("The cell is already a center of a different province. Select other cell", false, "error"); + return; + } const state = cells.state[center]; - if (!state) {tip("You cannot create a province in neutral lands. Please assign this land to a state first", false, "error"); return;} + if (!state) { + tip("You cannot create a province in neutral lands. Please assign this land to a state first", false, "error"); + return; + } if (d3.event.shiftKey === false) exitAddProvinceMode(); @@ -770,18 +879,19 @@ function editProvinces() { const name = burg ? pack.burgs[burg].name : Names.getState(Names.getCultureShort(c), c); const formName = oldProvince ? provinces[oldProvince].formName : "Province"; const fullName = name + " " + formName; - const stateColor = pack.states[state].color, rndColor = getRandomColor(); - const color = stateColor[0] === "#" ? d3.color(d3.interpolate(stateColor, rndColor)(.2)).hex() : rndColor; + const stateColor = pack.states[state].color, + rndColor = getRandomColor(); + const color = stateColor[0] === "#" ? d3.color(d3.interpolate(stateColor, rndColor)(0.2)).hex() : rndColor; // generate emblem - const kinship = burg ? .8 : .4; + const kinship = burg ? 0.8 : 0.4; const parent = burg ? pack.burgs[burg].coa : pack.states[state].coa; const type = BurgsAndStates.getType(center, parent.port); - const coa = COA.generate(parent, kinship, P(.1), type); + const coa = COA.generate(parent, kinship, P(0.1), type); coa.shield = COA.getShield(c, state); COArenderer.add("province", province, coa, point[0], point[1]); - provinces.push({i:province, state, center, burg, name, formName, fullName, color, coa}); + provinces.push({i: province, state, center, burg, name, formName, fullName, color, coa}); cells.province[center] = province; cells.c[center].forEach(c => { @@ -790,8 +900,10 @@ function editProvinces() { cells.province[c] = province; }); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); - if (!layerIsOn("toggleProvinces")) toggleProvinces(); else drawProvinces(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); + if (!layerIsOn("toggleProvinces")) toggleProvinces(); + else drawProvinces(); collectStatistics(); document.getElementById("provincesFilterState").value = state; provincesEditorAddLines(); @@ -801,7 +913,7 @@ function editProvinces() { customization = 0; restoreDefaultEvents(); clearMainTip(); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); if (provincesAdd.classList.contains("pressed")) provincesAdd.classList.remove("pressed"); } @@ -813,18 +925,19 @@ function editProvinces() { if (state !== -1 && p.state !== state) return; const stateColor = pack.states[p.state].color; const rndColor = getRandomColor(); - p.color = stateColor[0] === "#" ? d3.color(d3.interpolate(stateColor, rndColor)(.2)).hex() : rndColor; + p.color = stateColor[0] === "#" ? d3.color(d3.interpolate(stateColor, rndColor)(0.2)).hex() : rndColor; }); - if (!layerIsOn("toggleProvinces")) toggleProvinces(); else drawProvinces(); + if (!layerIsOn("toggleProvinces")) toggleProvinces(); + else drawProvinces(); } function downloadProvincesData() { const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; - let data = "Id,Province,Form,State,Color,Capital,Area "+unit+",Total Population,Rural Population,Urban Population\n"; // headers + let data = "Id,Province,Form,State,Color,Capital,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers - body.querySelectorAll(":scope > div").forEach(function(el) { - let key = parseInt(el.dataset.id) + body.querySelectorAll(":scope > div").forEach(function (el) { + let key = parseInt(el.dataset.id); data += el.dataset.id + ","; data += el.dataset.name + ","; data += el.dataset.form + ","; @@ -833,8 +946,8 @@ function editProvinces() { data += el.dataset.capital + ","; data += el.dataset.area + ","; data += el.dataset.population + ","; - data += `${Math.round(pack.provinces[key].rural*populationRate.value)},` - data += `${Math.round(pack.provinces[key].urban*populationRate.value * urbanization.value)}\n` + data += `${Math.round(pack.provinces[key].rural * populationRate)},`; + data += `${Math.round(pack.provinces[key].urban * populationRate * urbanization)}\n`; }); const name = getFileName("Provinces") + ".csv"; @@ -843,9 +956,11 @@ function editProvinces() { function removeAllProvinces() { alertMessage.innerHTML = `Are you sure you want to remove all provinces?
This action cannot be reverted`; - $("#alert").dialog({resizable: false, title: "Remove all provinces", + $("#alert").dialog({ + resizable: false, + title: "Remove all provinces", buttons: { - Remove: function() { + Remove: function () { $(this).dialog("close"); // remove emblems @@ -855,26 +970,30 @@ function editProvinces() { // remove data pack.provinces = [0]; pack.cells.province = new Uint16Array(pack.cells.i.length); - pack.states.forEach(s => s.provinces = []); + pack.states.forEach(s => (s.provinces = [])); unfog(); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); provs.select("#provincesBody").remove(); turnButtonOff("toggleProvinces"); provincesEditorAddLines(); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } function dragLabel() { const tr = parseTransform(this.getAttribute("transform")); - const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y; + const x = +tr[0] - d3.event.x, + y = +tr[1] - d3.event.y; - d3.event.on("drag", function() { - const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`; + d3.event.on("drag", function () { + const transform = `translate(${x + d3.event.x},${y + d3.event.y})`; this.setAttribute("transform", transform); }); } @@ -884,6 +1003,4 @@ function editProvinces() { if (customization === 11) exitProvincesManualAssignment("close"); if (customization === 12) exitAddProvinceMode(); } - } - diff --git a/modules/ui/religions-editor.js b/modules/ui/religions-editor.js index 8161f15e..f635dd40 100644 --- a/modules/ui/religions-editor.js +++ b/modules/ui/religions-editor.js @@ -17,7 +17,10 @@ function editReligions() { modules.editReligions = true; $("#religionsEditor").dialog({ - title: "Religions Editor", resizable: false, width: fitContent(), close: closeReligionsEditor, + title: "Religions Editor", + resizable: false, + width: fitContent(), + close: closeReligionsEditor, position: {my: "right top", at: "right-10 top+10", of: "svg"} }); @@ -40,8 +43,9 @@ function editReligions() { } function religionsCollectStatistics() { - const cells = pack.cells, religions = pack.religions; - religions.forEach(r => r.cells = r.area = r.rural = r.urban = 0); + const cells = pack.cells, + religions = pack.religions; + religions.forEach(r => (r.cells = r.area = r.rural = r.urban = 0)); for (const i of cells.i) { if (cells.h[i] < 20) continue; @@ -56,14 +60,16 @@ function editReligions() { // add line for each religion function religionsEditorAddLines() { const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; - let lines = "", totalArea = 0, totalPopulation = 0; + let lines = "", + totalArea = 0, + totalPopulation = 0; for (const r of pack.religions) { if (r.removed) continue; - const area = r.area * (distanceScaleInput.value ** 2); - const rural = r.rural * populationRate.value; - const urban = r.urban * populationRate.value * urbanization.value; + const area = r.area * distanceScaleInput.value ** 2; + const rural = r.rural * populationRate; + const urban = r.urban * populationRate * urbanization; const population = rn(rural + urban); if (r.i && !r.cells && body.dataset.extinct !== "show") continue; // hide extinct religions const populationTip = `Believers: ${si(population)}; Rural areas: ${si(rural)}; Urban areas: ${si(urban)}. Click to change`; @@ -72,13 +78,13 @@ function editReligions() { if (r.i) { lines += `
+ data-population=${population} data-type=${r.type} data-form=${r.form} data-deity="${r.deity ? r.deity : ""}" data-expansionism=${r.expansionism}> - +
${si(area) + unit}
@@ -127,7 +133,10 @@ function editReligions() { body.querySelectorAll("div > div.culturePopulation").forEach(el => el.addEventListener("click", changePopulation)); body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", religionRemove)); - if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} + if (body.dataset.type === "percentage") { + body.dataset.type = "absolute"; + togglePercentageMode(); + } applySorting(religionsHeader); $("#religionsEditor").dialog({width: fitContent()}); } @@ -135,7 +144,7 @@ function editReligions() { function getTypeOptions(type) { let options = ""; const types = ["Folk", "Organized", "Cult", "Heresy"]; - types.forEach(t => options += ``); + types.forEach(t => (options += ``)); return options; } @@ -143,14 +152,14 @@ function editReligions() { const religion = +event.target.dataset.id; const info = document.getElementById("religionInfo"); if (info) { - d3.select("#hierarchy").select("g[data-id='"+religion+"'] > path").classed("selected", 1); + d3.select("#hierarchy") + .select("g[data-id='" + religion + "'] > path") + .classed("selected", 1); const r = pack.religions[religion]; - const type = r.name.includes(r.type) ? "" - : r.type === "Folk" || r.type === "Organized" - ? ". " + r.type + " religion" : ". " + r.type; + const type = r.name.includes(r.type) ? "" : r.type === "Folk" || r.type === "Organized" ? ". " + r.type + " religion" : ". " + r.type; const form = r.form === r.type || r.name.includes(r.form) ? "" : ". " + r.form; - const rural = r.rural * populationRate.value; - const urban = r.urban * populationRate.value * urbanization.value; + const rural = r.rural * populationRate; + const urban = r.urban * populationRate * urbanization; const population = rural + urban > 0 ? ". " + si(rn(rural + urban)) + " believers" : ". Extinct"; info.innerHTML = `${r.name}${type}${form}${population}`; tip("Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation"); @@ -161,24 +170,46 @@ function editReligions() { if (!layerIsOn("toggleReligions")) return; if (customization) return; - relig.select("#religion"+religion).raise().transition(animate).attr("stroke-width", 2.5).attr("stroke", "#c13119"); - debug.select("#religionsCenter"+religion).raise().transition(animate).attr("r", 8).attr("stroke-width", 2).attr("stroke", "#c13119"); + relig + .select("#religion" + religion) + .raise() + .transition(animate) + .attr("stroke-width", 2.5) + .attr("stroke", "#c13119"); + debug + .select("#religionsCenter" + religion) + .raise() + .transition(animate) + .attr("r", 8) + .attr("stroke-width", 2) + .attr("stroke", "#c13119"); } function religionHighlightOff(event) { const religion = +event.target.dataset.id; const info = document.getElementById("religionInfo"); if (info) { - d3.select("#hierarchy").select("g[data-id='"+religion+"'] > path").classed("selected", 0); + d3.select("#hierarchy") + .select("g[data-id='" + religion + "'] > path") + .classed("selected", 0); info.innerHTML = "‍"; tip(""); } - const el = body.querySelector(`div[data-id='${religion}']`) + const el = body.querySelector(`div[data-id='${religion}']`); if (el) el.classList.remove("active"); - relig.select("#religion"+religion).transition().attr("stroke-width", null).attr("stroke", null); - debug.select("#religionsCenter"+religion).transition().attr("r", 4).attr("stroke-width", 1.2).attr("stroke", null); + relig + .select("#religion" + religion) + .transition() + .attr("stroke-width", null) + .attr("stroke", null); + debug + .select("#religionsCenter" + religion) + .transition() + .attr("r", 4) + .attr("stroke-width", 1.2) + .attr("stroke", null); } function religionChangeColor() { @@ -186,12 +217,12 @@ function editReligions() { const currentFill = el.getAttribute("fill"); const religion = +el.parentNode.parentNode.dataset.id; - const callback = function(fill) { + const callback = function (fill) { el.setAttribute("fill", fill); pack.religions[religion].color = fill; - relig.select("#religion"+religion).attr("fill", fill); - debug.select("#religionsCenter"+religion).attr("fill", fill); - } + relig.select("#religion" + religion).attr("fill", fill); + debug.select("#religionsCenter" + religion).attr("fill", fill); + }; openPicker(currentFill, callback); } @@ -200,7 +231,10 @@ function editReligions() { const religion = +this.parentNode.dataset.id; this.parentNode.dataset.name = this.value; pack.religions[religion].name = this.value; - pack.religions[religion].code = abbreviate(this.value, pack.religions.map(c => c.code)); + pack.religions[religion].code = abbreviate( + this.value, + pack.religions.map(c => c.code) + ); } function religionChangeType() { @@ -233,9 +267,12 @@ function editReligions() { function changePopulation() { const religion = +this.parentNode.dataset.id; const r = pack.religions[religion]; - if (!r.cells) {tip("Religion does not have any cells, cannot change population", false, "error"); return;} - const rural = rn(r.rural * populationRate.value); - const urban = rn(r.urban * populationRate.value * urbanization.value); + if (!r.cells) { + tip("Religion does not have any cells, cannot change population", false, "error"); + return; + } + const rural = rn(r.rural * populationRate); + const urban = rn(r.urban * populationRate * urbanization); const total = rural + urban; const l = n => Number(n).toLocaleString(); const burgs = pack.burgs.filter(b => !b.removed && pack.cells.religion[b.cell] === religion); @@ -243,52 +280,60 @@ function editReligions() { alertMessage.innerHTML = `

Please note all population of religion territory is considered believers of this religion. It means believers number change will directly change population

Rural: - Urban: + Urban:

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

`; - const update = function() { + const update = function () { const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; if (isNaN(totalNew)) return; totalPop.innerHTML = l(totalNew); - totalPopPerc.innerHTML = rn(totalNew / total * 100); - } + totalPopPerc.innerHTML = rn((totalNew / total) * 100); + }; ruralPop.oninput = () => update(); urbanPop.oninput = () => update(); $("#alert").dialog({ - resizable: false, title: "Change believers number", width: "24em", buttons: { - Apply: function() {applyPopulationChange(); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Change believers number", + width: "24em", + buttons: { + Apply: function () { + applyPopulationChange(); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); function applyPopulationChange() { const ruralChange = ruralPop.value / rural; if (isFinite(ruralChange) && ruralChange !== 1) { const cells = pack.cells.i.filter(i => pack.cells.religion[i] === religion); - cells.forEach(i => pack.cells.pop[i] *= ruralChange); + cells.forEach(i => (pack.cells.pop[i] *= ruralChange)); } if (!isFinite(ruralChange) && +ruralPop.value > 0) { - const points = ruralPop.value / populationRate.value; + const points = ruralPop.value / populationRate; const cells = pack.cells.i.filter(i => pack.cells.religion[i] === religion); const pop = rn(points / cells.length); - cells.forEach(i => pack.cells.pop[i] = pop); + cells.forEach(i => (pack.cells.pop[i] = pop)); } const urbanChange = urbanPop.value / urban; if (isFinite(urbanChange) && urbanChange !== 1) { - burgs.forEach(b => b.population = rn(b.population * urbanChange, 4)); + burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4))); } if (!isFinite(urbanChange) && +urbanPop.value > 0) { - const points = urbanPop.value / populationRate.value / urbanization.value; + const points = urbanPop.value / populationRate / urbanization; const population = rn(points / burgs.length, 4); - burgs.forEach(b => b.population = population); + burgs.forEach(b => (b.population = population)); } refreshReligionsEditor(); } - } function religionRemove() { @@ -296,43 +341,59 @@ function editReligions() { const religion = +this.parentNode.dataset.id; alertMessage.innerHTML = "Are you sure you want to remove the religion?
This action cannot be reverted"; - $("#alert").dialog({resizable: false, title: "Remove religion", + $("#alert").dialog({ + resizable: false, + title: "Remove religion", buttons: { - Remove: function() { - relig.select("#religion"+religion).remove(); - relig.select("#religion-gap"+religion).remove(); - debug.select("#religionsCenter"+religion).remove(); + Remove: function () { + relig.select("#religion" + religion).remove(); + relig.select("#religion-gap" + religion).remove(); + debug.select("#religionsCenter" + religion).remove(); - pack.cells.religion.forEach((r, i) => {if(r === religion) pack.cells.religion[i] = 0;}); + pack.cells.religion.forEach((r, i) => { + if (r === religion) pack.cells.religion[i] = 0; + }); pack.religions[religion].removed = true; const origin = pack.religions[religion].origin; - pack.religions.forEach(r => {if(r.origin === religion) r.origin = origin;}); - + pack.religions.forEach(r => { + if (r.origin === religion) r.origin = origin; + }); + refreshReligionsEditor(); $(this).dialog("close"); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } function drawReligionCenters() { debug.select("#religionCenters").remove(); - const religionCenters = debug.append("g").attr("id", "religionCenters") - .attr("stroke-width", 1.2).attr("stroke", "#444444").style("cursor", "move"); + const religionCenters = debug.append("g").attr("id", "religionCenters").attr("stroke-width", 1.2).attr("stroke", "#444444").style("cursor", "move"); const data = pack.religions.filter(r => r.i && r.center && r.cells && !r.removed); - religionCenters.selectAll("circle").data(data).enter().append("circle") - .attr("id", d => "religionsCenter"+d.i).attr("data-id", d => d.i) - .attr("r", 4).attr("fill", d => d.color) - .attr("cx", d => pack.cells.p[d.center][0]).attr("cy", d => pack.cells.p[d.center][1]) + religionCenters + .selectAll("circle") + .data(data) + .enter() + .append("circle") + .attr("id", d => "religionsCenter" + d.i) + .attr("data-id", d => d.i) + .attr("r", 4) + .attr("fill", d => d.color) + .attr("cx", d => pack.cells.p[d.center][0]) + .attr("cy", d => pack.cells.p[d.center][1]) .on("mouseenter", d => { - tip(d.name+ ". Drag to move the religion center", true); + tip(d.name + ". Drag to move the religion center", true); religionHighlightOn(event); - }).on("mouseleave", d => { - tip('', true); + }) + .on("mouseleave", d => { + tip("", true); religionHighlightOff(event); - }).call(d3.drag().on("start", religionCenterDrag)); + }) + .call(d3.drag().on("start", religionCenterDrag)); } function religionCenterDrag() { @@ -347,8 +408,14 @@ function editReligions() { } function toggleLegend() { - if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend - const data = pack.religions.filter(r => r.i && !r.removed && r.area).sort((a, b) => b.area - a.area).map(r => [r.i, r.color, r.name]); + if (legend.selectAll("*").size()) { + clearLegend(); + return; + } // hide legend + const data = pack.religions + .filter(r => r.i && !r.removed && r.area) + .sort((a, b) => b.area - a.area) + .map(r => [r.i, r.color, r.name]); drawLegend("Religions", data); } @@ -358,9 +425,9 @@ function editReligions() { const totalArea = +religionsFooterArea.dataset.area; const totalPopulation = +religionsFooterPopulation.dataset.population; - body.querySelectorAll(":scope > div").forEach(function(el) { - el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100) + "%"; - el.querySelector(".culturePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100) + "%"; + body.querySelectorAll(":scope > div").forEach(function (el) { + el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%"; + el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%"; }); } else { body.dataset.type = "absolute"; @@ -372,11 +439,18 @@ function editReligions() { // build hierarchy tree pack.religions[0].origin = null; const religions = pack.religions.filter(r => !r.removed); - if (religions.length < 3) {tip("Not enough religions to show hierarchy", false, "error"); return;} - const root = d3.stratify().id(d => d.i).parentId(d => d.origin)(religions); + if (religions.length < 3) { + tip("Not enough religions to show hierarchy", false, "error"); + return; + } + const root = d3 + .stratify() + .id(d => d.i) + .parentId(d => d.origin)(religions); const treeWidth = root.leaves().length; const treeHeight = root.height; - const width = treeWidth * 40, height = treeHeight * 60; + const width = treeWidth * 40, + height = treeHeight * 60; const margin = {top: 10, right: 10, bottom: -5, left: 10}; const w = width - margin.left - margin.right; @@ -385,8 +459,7 @@ function editReligions() { // prepare svg alertMessage.innerHTML = "
"; - const svg = d3.select("#alertMessage").insert("svg", "#religionInfo").attr("id", "hierarchy") - .attr("width", width).attr("height", height).style("text-anchor", "middle"); + const svg = d3.select("#alertMessage").insert("svg", "#religionInfo").attr("id", "hierarchy").attr("width", width).attr("height", height).style("text-anchor", "middle"); const graph = svg.append("g").attr("transform", `translate(10, -45)`); const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa"); const nodes = graph.append("g"); @@ -394,45 +467,70 @@ function editReligions() { renderTree(); function renderTree() { treeLayout(root); - links.selectAll('path').data(root.links()).enter() - .append('path').attr("d", d => {return "M" + d.source.x + "," + d.source.y - + "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 - + " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 - + " " + d.target.x + "," + d.target.y;}); + links + .selectAll("path") + .data(root.links()) + .enter() + .append("path") + .attr("d", d => { + return "M" + d.source.x + "," + d.source.y + "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 + " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 + " " + d.target.x + "," + d.target.y; + }); - const node = nodes.selectAll('g').data(root.descendants()).enter() - .append('g').attr("data-id", d => d.data.i).attr("stroke", "#333333") + const node = nodes + .selectAll("g") + .data(root.descendants()) + .enter() + .append("g") + .attr("data-id", d => d.data.i) + .attr("stroke", "#333333") .attr("transform", d => `translate(${d.x}, ${d.y})`) .on("mouseenter", () => religionHighlightOn(event)) .on("mouseleave", () => religionHighlightOff(event)) .call(d3.drag().on("start", d => dragToReorigin(d))); - node.append("path").attr("d", d => { - if (d.data.type === "Folk") return "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0"; else // circle - if (d.data.type === "Heresy") return "M0,-14L14,0L0,14L-14,0Z"; else // diamond - if (d.data.type === "Cult") return "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z"; else // hex - if (!d.data.i) return "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0"; else // small circle - return "M-11,-11h22v22h-22Z"; // square - }).attr("fill", d => d.data.i ? d.data.color : "#ffffff") - .attr("stroke-dasharray", d => d.data.cells ? "null" : "1"); + node + .append("path") + .attr("d", d => { + if (d.data.type === "Folk") return "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0"; + // circle + else if (d.data.type === "Heresy") return "M0,-14L14,0L0,14L-14,0Z"; + // diamond + else if (d.data.type === "Cult") return "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z"; + // hex + else if (!d.data.i) return "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0"; + // small circle + else return "M-11,-11h22v22h-22Z"; // square + }) + .attr("fill", d => (d.data.i ? d.data.color : "#ffffff")) + .attr("stroke-dasharray", d => (d.data.cells ? "null" : "1")); - node.append("text").attr("dy", ".35em").text(d => d.data.i ? d.data.code : ''); + node + .append("text") + .attr("dy", ".35em") + .text(d => (d.data.i ? d.data.code : "")); } $("#alert").dialog({ - title: "Religions tree", width: fitContent(), resizable: false, - position: {my: "left center", at: "left+10 center", of: "svg"}, buttons: {}, - close: () => {alertMessage.innerHTML = "";} + title: "Religions tree", + width: fitContent(), + resizable: false, + position: {my: "left center", at: "left+10 center", of: "svg"}, + buttons: {}, + close: () => { + alertMessage.innerHTML = ""; + } }); function dragToReorigin(d) { - if (isCtrlClick(d3.event.sourceEvent)) {changeCode(d); return;} + if (isCtrlClick(d3.event.sourceEvent)) { + changeCode(d); + return; + } - const originLine = graph.append("path") - .attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`); + const originLine = graph.append("path").attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`); d3.event.on("drag", () => { - originLine.attr("d", `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`) + originLine.attr("d", `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`); }); d3.event.on("end", () => { @@ -446,14 +544,17 @@ function editReligions() { if (newOrigin == religion) newOrigin = 0; // move to top if (newOrigin && d.descendants().some(node => node.id == newOrigin)) return; // cannot be a child of its own child pack.religions[religion].origin = d.data.origin = newOrigin; // change data - showHierarchy() // update hierarchy + showHierarchy(); // update hierarchy }); } function changeCode(d) { - prompt(`Please provide an abbreviation for ${d.data.name}`, {default:d.data.code}, v => { + prompt(`Please provide an abbreviation for ${d.data.name}`, {default: d.data.code}, v => { pack.religions[d.data.i].code = v; - nodes.select("g[data-id='"+d.data.i+"']").select("text").text(v); + nodes + .select("g[data-id='" + d.data.i + "']") + .select("text") + .text(v); }); } } @@ -467,20 +568,17 @@ function editReligions() { if (!layerIsOn("toggleReligions")) toggleReligions(); customization = 7; relig.append("g").attr("id", "temp"); - document.querySelectorAll("#religionsBottom > button").forEach(el => el.style.display = "none"); + document.querySelectorAll("#religionsBottom > button").forEach(el => (el.style.display = "none")); document.getElementById("religionsManuallyButtons").style.display = "inline-block"; debug.select("#religionCenters").style("display", "none"); religionsEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); religionsFooter.style.display = "none"; - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none")); $("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); tip("Click on religion to select, drag the circle to change religion", true); - viewbox.style("cursor", "crosshair") - .on("click", selectReligionOnMapClick) - .call(d3.drag().on("start", dragReligionBrush)) - .on("touchmove mousemove", moveReligionBrush); + viewbox.style("cursor", "crosshair").on("click", selectReligionOnMapClick).call(d3.drag().on("start", dragReligionBrush)).on("touchmove mousemove", moveReligionBrush); body.querySelector("div").classList.add("selected"); } @@ -496,13 +594,13 @@ function editReligions() { const i = findCell(point[0], point[1]); if (pack.cells.h[i] < 20) return; - const assigned = relig.select("#temp").select("polygon[data-cell='"+i+"']"); + const assigned = relig.select("#temp").select("polygon[data-cell='" + i + "']"); const religion = assigned.size() ? +assigned.attr("data-religion") : pack.cells.religion[i]; body.querySelector("div.selected").classList.remove("selected"); - body.querySelector("div[data-id='"+religion+"']").classList.add("selected"); + body.querySelector("div[data-id='" + religion + "']").classList.add("selected"); } - + function dragReligionBrush() { const r = +religionsManuallyBrushNumber.value; @@ -510,7 +608,7 @@ function editReligions() { if (!d3.event.dx && !d3.event.dy) return; const p = d3.mouse(this); moveCircle(p[0], p[1], r); - + const found = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1], r)]; const selection = found.filter(isLand); if (selection) changeReligionForSelection(selection); @@ -524,8 +622,8 @@ function editReligions() { const r = +selected.dataset.id; // religionNew const color = pack.religions[r].color || "#ffffff"; - selection.forEach(function(i) { - const exists = temp.select("polygon[data-cell='"+i+"']"); + selection.forEach(function (i) { + const exists = temp.select("polygon[data-cell='" + i + "']"); const religionOld = exists.size() ? +exists.attr("data-religion") : pack.cells.religion[i]; if (r === religionOld) return; @@ -544,7 +642,7 @@ function editReligions() { function applyReligionsManualAssignent() { const changed = relig.select("#temp").selectAll("polygon"); - changed.each(function() { + changed.each(function () { const i = +this.dataset.cell; const r = +this.dataset.religion; pack.cells.religion[i] = r; @@ -557,18 +655,18 @@ function editReligions() { } exitReligionsManualAssignment(); } - + function exitReligionsManualAssignment(close) { customization = 0; relig.select("#temp").remove(); removeCircle(); - document.querySelectorAll("#religionsBottom > button").forEach(el => el.style.display = "inline-block"); + document.querySelectorAll("#religionsBottom > button").forEach(el => (el.style.display = "inline-block")); document.getElementById("religionsManuallyButtons").style.display = "none"; religionsEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden")); religionsFooter.style.display = "block"; - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); - if(!close) $("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); + if (!close) $("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); debug.select("#religionCenters").style("display", null); restoreDefaultEvents(); @@ -578,28 +676,37 @@ function editReligions() { } function enterAddReligionMode() { - if (this.classList.contains("pressed")) {exitAddReligionMode(); return;}; + if (this.classList.contains("pressed")) { + exitAddReligionMode(); + return; + } customization = 8; this.classList.add("pressed"); tip("Click on the map to add a new religion", true); viewbox.style("cursor", "crosshair").on("click", addReligion); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none")); } function exitAddReligionMode() { customization = 0; restoreDefaultEvents(); clearMainTip(); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); if (religionsAdd.classList.contains("pressed")) religionsAdd.classList.remove("pressed"); } function addReligion() { const point = d3.mouse(this); const center = findCell(point[0], point[1]); - if (pack.cells.h[center] < 20) {tip("You cannot place religion center into the water. Please click on a land cell", false, "error"); return;} + if (pack.cells.h[center] < 20) { + tip("You cannot place religion center into the water. Please click on a land cell", false, "error"); + return; + } const occupied = pack.religions.some(r => !r.removed && r.center === center); - if (occupied) {tip("This cell is already a religion center. Please select a different cell", false, "error"); return;} + if (occupied) { + tip("This cell is already a religion center. Please select a different cell", false, "error"); + return; + } if (d3.event.shiftKey === false) exitAddReligionMode(); Religions.add(center); @@ -611,9 +718,9 @@ function editReligions() { function downloadReligionsData() { const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; - let data = "Id,Religion,Color,Type,Form,Deity,Area "+unit+",Believers\n"; // headers + let data = "Id,Religion,Color,Type,Form,Deity,Area " + unit + ",Believers\n"; // headers - body.querySelectorAll(":scope > div").forEach(function(el) { + body.querySelectorAll(":scope > div").forEach(function (el) { data += el.dataset.id + ","; data += el.dataset.name + ","; data += el.dataset.color + ","; @@ -627,11 +734,10 @@ function editReligions() { const name = getFileName("Religions") + ".csv"; downloadFile(data, name); } - + function closeReligionsEditor() { debug.select("#religionCenters").remove(); exitReligionsManualAssignment("close"); exitAddReligionMode(); } - -} \ No newline at end of file +} diff --git a/modules/ui/states-editor.js b/modules/ui/states-editor.js index ffa86812..441b551e 100644 --- a/modules/ui/states-editor.js +++ b/modules/ui/states-editor.js @@ -15,7 +15,10 @@ function editStates() { modules.editStates = true; $("#statesEditor").dialog({ - title: "States Editor", resizable: false, width: fitContent(), close: closeStatesEditor, + title: "States Editor", + resizable: false, + width: fitContent(), + close: closeStatesEditor, position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -37,26 +40,35 @@ function editStates() { document.getElementById("statesAdd").addEventListener("click", enterAddStateMode); document.getElementById("statesExport").addEventListener("click", downloadStatesData); - body.addEventListener("click", function(ev) { - const el = ev.target, cl = el.classList, line = el.parentNode, state = +line.dataset.id; - if (cl.contains("fillRect")) stateChangeFill(el); else - if (cl.contains("name")) editStateName(state); else - if (cl.contains("coaIcon")) editEmblem("state", "stateCOA"+state, pack.states[state]); else - if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); else - if (cl.contains("culturePopulation")) changePopulation(state); else - if (cl.contains("icon-pin")) toggleFog(state, cl); else - if (cl.contains("icon-trash-empty")) stateRemovePrompt(state); + body.addEventListener("click", function (ev) { + const el = ev.target, + cl = el.classList, + line = el.parentNode, + state = +line.dataset.id; + if (cl.contains("fillRect")) stateChangeFill(el); + else if (cl.contains("name")) editStateName(state); + else if (cl.contains("coaIcon")) editEmblem("state", "stateCOA" + state, pack.states[state]); + else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); + else if (cl.contains("culturePopulation")) changePopulation(state); + else if (cl.contains("icon-pin")) toggleFog(state, cl); + else if (cl.contains("icon-trash-empty")) stateRemovePrompt(state); }); - body.addEventListener("input", function(ev) { - const el = ev.target, cl = el.classList, line = el.parentNode, state = +line.dataset.id; - if (cl.contains("stateCapital")) stateChangeCapitalName(state, line, el.value); else - if (cl.contains("cultureType")) stateChangeType(state, line, el.value); else - if (cl.contains("statePower")) stateChangeExpansionism(state, line, el.value); + body.addEventListener("input", function (ev) { + const el = ev.target, + cl = el.classList, + line = el.parentNode, + state = +line.dataset.id; + if (cl.contains("stateCapital")) stateChangeCapitalName(state, line, el.value); + else if (cl.contains("cultureType")) stateChangeType(state, line, el.value); + else if (cl.contains("statePower")) stateChangeExpansionism(state, line, el.value); }); - body.addEventListener("change", function(ev) { - const el = ev.target, cl = el.classList, line = el.parentNode, state = +line.dataset.id; + body.addEventListener("change", function (ev) { + const el = ev.target, + cl = el.classList, + line = el.parentNode, + state = +line.dataset.id; if (cl.contains("stateCulture")) stateChangeCulture(state, line, el.value); }); @@ -69,19 +81,22 @@ function editStates() { function statesEditorAddLines() { const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const hidden = statesRegenerateButtons.style.display === "block" ? "" : "hidden"; // show/hide regenerate columns - let lines = "", totalArea = 0, totalPopulation = 0, totalBurgs = 0; + let lines = "", + totalArea = 0, + totalPopulation = 0, + totalBurgs = 0; for (const s of pack.states) { if (s.removed) continue; - const area = s.area * (distanceScaleInput.value ** 2); - const rural = s.rural * populationRate.value; - const urban = s.urban * populationRate.value * urbanization.value; + const area = s.area * distanceScaleInput.value ** 2; + const rural = s.rural * populationRate; + const urban = s.urban * populationRate * urbanization; const population = rn(rural + urban); const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`; totalArea += area; totalPopulation += population; totalBurgs += s.burgs; - const focused = defs.select("#fog #focusState"+s.i).size(); + const focused = defs.select("#fog #focusState" + s.i).size(); if (!s.i) { // Neutral line @@ -110,7 +125,7 @@ function editStates() { } const capital = pack.burgs[s.capital].name; - COArenderer.trigger("stateCOA"+s.i, s.coa); + COArenderer.trigger("stateCOA" + s.i, s.coa); lines += `
@@ -131,7 +146,7 @@ function editStates() {
${s.cells}
- +
`; } @@ -152,21 +167,28 @@ function editStates() { el.addEventListener("mouseleave", ev => stateHighlightOff(ev)); }); - if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} + if (body.dataset.type === "percentage") { + body.dataset.type = "absolute"; + togglePercentageMode(); + } applySorting(statesHeader); $("#statesEditor").dialog({width: fitContent()}); } - + function getCultureOptions(culture) { let options = ""; - pack.cultures.forEach(c => {if (!c.removed) { options += `` }}); + pack.cultures.forEach(c => { + if (!c.removed) { + options += ``; + } + }); return options; } function getTypeOptions(type) { let options = ""; const types = ["Generic", "River", "Lake", "Naval", "Nomadic", "Hunting", "Highland"]; - types.forEach(t => options += ``); + types.forEach(t => (options += ``)); return options; } @@ -176,19 +198,23 @@ function editStates() { const state = +event.target.dataset.id; if (customization || !state) return; - const d = regions.select("#state"+state).attr("d"); + const d = regions.select("#state" + state).attr("d"); - const path = debug.append("path").attr("class", "highlight").attr("d", d) - .attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1) - .attr("filter", "url(#blur1)"); + const path = debug.append("path").attr("class", "highlight").attr("d", d).attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1).attr("filter", "url(#blur1)"); - const l = path.node().getTotalLength(), dur = (l + 5000) / 2; + const l = path.node().getTotalLength(), + dur = (l + 5000) / 2; const i = d3.interpolateString("0," + l, l + "," + l); - path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)}); + path + .transition() + .duration(dur) + .attrTween("stroke-dasharray", function () { + return t => i(t); + }); } function stateHighlightOff() { - debug.selectAll(".highlight").each(function() { + debug.selectAll(".highlight").each(function () { d3.select(this).transition().duration(1000).attr("opacity", 0).remove(); }); } @@ -197,20 +223,23 @@ function editStates() { const currentFill = el.getAttribute("fill"); const state = +el.parentNode.parentNode.dataset.id; - const callback = function(fill) { + const callback = function (fill) { el.setAttribute("fill", fill); pack.states[state].color = fill; - statesBody.select("#state"+state).attr("fill", fill); - statesBody.select("#state-gap"+state).attr("stroke", fill); + statesBody.select("#state" + state).attr("fill", fill); + statesBody.select("#state-gap" + state).attr("stroke", fill); const halo = d3.color(fill) ? d3.color(fill).darker().hex() : "#666666"; - statesHalo.select("#state-border"+state).attr("stroke", halo); + statesHalo.select("#state-border" + state).attr("stroke", halo); // recolor regiments const solidColor = fill[0] === "#" ? fill : "#999"; const darkerColor = d3.color(solidColor).darker().hex(); - armies.select("#army"+state).attr("fill", solidColor); - armies.select("#army"+state).selectAll("g > rect:nth-of-type(2)").attr("fill", darkerColor); - } + armies.select("#army" + state).attr("fill", solidColor); + armies + .select("#army" + state) + .selectAll("g > rect:nth-of-type(2)") + .attr("fill", darkerColor); + }; openPicker(currentFill, callback); } @@ -231,10 +260,18 @@ function editStates() { document.getElementById("stateNameEditorFull").value = s.fullName || ""; $("#stateNameEditor").dialog({ - resizable: false, title: "Change state name", buttons: { - Apply: function() {applyNameChange(s); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Change state name", + buttons: { + Apply: function () { + applyNameChange(s); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); if (modules.editStateName) return; @@ -255,7 +292,7 @@ function editStates() { } function regenerateShortNameRandom() { - const base = rand(nameBases.length-1); + const base = rand(nameBases.length - 1); const name = Names.getState(Names.getBase(base), undefined, base); document.getElementById("stateNameEditorShort").value = name; } @@ -278,8 +315,8 @@ function editStates() { if (!form) return short; if (!short && form) return "The " + form; const tick = +stateNameEditorFullRegenerate.dataset.tick; - stateNameEditorFullRegenerate.dataset.tick = tick+1; - return tick%2 ? getAdjective(short) + " " + form : form + " of " + short; + stateNameEditorFullRegenerate.dataset.tick = tick + 1; + return tick % 2 ? getAdjective(short) + " " + form : form + " of " + short; } } @@ -312,73 +349,85 @@ function editStates() { const capital = pack.states[state].capital; if (!capital) return; pack.burgs[capital].name = value; - document.querySelector("#burgLabel"+capital).textContent = value; + document.querySelector("#burgLabel" + capital).textContent = value; } function changePopulation(state) { const s = pack.states[state]; - if (!s.cells) {tip("State does not have any cells, cannot change population", false, "error"); return;} - const rural = rn(s.rural * populationRate.value); - const urban = rn(s.urban * populationRate.value * urbanization.value); + if (!s.cells) { + tip("State does not have any cells, cannot change population", false, "error"); + return; + } + const rural = rn(s.rural * populationRate); + const urban = rn(s.urban * populationRate * urbanization); const total = rural + urban; const l = n => Number(n).toLocaleString(); alertMessage.innerHTML = ` Rural: - Urban: + Urban:

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

`; - const update = function() { + const update = function () { const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; if (isNaN(totalNew)) return; totalPop.innerHTML = l(totalNew); - totalPopPerc.innerHTML = rn(totalNew / total * 100); - } + totalPopPerc.innerHTML = rn((totalNew / total) * 100); + }; ruralPop.oninput = () => update(); urbanPop.oninput = () => update(); $("#alert").dialog({ - resizable: false, title: "Change state population", width: "24em", buttons: { - Apply: function() {applyPopulationChange(); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Change state population", + width: "24em", + buttons: { + Apply: function () { + applyPopulationChange(); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); function applyPopulationChange() { const ruralChange = ruralPop.value / rural; if (isFinite(ruralChange) && ruralChange !== 1) { const cells = pack.cells.i.filter(i => pack.cells.state[i] === state); - cells.forEach(i => pack.cells.pop[i] *= ruralChange); + cells.forEach(i => (pack.cells.pop[i] *= ruralChange)); } if (!isFinite(ruralChange) && +ruralPop.value > 0) { - const points = ruralPop.value / populationRate.value; + const points = ruralPop.value / populationRate; const cells = pack.cells.i.filter(i => pack.cells.state[i] === state); const pop = points / cells.length; - cells.forEach(i => pack.cells.pop[i] = pop); + cells.forEach(i => (pack.cells.pop[i] = pop)); } const urbanChange = urbanPop.value / urban; if (isFinite(urbanChange) && urbanChange !== 1) { const burgs = pack.burgs.filter(b => !b.removed && b.state === state); - burgs.forEach(b => b.population = rn(b.population * urbanChange, 4)); + burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4))); } if (!isFinite(urbanChange) && +urbanPop.value > 0) { - const points = urbanPop.value / populationRate.value / urbanization.value; + const points = urbanPop.value / populationRate / urbanization; const burgs = pack.burgs.filter(b => !b.removed && b.state === state); const population = rn(points / burgs.length, 4); - burgs.forEach(b => b.population = population); + burgs.forEach(b => (b.population = population)); } refreshStatesEditor(); } - } function stateCapitalZoomIn(state) { const capital = pack.states[state].capital; const l = burgLabels.select("[data-id='" + capital + "']"); - const x = +l.attr("x"), y = +l.attr("y"); + const x = +l.attr("x"), + y = +l.attr("y"); zoomTo(x, y, 8, 2000); } @@ -392,13 +441,14 @@ function editStates() { } function stateChangeExpansionism(state, line, value) { - line.dataset.expansionism = pack.states[state].expansionism = value; + line.dataset.expansionism = pack.states[state].expansionism = value; recalculateStates(); } function toggleFog(state, cl) { if (customization) return; - const path = statesBody.select("#state"+state).attr("d"), id = "focusState"+state; + const path = statesBody.select("#state" + state).attr("d"), + id = "focusState" + state; cl.contains("inactive") ? fog(id, path) : unfog(id); cl.toggle("inactive"); } @@ -407,27 +457,35 @@ function editStates() { if (customization) return; alertMessage.innerHTML = "Are you sure you want to remove the state?
This action cannot be reverted"; - $("#alert").dialog({resizable: false, title: "Remove state", + $("#alert").dialog({ + resizable: false, + title: "Remove state", buttons: { - Remove: function() { + Remove: function () { $(this).dialog("close"); stateRemove(state); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } function stateRemove(state) { - statesBody.select("#state"+state).remove(); - statesBody.select("#state-gap"+state).remove(); - statesHalo.select("#state-border"+state).remove(); - labels.select("#stateLabel"+state).remove(); - defs.select("#textPath_stateLabel"+state).remove(); + statesBody.select("#state" + state).remove(); + statesBody.select("#state-gap" + state).remove(); + statesHalo.select("#state-border" + state).remove(); + labels.select("#stateLabel" + state).remove(); + defs.select("#textPath_stateLabel" + state).remove(); - unfog("focusState"+state); - pack.burgs.forEach(b => {if(b.state === state) b.state = 0;}); - pack.cells.state.forEach((s, i) => {if(s === state) pack.cells.state[i] = 0;}); + unfog("focusState" + state); + pack.burgs.forEach(b => { + if (b.state === state) b.state = 0; + }); + pack.cells.state.forEach((s, i) => { + if (s === state) pack.cells.state[i] = 0; + }); // remove emblem const coaId = "stateCOA" + state; @@ -437,13 +495,15 @@ function editStates() { // remove provinces pack.states[state].provinces.forEach(p => { pack.provinces[p] = {i: p, removed: true}; - pack.cells.province.forEach((pr, i) => {if(pr === p) pack.cells.province[i] = 0;}); + pack.cells.province.forEach((pr, i) => { + if (pr === p) pack.cells.province[i] = 0; + }); const coaId = "provinceCOA" + p; if (document.getElementById(coaId)) document.getElementById(coaId).remove(); emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove(); const g = provs.select("#provincesBody"); - g.select("#province"+p).remove(); - g.select("#province-gap"+p).remove(); + g.select("#province" + p).remove(); + g.select("#province-gap" + p).remove(); }); // remove military @@ -452,7 +512,7 @@ function editStates() { const index = notes.findIndex(n => n.id === id); if (index != -1) notes.splice(index, 1); }); - armies.select("g#army"+state).remove(); + armies.select("g#army" + state).remove(); const capital = pack.states[state].capital; pack.burgs[capital].capital = 0; @@ -462,15 +522,23 @@ function editStates() { pack.states[state] = {i: state, removed: true}; debug.selectAll(".highlight").remove(); - if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); + if (!layerIsOn("toggleStates")) toggleStates(); + else drawStates(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); if (layerIsOn("toggleProvinces")) drawProvinces(); refreshStatesEditor(); } function toggleLegend() { - if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend - const data = pack.states.filter(s => s.i && !s.removed && s.cells).sort((a, b) => b.area - a.area).map(s => [s.i, s.color, s.name]); + if (legend.selectAll("*").size()) { + clearLegend(); + return; + } // hide legend + const data = pack.states + .filter(s => s.i && !s.removed && s.cells) + .sort((a, b) => b.area - a.area) + .map(s => [s.i, s.color, s.name]); drawLegend("States", data); } @@ -482,11 +550,11 @@ function editStates() { const totalArea = +statesFooterArea.dataset.area; const totalPopulation = +statesFooterPopulation.dataset.population; - body.querySelectorAll(":scope > div").forEach(function(el) { - el.querySelector(".stateCells").innerHTML = rn(+el.dataset.cells / totalCells * 100) + "%"; - el.querySelector(".stateBurgs").innerHTML = rn(+el.dataset.burgs / totalBurgs * 100) + "%"; - el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100) + "%"; - el.querySelector(".culturePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100) + "%"; + body.querySelectorAll(":scope > div").forEach(function (el) { + el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100) + "%"; + el.querySelector(".stateBurgs").innerHTML = rn((+el.dataset.burgs / totalBurgs) * 100) + "%"; + el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%"; + el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%"; }); } else { body.dataset.type = "absolute"; @@ -497,10 +565,15 @@ function editStates() { function showStatesChart() { // build hierarchy tree const data = pack.states.filter(s => !s.removed); - const root = d3.stratify().id(d => d.i).parentId(d => d.i ? 0 : null)(data) - .sum(d => d.area).sort((a, b) => b.value - a.value); + const root = d3 + .stratify() + .id(d => d.i) + .parentId(d => (d.i ? 0 : null))(data) + .sum(d => d.area) + .sort((a, b) => b.value - a.value); - const width = 150 + 200 * uiSizeOutput.value, height = 150 + 200 * uiSizeOutput.value; + const width = 150 + 200 * uiSizeOutput.value, + height = 150 + 200 * uiSizeOutput.value; const margin = {top: 0, right: -50, bottom: 0, left: -50}; const w = width - margin.left - margin.right; const h = height - margin.top - margin.bottom; @@ -515,46 +588,51 @@ function editStates() { `; alertMessage.innerHTML += `
`; - const svg = d3.select("#alertMessage").insert("svg", "#statesInfo").attr("id", "statesTree") - .attr("width", width).attr("height", height).style("font-family", "Almendra SC") - .attr("text-anchor", "middle").attr("dominant-baseline", "central"); + const svg = d3.select("#alertMessage").insert("svg", "#statesInfo").attr("id", "statesTree").attr("width", width).attr("height", height).style("font-family", "Almendra SC").attr("text-anchor", "middle").attr("dominant-baseline", "central"); const graph = svg.append("g").attr("transform", `translate(-50, 0)`); document.getElementById("statesTreeType").addEventListener("change", updateChart); treeLayout(root); - const node = graph.selectAll("g").data(root.leaves()).enter() - .append("g").attr("transform", d => `translate(${d.x},${d.y})`) + const node = graph + .selectAll("g") + .data(root.leaves()) + .enter() + .append("g") + .attr("transform", d => `translate(${d.x},${d.y})`) .attr("data-id", d => d.data.i) .on("mouseenter", d => showInfo(event, d)) .on("mouseleave", d => hideInfo(event, d)); - node.append("circle").attr("fill", d => d.data.color).attr("r", d => d.r); + node + .append("circle") + .attr("fill", d => d.data.color) + .attr("r", d => d.r); const exp = /(?=[A-Z][^A-Z])/g; const lp = n => d3.max(n.split(exp).map(p => p.length)) + 1; // longest name part + 1 - node.append("text") - .style("font-size", d => rn(d.r ** .97 * 4 / lp(d.data.name), 2) + "px") - .selectAll("tspan").data(d => d.data.name.split(exp)) - .join("tspan").attr("x", 0).text(d => d) - .attr("dy", (d, i, n) => `${i ? 1 : (n.length-1) / -2}em`); + node + .append("text") + .style("font-size", d => rn((d.r ** 0.97 * 4) / lp(d.data.name), 2) + "px") + .selectAll("tspan") + .data(d => d.data.name.split(exp)) + .join("tspan") + .attr("x", 0) + .text(d => d) + .attr("dy", (d, i, n) => `${i ? 1 : (n.length - 1) / -2}em`); function showInfo(ev, d) { d3.select(ev.target).select("circle").classed("selected", 1); const state = d.data.fullName; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; - const area = d.data.area * (distanceScaleInput.value ** 2) + unit; - const rural = rn(d.data.rural * populationRate.value); - const urban = rn(d.data.urban * populationRate.value * urbanization.value); + const area = d.data.area * distanceScaleInput.value ** 2 + unit; + const rural = rn(d.data.rural * populationRate); + const urban = rn(d.data.urban * populationRate * urbanization); const option = statesTreeType.value; - const value = option === "area" ? "Area: " + area - : option === "rural" ? "Rural population: " + si(rural) - : option === "urban" ? "Urban population: " + si(urban) - : option === "burgs" ? "Burgs number: " + d.data.burgs - : "Population: " + si(rural + urban); + const value = option === "area" ? "Area: " + area : option === "rural" ? "Rural population: " + si(rural) : option === "urban" ? "Urban population: " + si(urban) : option === "burgs" ? "Burgs number: " + d.data.burgs : "Population: " + si(rural + urban); statesInfo.innerHTML = `${state}. ${value}`; stateHighlightOn(ev); @@ -568,30 +646,40 @@ function editStates() { } function updateChart() { - const value = this.value === "area" ? d => d.area - : this.value === "rural" ? d => d.rural - : this.value === "urban" ? d => d.urban - : this.value === "burgs" ? d => d.burgs - : d => d.rural + d.urban; + const value = this.value === "area" ? d => d.area : this.value === "rural" ? d => d.rural : this.value === "urban" ? d => d.urban : this.value === "burgs" ? d => d.burgs : d => d.rural + d.urban; root.sum(value); node.data(treeLayout(root).leaves()); - node.transition().duration(1500).attr("transform", d => `translate(${d.x},${d.y})`) - node.select("circle").transition().duration(1500).attr("r", d => d.r); - node.select("text").transition().duration(1500) - .style("font-size", d => rn(d.r ** .97 * 4 / lp(d.data.name), 2) + "px"); + node + .transition() + .duration(1500) + .attr("transform", d => `translate(${d.x},${d.y})`); + node + .select("circle") + .transition() + .duration(1500) + .attr("r", d => d.r); + node + .select("text") + .transition() + .duration(1500) + .style("font-size", d => rn((d.r ** 0.97 * 4) / lp(d.data.name), 2) + "px"); } $("#alert").dialog({ - title: "States bubble chart", width: fitContent(), - position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, buttons: {}, - close: () => {alertMessage.innerHTML = "";} + title: "States bubble chart", + width: fitContent(), + position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, + buttons: {}, + close: () => { + alertMessage.innerHTML = ""; + } }); } function openRegenerationMenu() { - statesBottom.querySelectorAll(":scope > button").forEach(el => el.style.display = "none"); + statesBottom.querySelectorAll(":scope > button").forEach(el => (el.style.display = "none")); statesRegenerateButtons.style.display = "block"; statesEditor.querySelectorAll(".show").forEach(el => el.classList.remove("hidden")); @@ -603,8 +691,10 @@ function editStates() { BurgsAndStates.expandStates(); BurgsAndStates.generateProvinces(); - if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); + if (!layerIsOn("toggleStates")) toggleStates(); + else drawStates(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); if (layerIsOn("toggleProvinces")) drawProvinces(); if (adjustLabels.checked) BurgsAndStates.drawStateLabels(); refreshStatesEditor(); @@ -615,13 +705,13 @@ function editStates() { if (!s.i || s.removed) return; const expansionism = rn(Math.random() * 4 + 1, 1); s.expansionism = expansionism; - body.querySelector("div.states[data-id='"+s.i+"'] > input.statePower").value = expansionism; + body.querySelector("div.states[data-id='" + s.i + "'] > input.statePower").value = expansionism; }); recalculateStates(true, true); } function exitRegenerationMenu() { - statesBottom.querySelectorAll(":scope > button").forEach(el => el.style.display = "inline-block"); + statesBottom.querySelectorAll(":scope > button").forEach(el => (el.style.display = "inline-block")); statesRegenerateButtons.style.display = "none"; statesEditor.querySelectorAll(".show").forEach(el => el.classList.add("hidden")); $("#statesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); @@ -631,20 +721,17 @@ function editStates() { if (!layerIsOn("toggleStates")) toggleStates(); customization = 2; statesBody.append("g").attr("id", "temp"); - document.querySelectorAll("#statesBottom > button").forEach(el => el.style.display = "none"); + document.querySelectorAll("#statesBottom > button").forEach(el => (el.style.display = "none")); document.getElementById("statesManuallyButtons").style.display = "inline-block"; document.getElementById("statesHalo").style.display = "none"; statesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); statesFooter.style.display = "none"; - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none")); $("#statesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); tip("Click on state to select, drag the circle to change state", true); - viewbox.style("cursor", "crosshair") - .on("click", selectStateOnMapClick) - .call(d3.drag().on("start", dragStateBrush)) - .on("touchmove mousemove", moveStateBrush); + viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick).call(d3.drag().on("start", dragStateBrush)).on("touchmove mousemove", moveStateBrush); body.querySelector("div").classList.add("selected"); } @@ -661,11 +748,11 @@ function editStates() { const i = findCell(point[0], point[1]); if (pack.cells.h[i] < 20) return; - const assigned = statesBody.select("#temp").select("polygon[data-cell='"+i+"']"); + const assigned = statesBody.select("#temp").select("polygon[data-cell='" + i + "']"); const state = assigned.size() ? +assigned.attr("data-state") : pack.cells.state[i]; body.querySelector("div.selected").classList.remove("selected"); - body.querySelector("div[data-id='"+state+"']").classList.add("selected"); + body.querySelector("div[data-id='" + state + "']").classList.add("selected"); } function dragStateBrush() { @@ -690,8 +777,8 @@ function editStates() { const stateNew = +selected.dataset.id; const color = pack.states[stateNew].color || "#ffffff"; - selection.forEach(function(i) { - const exists = temp.select("polygon[data-cell='"+i+"']"); + selection.forEach(function (i) { + const exists = temp.select("polygon[data-cell='" + i + "']"); const stateOld = exists.size() ? +exists.attr("data-state") : pack.cells.state[i]; if (stateNew === stateOld) return; if (i === pack.states[stateOld].center) return; @@ -710,31 +797,40 @@ function editStates() { } function applyStatesManualAssignent() { - const cells = pack.cells, affectedStates = [], affectedProvinces = []; + const cells = pack.cells, + affectedStates = [], + affectedProvinces = []; - statesBody.select("#temp").selectAll("polygon").each(function() { - const i = +this.dataset.cell; - const c = +this.dataset.state; - affectedStates.push(cells.state[i], c); - affectedProvinces.push(cells.province[i]); - cells.state[i] = c; - if (cells.burg[i]) pack.burgs[cells.burg[i]].state = c; - }); + statesBody + .select("#temp") + .selectAll("polygon") + .each(function () { + const i = +this.dataset.cell; + const c = +this.dataset.state; + affectedStates.push(cells.state[i], c); + affectedProvinces.push(cells.province[i]); + cells.state[i] = c; + if (cells.burg[i]) pack.burgs[cells.burg[i]].state = c; + }); if (affectedStates.length) { refreshStatesEditor(); - if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); + if (!layerIsOn("toggleStates")) toggleStates(); + else drawStates(); if (adjustLabels.checked) BurgsAndStates.drawStateLabels([...new Set(affectedStates)]); adjustProvinces([...new Set(affectedProvinces)]); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); if (layerIsOn("toggleProvinces")) drawProvinces(); } exitStatesManualAssignment(); } function adjustProvinces(affectedProvinces) { - const cells = pack.cells, provinces = pack.provinces, states = pack.states; - const form = {"Zone":1, "Area":1, "Territory":2, "Province":1}; + const cells = pack.cells, + provinces = pack.provinces, + states = pack.states; + const form = {Zone: 1, Area: 1, Territory: 2, Province: 1}; affectedProvinces.forEach(p => { // do nothing if neutral lands are captured @@ -757,7 +853,7 @@ function editStates() { const part = states[owner].provinces.find(n => name.includes(provinces[n].name)); if (part) { provinces[p].removed = true; - provCells.filter(i => cells.state[i] === owner).forEach(i => cells.province[i] = part); + provCells.filter(i => cells.state[i] === owner).forEach(i => (cells.province[i] = part)); } else { provinces[p].state = owner; states[owner].provinces.push(p); @@ -765,43 +861,49 @@ function editStates() { } } else { provinces[p].removed = true; - provCells.filter(i => !cells.state[i]).forEach(i => cells.province[i] = 0); + provCells.filter(i => !cells.state[i]).forEach(i => (cells.province[i] = 0)); } // create new provinces for non-main part - provStates.filter(s => s && s !== owner).forEach(s => createProvince(p, s, provCells.filter(i => cells.state[i] === s))); + provStates + .filter(s => s && s !== owner) + .forEach(s => + createProvince( + p, + s, + provCells.filter(i => cells.state[i] === s) + ) + ); }); function createProvince(initProv, state, provCells) { const province = provinces.length; - provCells.forEach(i => cells.province[i] = province); + provCells.forEach(i => (cells.province[i] = province)); const burgCell = provCells.find(i => cells.burg[i]); const center = burgCell ? burgCell : provCells[0]; const burg = burgCell ? cells.burg[burgCell] : 0; - const name = burgCell && P(.7) ? getAdjective(pack.burgs[burg].name) - : getAdjective(states[state].name) + " " + provinces[initProv].name.split(" ").slice(-1)[0]; + const name = burgCell && P(0.7) ? getAdjective(pack.burgs[burg].name) : getAdjective(states[state].name) + " " + provinces[initProv].name.split(" ").slice(-1)[0]; const formName = name.split(" ").length > 1 ? provinces[initProv].formName : rw(form); const fullName = name + " " + formName; const color = getMixedColor(states[state].color); - provinces.push({i:province, state, center, burg, name, formName, fullName, color}); + provinces.push({i: province, state, center, burg, name, formName, fullName, color}); } - } - + function exitStatesManualAssignment(close) { customization = 0; statesBody.select("#temp").remove(); removeCircle(); - document.querySelectorAll("#statesBottom > button").forEach(el => el.style.display = "inline-block"); + document.querySelectorAll("#statesBottom > button").forEach(el => (el.style.display = "inline-block")); document.getElementById("statesManuallyButtons").style.display = "none"; document.getElementById("statesHalo").style.display = "block"; statesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden")); statesFooter.style.display = "block"; - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); - if(!close) $("#statesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); + if (!close) $("#statesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); restoreDefaultEvents(); clearMainTip(); @@ -810,21 +912,32 @@ function editStates() { } function enterAddStateMode() { - if (this.classList.contains("pressed")) {exitAddStateMode(); return;}; + if (this.classList.contains("pressed")) { + exitAddStateMode(); + return; + } customization = 3; this.classList.add("pressed"); tip("Click on the map to create a new capital or promote an existing burg", true); viewbox.style("cursor", "crosshair").on("click", addState); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none")); } function addState() { - const states = pack.states, burgs = pack.burgs, cells = pack.cells; + const states = pack.states, + burgs = pack.burgs, + cells = pack.cells; const point = d3.mouse(this); const center = findCell(point[0], point[1]); - if (cells.h[center] < 20) {tip("You cannot place state into the water. Please click on a land cell", false, "error"); return;} + if (cells.h[center] < 20) { + tip("You cannot place state into the water. Please click on a land cell", false, "error"); + return; + } let burg = cells.burg[center]; - if (burg && burgs[burg].capital) {tip("Existing capital cannot be selected as a new state capital! Select other cell", false, "error"); return;} + if (burg && burgs[burg].capital) { + tip("Existing capital cannot be selected as a new state capital! Select other cell", false, "error"); + return; + } if (!burg) burg = addBurg(point); // add new burg const oldState = cells.state[center]; @@ -838,14 +951,14 @@ function editStates() { if (d3.event.shiftKey === false) exitAddStateMode(); const culture = cells.culture[center]; - const basename = center%5 === 0 ? burgs[burg].name : Names.getCulture(culture); + const basename = center % 5 === 0 ? burgs[burg].name : Names.getCulture(culture); const name = Names.getState(basename, culture); const color = getRandomColor(); const pole = cells.p[center]; // generate emblem const cultureType = pack.cultures[culture].type; - const coa = COA.generate(burgs[burg].coa, .4, null, cultureType); + const coa = COA.generate(burgs[burg].coa, 0.4, null, cultureType); coa.shield = COA.getShield(culture, null); // update diplomacy and reverse relations @@ -857,7 +970,8 @@ function editStates() { } let relations = states[oldState].diplomacy[s.i]; // relations between Nth state and old overlord - if (s.i === oldState) relations = "Enemy"; // new state is Enemy to its old overlord + if (s.i === oldState) relations = "Enemy"; + // new state is Enemy to its old overlord else if (relations === "Ally") relations = "Suspicion"; else if (relations === "Friendly") relations = "Suspicion"; else if (relations === "Suspicion") relations = "Neutral"; @@ -874,21 +988,34 @@ function editStates() { cells.state[center] = newState; cells.province[center] = 0; - states.push({i:newState, name, diplomacy, provinces:[], color, expansionism:.5, capital:burg, type:"Generic", center, culture, military:[], alert:1, coa, pole}); + states.push({i: newState, name, diplomacy, provinces: [], color, expansionism: 0.5, capital: burg, type: "Generic", center, culture, military: [], alert: 1, coa, pole}); BurgsAndStates.collectStatistics(); BurgsAndStates.defineStateForms([newState]); adjustProvinces([cells.province[center]]); if (layerIsOn("toggleProvinces")) toggleProvinces(); - if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); + if (!layerIsOn("toggleStates")) toggleStates(); + else drawStates(); + if (!layerIsOn("toggleBorders")) toggleBorders(); + else drawBorders(); // add label - defs.select("#textPaths").append("path").attr("d", `M${pole[0]-50},${pole[1]+6}h${100}`).attr("id", "textPath_stateLabel"+newState); - labels.select("#states") - .append("text").attr("id", "stateLabel"+newState) - .append("textPath").attr("xlink:href", "#textPath_stateLabel"+newState).attr("startOffset", "50%").attr("font-size", "50%") - .append("tspan").attr("x", name.length * -3).text(name); + defs + .select("#textPaths") + .append("path") + .attr("d", `M${pole[0] - 50},${pole[1] + 6}h${100}`) + .attr("id", "textPath_stateLabel" + newState); + labels + .select("#states") + .append("text") + .attr("id", "stateLabel" + newState) + .append("textPath") + .attr("xlink:href", "#textPath_stateLabel" + newState) + .attr("startOffset", "50%") + .attr("font-size", "50%") + .append("tspan") + .attr("x", name.length * -3) + .text(name); COArenderer.add("state", newState, coa, states[newState].pole[0], states[newState].pole[1]); statesEditorAddLines(); @@ -898,15 +1025,15 @@ function editStates() { customization = 0; restoreDefaultEvents(); clearMainTip(); - body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all"); + body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all")); if (statesAdd.classList.contains("pressed")) statesAdd.classList.remove("pressed"); } function downloadStatesData() { const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; - let data = "Id,State,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area "+unit+",Total Population,Rural Population,Urban Population\n"; // headers + let data = "Id,State,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers - body.querySelectorAll(":scope > div").forEach(function(el) { + body.querySelectorAll(":scope > div").forEach(function (el) { const key = parseInt(el.dataset.id); data += el.dataset.id + ","; data += el.dataset.name + ","; @@ -920,8 +1047,8 @@ function editStates() { data += el.dataset.burgs + ","; data += el.dataset.area + ","; data += el.dataset.population + ","; - data += `${Math.round(pack.states[key].rural*populationRate.value)},`; - data += `${Math.round(pack.states[key].urban*populationRate.value * urbanization.value)}\n`; + data += `${Math.round(pack.states[key].rural * populationRate)},`; + data += `${Math.round(pack.states[key].urban * populationRate * urbanization)}\n`; }); const name = getFileName("States") + ".csv"; diff --git a/modules/ui/units-editor.js b/modules/ui/units-editor.js index fe12ecf9..54571191 100644 --- a/modules/ui/units-editor.js +++ b/modules/ui/units-editor.js @@ -28,9 +28,9 @@ function editUnits() { document.getElementById("barBackColor").addEventListener("input", changeScaleBarColor); document.getElementById("populationRateOutput").addEventListener("input", changePopulationRate); - document.getElementById("populationRate").addEventListener("change", changePopulationRate); + document.getElementById("populationRateInput").addEventListener("change", changePopulationRate); document.getElementById("urbanizationOutput").addEventListener("input", changeUrbanizationRate); - document.getElementById("urbanization").addEventListener("change", changeUrbanizationRate); + document.getElementById("urbanizationInput").addEventListener("change", changeUrbanizationRate); document.getElementById("addLinearRuler").addEventListener("click", addRuler); document.getElementById("addOpisometer").addEventListener("click", toggleOpisometerMode); @@ -86,13 +86,11 @@ function editUnits() { } function changePopulationRate() { - document.getElementById("populationRateOutput").value = this.value; - document.getElementById("populationRate").value = this.value; + populationRate = +this.value; } function changeUrbanizationRate() { - document.getElementById("urbanizationOutput").value = this.value; - document.getElementById("urbanization").value = this.value; + urbanization = +this.value; } function restoreDefaultUnits() { @@ -135,8 +133,8 @@ function editUnits() { drawScaleBar(); // population - populationRateOutput.value = populationRate.value = 1000; - urbanizationOutput.value = urbanization.value = 1; + populationRate = populationRateOutput.value = populationRateInput.value = 1000; + urbanization = urbanizationOutput.value = urbanizationInput.value = 1; localStorage.removeItem("populationRate"); localStorage.removeItem("urbanization"); } diff --git a/modules/ui/zones-editor.js b/modules/ui/zones-editor.js index 7e751b7c..a27b1f0f 100644 --- a/modules/ui/zones-editor.js +++ b/modules/ui/zones-editor.js @@ -9,7 +9,10 @@ function editZones() { modules.editZones = true; $("#zonesEditor").dialog({ - title: "Zones Editor", resizable: false, width: fitContent(), close: () => exitZonesManualAssignment("close"), + title: "Zones Editor", + resizable: false, + width: fitContent(), + close: () => exitZonesManualAssignment("close"), position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -25,19 +28,37 @@ function editZones() { document.getElementById("zonesExport").addEventListener("click", downloadZonesData); document.getElementById("zonesRemove").addEventListener("click", toggleEraseMode); - body.addEventListener("click", function(ev) { - const el = ev.target, cl = el.classList, zone = el.parentNode.dataset.id; - if (cl.contains("culturePopulation")) {changePopulation(zone); return;} - if (cl.contains("icon-trash-empty")) {zoneRemove(zone); return;} - if (cl.contains("icon-eye")) {toggleVisibility(el); return;} - if (cl.contains("icon-pin")) {toggleFog(zone, cl); return;} - if (cl.contains("fillRect")) {changeFill(el); return;} + body.addEventListener("click", function (ev) { + const el = ev.target, + cl = el.classList, + zone = el.parentNode.dataset.id; + if (cl.contains("culturePopulation")) { + changePopulation(zone); + return; + } + if (cl.contains("icon-trash-empty")) { + zoneRemove(zone); + return; + } + if (cl.contains("icon-eye")) { + toggleVisibility(el); + return; + } + if (cl.contains("icon-pin")) { + toggleFog(zone, cl); + return; + } + if (cl.contains("fillRect")) { + changeFill(el); + return; + } if (customization) selectZone(el); }); - body.addEventListener("input", function(ev) { - const el = ev.target, zone = el.parentNode.dataset.id; - if (el.classList.contains("religionName")) zones.select("#"+zone).attr("data-description", el.value); + body.addEventListener("input", function (ev) { + const el = ev.target, + zone = el.parentNode.dataset.id; + if (el.classList.contains("religionName")) zones.select("#" + zone).attr("data-description", el.value); }); // add line for each zone @@ -45,17 +66,17 @@ function editZones() { const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; let lines = ""; - zones.selectAll("g").each(function() { + zones.selectAll("g").each(function () { const c = this.dataset.cells ? this.dataset.cells.split(",").map(c => +c) : []; const description = this.dataset.description; const fill = this.getAttribute("fill"); - const area = d3.sum(c.map(i => pack.cells.area[i])) * (distanceScaleInput.value ** 2); - const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate.value; - const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value; + const area = d3.sum(c.map(i => pack.cells.area[i])) * distanceScaleInput.value ** 2; + const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate; + const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization; const population = rural + urban; const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`; const inactive = this.style.display === "none"; - const focused = defs.select("#fog #focus"+this.id).size(); + const focused = defs.select("#fog #focus" + this.id).size(); lines += `
@@ -67,8 +88,8 @@ function editZones() {
${si(population)}
- - + +
`; }); @@ -76,8 +97,8 @@ function editZones() { body.innerHTML = lines; // update footer - const totalArea = zonesFooterArea.dataset.area = graphWidth * graphHeight * (distanceScaleInput.value ** 2); - const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization.value) * populationRate.value; + const totalArea = (zonesFooterArea.dataset.area = graphWidth * graphHeight * distanceScaleInput.value ** 2); + const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate; zonesFooterPopulation.dataset.population = totalPop; zonesFooterNumber.innerHTML = zones.selectAll("g").size(); zonesFooterCells.innerHTML = pack.cells.i.length; @@ -88,48 +109,53 @@ function editZones() { body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => zoneHighlightOn(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => zoneHighlightOff(ev))); - if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} + if (body.dataset.type === "percentage") { + body.dataset.type = "absolute"; + togglePercentageMode(); + } $("#zonesEditor").dialog({width: fitContent()}); } function zoneHighlightOn(event) { const zone = event.target.dataset.id; - zones.select("#"+zone).style("outline", "1px solid red"); + zones.select("#" + zone).style("outline", "1px solid red"); } function zoneHighlightOff(event) { const zone = event.target.dataset.id; - zones.select("#"+zone).style("outline", null); + zones.select("#" + zone).style("outline", null); } $(body).sortable({items: "div.states", handle: ".icon-resize-vertical", containment: "parent", axis: "y", update: movezone}); function movezone(ev, ui) { - const zone = $("#"+ui.item.attr("data-id")); - const prev = $("#"+ui.item.prev().attr("data-id")); - if (prev) {zone.insertAfter(prev); return;} - const next = $("#"+ui.item.next().attr("data-id")); + const zone = $("#" + ui.item.attr("data-id")); + const prev = $("#" + ui.item.prev().attr("data-id")); + if (prev) { + zone.insertAfter(prev); + return; + } + const next = $("#" + ui.item.next().attr("data-id")); if (next) zone.insertBefore(next); } function enterZonesManualAssignent() { if (!layerIsOn("toggleZones")) toggleZones(); customization = 10; - document.querySelectorAll("#zonesBottom > button").forEach(el => el.style.display = "none"); + document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "none")); document.getElementById("zonesManuallyButtons").style.display = "inline-block"; zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); zonesFooter.style.display = "none"; - body.querySelectorAll("div > input, select, svg").forEach(e => e.style.pointerEvents = "none"); + body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "none")); $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); tip("Click to select a zone, drag to paint a zone", true); - viewbox.style("cursor", "crosshair") - .on("click", selectZoneOnMapClick) - .call(d3.drag().on("start", dragZoneBrush)) - .on("touchmove mousemove", moveZoneBrush); + viewbox.style("cursor", "crosshair").on("click", selectZoneOnMapClick).call(d3.drag().on("start", dragZoneBrush)).on("touchmove mousemove", moveZoneBrush); body.querySelector("div").classList.add("selected"); - zones.selectAll("g").each(function() {this.setAttribute("data-init", this.getAttribute("data-cells"));}); + zones.selectAll("g").each(function () { + this.setAttribute("data-init", this.getAttribute("data-cells")); + }); } function selectZone(el) { @@ -154,9 +180,9 @@ function editZones() { const selection = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1], r)]; if (!selection) return; - + const selected = body.querySelector("div.selected"); - const zone = zones.select("#"+selected.dataset.id); + const zone = zones.select("#" + selected.dataset.id); const base = zone.attr("id") + "_"; // id generic part const dataCells = zone.attr("data-cells"); let cells = dataCells ? dataCells.split(",").map(i => +i) : []; @@ -175,7 +201,10 @@ function editZones() { selection.forEach(i => { if (cells.includes(i)) return; cells.push(i); - zone.append("polygon").attr("points", getPackPolygon(i)).attr("id", base + i); + zone + .append("polygon") + .attr("points", getPackPolygon(i)) + .attr("id", base + i); }); } @@ -191,10 +220,10 @@ function editZones() { } function applyZonesManualAssignent() { - zones.selectAll("g").each(function() { + zones.selectAll("g").each(function () { if (this.dataset.cells) return; // all zone cells are removed - unfog("focusZone"+this.id); + unfog("focusZone" + this.id); this.style.display = "block"; }); @@ -204,15 +233,20 @@ function editZones() { // restore initial zone cells function cancelZonesManualAssignent() { - zones.selectAll("g").each(function() { + zones.selectAll("g").each(function () { const zone = d3.select(this); const dataCells = zone.attr("data-init"); const cells = dataCells ? dataCells.split(",").map(i => +i) : []; zone.attr("data-cells", cells); zone.selectAll("*").remove(); const base = zone.attr("id") + "_"; // id generic part - zone.selectAll("*").data(cells).enter().append("polygon") - .attr("points", d => getPackPolygon(d)).attr("id", d => base + d); + zone + .selectAll("*") + .data(cells) + .enter() + .append("polygon") + .attr("points", d => getPackPolygon(d)) + .attr("id", d => base + d); }); exitZonesManualAssignment(); @@ -221,56 +255,68 @@ function editZones() { function exitZonesManualAssignment(close) { customization = 0; removeCircle(); - document.querySelectorAll("#zonesBottom > button").forEach(el => el.style.display = "inline-block"); + document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "inline-block")); document.getElementById("zonesManuallyButtons").style.display = "none"; zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden")); zonesFooter.style.display = "block"; - body.querySelectorAll("div > input, select, svg").forEach(e => e.style.pointerEvents = "all"); - if(!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); + body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "all")); + if (!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}}); restoreDefaultEvents(); clearMainTip(); - zones.selectAll("g").each(function() {this.removeAttribute("data-init");}); + zones.selectAll("g").each(function () { + this.removeAttribute("data-init"); + }); const selected = body.querySelector("div.selected"); if (selected) selected.classList.remove("selected"); } function changeFill(el) { const fill = el.getAttribute("fill"); - const callback = function(fill) { + const callback = function (fill) { el.setAttribute("fill", fill); document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill); - } + }; openPicker(fill, callback); } function toggleVisibility(el) { - const zone = zones.select("#"+el.parentNode.dataset.id); + const zone = zones.select("#" + el.parentNode.dataset.id); const inactive = zone.style("display") === "none"; inactive ? zone.style("display", "block") : zone.style("display", "none"); el.classList.toggle("inactive"); } function toggleFog(z, cl) { - const dataCells = zones.select("#"+z).attr("data-cells"); + const dataCells = zones.select("#" + z).attr("data-cells"); if (!dataCells) return; - const path = "M" + dataCells.split(",").map(c => getPackPolygon(+c)).join("M") + "Z", id = "focusZone"+z; + const path = + "M" + + dataCells + .split(",") + .map(c => getPackPolygon(+c)) + .join("M") + + "Z", + id = "focusZone" + z; cl.contains("inactive") ? fog(id, path) : unfog(id); cl.toggle("inactive"); } function toggleLegend() { - if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend + if (legend.selectAll("*").size()) { + clearLegend(); + return; + } // hide legend const data = []; - zones.selectAll("g").each(function() { + zones.selectAll("g").each(function () { const id = this.dataset.id; const description = this.dataset.description; const fill = this.getAttribute("fill"); - data.push([id, fill, description]) + data.push([id, fill, description]); }); drawLegend("Zones", data); @@ -283,12 +329,11 @@ function editZones() { const totalArea = +zonesFooterArea.dataset.area; const totalPopulation = +zonesFooterPopulation.dataset.population; - body.querySelectorAll(":scope > div").forEach(function(el) { - el.querySelector(".stateCells").innerHTML = rn(+el.dataset.cells / totalCells * 100, 2) + "%"; - el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100, 2) + "%"; - el.querySelector(".culturePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100, 2) + "%"; + body.querySelectorAll(":scope > div").forEach(function (el) { + el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + "%"; + el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100, 2) + "%"; + el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + "%"; }); - } else { body.dataset.type = "absolute"; zonesEditorAddLines(); @@ -298,7 +343,7 @@ function editZones() { function addZonesLayer() { const id = getNextId("zone"); const description = "Unknown zone"; - const fill = "url(#hatch" + id.slice(4)%14 + ")"; + const fill = "url(#hatch" + (id.slice(4) % 14) + ")"; zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill); const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; @@ -323,9 +368,9 @@ function editZones() { function downloadZonesData() { const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; - let data = "Id,Fill,Description,Cells,Area "+unit+",Population\n"; // headers + let data = "Id,Fill,Description,Cells,Area " + unit + ",Population\n"; // headers - body.querySelectorAll(":scope > div").forEach(function(el) { + body.querySelectorAll(":scope > div").forEach(function (el) { data += el.dataset.id + ","; data += el.dataset.fill + ","; data += el.dataset.description + ","; @@ -343,68 +388,83 @@ function editZones() { } function changePopulation(zone) { - const dataCells = zones.select("#"+zone).attr("data-cells"); - const cells = dataCells ? dataCells.split(",").map(i => +i).filter(i => pack.cells.h[i] >= 20) : []; - if (!cells.length) {tip("Zone does not have any land cells, cannot change population", false, "error"); return;} + const dataCells = zones.select("#" + zone).attr("data-cells"); + const cells = dataCells + ? dataCells + .split(",") + .map(i => +i) + .filter(i => pack.cells.h[i] >= 20) + : []; + if (!cells.length) { + tip("Zone does not have any land cells, cannot change population", false, "error"); + return; + } const burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell)); - const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate.value); - const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value); + const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate); + const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization); const total = rural + urban; const l = n => Number(n).toLocaleString(); alertMessage.innerHTML = ` Rural: - Urban: + Urban:

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

`; - const update = function() { + const update = function () { const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; if (isNaN(totalNew)) return; totalPop.innerHTML = l(totalNew); - totalPopPerc.innerHTML = rn(totalNew / total * 100); - } + totalPopPerc.innerHTML = rn((totalNew / total) * 100); + }; ruralPop.oninput = () => update(); urbanPop.oninput = () => update(); $("#alert").dialog({ - resizable: false, title: "Change zone population", width: "24em", buttons: { - Apply: function() {applyPopulationChange(); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Change zone population", + width: "24em", + buttons: { + Apply: function () { + applyPopulationChange(); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); function applyPopulationChange() { const ruralChange = ruralPop.value / rural; if (isFinite(ruralChange) && ruralChange !== 1) { - cells.forEach(i => pack.cells.pop[i] *= ruralChange); + cells.forEach(i => (pack.cells.pop[i] *= ruralChange)); } if (!isFinite(ruralChange) && +ruralPop.value > 0) { - const points = ruralPop.value / populationRate.value; + const points = ruralPop.value / populationRate; const pop = rn(points / cells.length); - cells.forEach(i => pack.cells.pop[i] = pop); + cells.forEach(i => (pack.cells.pop[i] = pop)); } const urbanChange = urbanPop.value / urban; if (isFinite(urbanChange) && urbanChange !== 1) { - burgs.forEach(b => b.population = rn(b.population * urbanChange, 4)); + burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4))); } if (!isFinite(urbanChange) && +urbanPop.value > 0) { - const points = urbanPop.value / populationRate.value / urbanization.value; + const points = urbanPop.value / populationRate / urbanization; const population = rn(points / burgs.length, 4); - burgs.forEach(b => b.population = population); + burgs.forEach(b => (b.population = population)); } zonesEditorAddLines(); } - } function zoneRemove(zone) { - zones.select("#"+zone).remove(); - unfog("focusZone"+zone); + zones.select("#" + zone).remove(); + unfog("focusZone" + zone); zonesEditorAddLines(); } - }