From 95928b44f871e37d785000a3ea63b05fad9b1ca8 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 2 May 2021 11:11:46 +0300 Subject: [PATCH 01/34] suppoters update --- modules/ui/options.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui/options.js b/modules/ui/options.js index 888d22fb..798493a7 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -91,7 +91,8 @@ function showSupporters() { Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,Radovan Zapletal,Jmmat6, Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,Guilherme Aguiar,Jarno Hallikainen, Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,Cooper Counts,Patrick Jones,Clonetone, - PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram`; + PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram, + Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore`; const array = supporters.replace(/(?:\r\n|\r|\n)/g, "").split(",").map(v => capitalize(v.trim())).sort(); alertMessage.innerHTML = ""; From ab065da5d27f2e61d0bb98fb3a09982ee62f72a3 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 15 May 2021 17:41:31 +0300 Subject: [PATCH 02/34] suppoters update --- modules/ui/options.js | 335 +++++++++++++++++++++++++++--------------- 1 file changed, 219 insertions(+), 116 deletions(-) diff --git a/modules/ui/options.js b/modules/ui/options.js index 798493a7..48efa169 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -40,34 +40,38 @@ function toggleOptions(event) { } // Toggle "New Map!" pane on hover -optionsTrigger.addEventListener("mouseenter", function() { +optionsTrigger.addEventListener("mouseenter", function () { if (optionsTrigger.classList.contains("glow")) return; if (document.getElementById("options").style.display === "none") regenerate.style.display = "block"; }); -collapsible.addEventListener("mouseleave", function() { +collapsible.addEventListener("mouseleave", function () { regenerate.style.display = "none"; }); // Activate options tab on click -document.getElementById("options").querySelector("div.tab").addEventListener("click", function(event) { - if (event.target.tagName !== "BUTTON") return; - const id = event.target.id; - const active = document.getElementById("options").querySelector(".tab > button.active"); - if (active && id === active.id) return; // already active tab is clicked +document + .getElementById("options") + .querySelector("div.tab") + .addEventListener("click", function (event) { + if (event.target.tagName !== "BUTTON") return; + const id = event.target.id; + const active = document.getElementById("options").querySelector(".tab > button.active"); + if (active && id === active.id) return; // already active tab is clicked - if (active) active.classList.remove("active"); - document.getElementById(id).classList.add("active"); - document.getElementById("options").querySelectorAll(".tabcontent").forEach(e => e.style.display = "none"); + if (active) active.classList.remove("active"); + document.getElementById(id).classList.add("active"); + document + .getElementById("options") + .querySelectorAll(".tabcontent") + .forEach(e => (e.style.display = "none")); - if (id === "layersTab") layersContent.style.display = "block"; else - if (id === "styleTab") styleContent.style.display = "block"; else - if (id === "optionsTab") optionsContent.style.display = "block"; else - if (id === "toolsTab") customization === 1 - ? customizationMenu.style.display = "block" - : toolsContent.style.display = "block"; else - if (id === "aboutTab") aboutContent.style.display = "block"; -}); + if (id === "layersTab") layersContent.style.display = "block"; + else if (id === "styleTab") styleContent.style.display = "block"; + else if (id === "optionsTab") optionsContent.style.display = "block"; + else if (id === "toolsTab") customization === 1 ? (customizationMenu.style.display = "block") : (toolsContent.style.display = "block"); + else if (id === "aboutTab") aboutContent.style.display = "block"; + }); // show popup with a list of Patreon supportes (updated manually, to be replaced with API call) function showSupporters() { @@ -92,17 +96,22 @@ function showSupporters() { Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,Guilherme Aguiar,Jarno Hallikainen, Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,Cooper Counts,Patrick Jones,Clonetone, PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram, - Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore`; + Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee`; - const array = supporters.replace(/(?:\r\n|\r|\n)/g, "").split(",").map(v => capitalize(v.trim())).sort(); + const array = supporters + .replace(/(?:\r\n|\r|\n)/g, "") + .split(",") + .map(v => capitalize(v.trim())) + .sort(); alertMessage.innerHTML = ""; - $("#alert").dialog({resizable: false,title: "Patreon Supporters",width: "54vw",position: {my: "center",at: "center",of: "svg"}}); + $("#alert").dialog({resizable: false, title: "Patreon Supporters", width: "54vw", position: {my: "center", at: "center", of: "svg"}}); } // Option listeners const optionsContent = document.getElementById("optionsContent"); -optionsContent.addEventListener("input", function(event) { - const id = event.target.id, value = event.target.value; +optionsContent.addEventListener("input", function (event) { + const id = event.target.id, + value = event.target.value; if (id === "mapWidthInput" || id === "mapHeightInput") mapSizeInputChange(); else if (id === "pointsInput") changeCellsDensity(+value); else if (id === "culturesInput") culturesOutput.value = value; @@ -123,9 +132,10 @@ optionsContent.addEventListener("input", function(event) { else if (id === "transparencyInput") changeDialogsTransparency(value); }); -optionsContent.addEventListener("change", function(event) { +optionsContent.addEventListener("change", function (event) { if (event.target.dataset.stored) lock(event.target.dataset.stored); - const id = event.target.id, value = event.target.value; + const id = event.target.id, + value = event.target.value; if (id === "zoomExtentMin" || id === "zoomExtentMax") changeZoomExtent(value); else if (id === "optionsSeed") generateMapWithSeed(); else if (id === "uiSizeInput" || id === "uiSizeOutput") changeUIsize(value); @@ -133,7 +143,7 @@ optionsContent.addEventListener("change", function(event) { else if (id === "eraInput") changeEra(); }); -optionsContent.addEventListener("click", function(event) { +optionsContent.addEventListener("click", function (event) { const id = event.target.id; if (id === "toggleFullscreen") toggleFullscreen(); else if (id === "optionsSeedGenerate") generateMapWithSeed(); @@ -159,7 +169,10 @@ function changeMapSize() { const maxWidth = Math.max(+mapWidthInput.value, graphWidth); const maxHeight = Math.max(+mapHeightInput.value, graphHeight); - zoom.translateExtent([[0, 0], [maxWidth, maxHeight]]); + zoom.translateExtent([ + [0, 0], + [maxWidth, maxHeight] + ]); landmass.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); @@ -173,13 +186,20 @@ function changeMapSize() { // just apply canvas size that was already set function applyMapSize() { - const zoomMin = +zoomExtentMin.value, zoomMax = +zoomExtentMax.value; + const zoomMin = +zoomExtentMin.value, + zoomMax = +zoomExtentMax.value; graphWidth = +mapWidthInput.value; graphHeight = +mapHeightInput.value; svgWidth = Math.min(graphWidth, window.innerWidth); svgHeight = Math.min(graphHeight, window.innerHeight); svg.attr("width", svgWidth).attr("height", svgHeight); - zoom.translateExtent([[0, 0], [graphWidth, graphHeight]]).scaleExtent([zoomMin, zoomMax]).scaleTo(svg, zoomMin); + zoom + .translateExtent([ + [0, 0], + [graphWidth, graphHeight] + ]) + .scaleExtent([zoomMin, zoomMax]) + .scaleTo(svg, zoomMin); } function toggleFullscreen() { @@ -196,21 +216,31 @@ function toggleFullscreen() { } function toggleTranslateExtent(el) { - const on = el.dataset.on = +!(+el.dataset.on); - if (on) zoom.translateExtent([[-graphWidth/2, -graphHeight/2], [graphWidth*1.5, graphHeight*1.5]]); - else zoom.translateExtent([[0, 0], [graphWidth, graphHeight]]); + const on = (el.dataset.on = +!+el.dataset.on); + if (on) + zoom.translateExtent([ + [-graphWidth / 2, -graphHeight / 2], + [graphWidth * 1.5, graphHeight * 1.5] + ]); + else + zoom.translateExtent([ + [0, 0], + [graphWidth, graphHeight] + ]); } // add voice options -const voiceInterval = setInterval(function() { +const voiceInterval = setInterval(function () { const voices = speechSynthesis.getVoices(); - if (voices.length) clearInterval(voiceInterval); else return; + if (voices.length) clearInterval(voiceInterval); + else return; const select = document.getElementById("speakerVoice"); voices.forEach((voice, i) => { select.options.add(new Option(voice.name, i, false)); }); - if (stored("speakerVoice")) select.value = localStorage.getItem("speakerVoice"); // se voice to store + if (stored("speakerVoice")) select.value = localStorage.getItem("speakerVoice"); + // se voice to store else select.value = voices.findIndex(voice => voice.lang === "en-US"); // or to first found English-US }, 1000); @@ -234,15 +264,19 @@ function generateMapWithSeed() { } function showSeedHistoryDialog() { - const alert = mapHistory.map(function(h, i) { - const created = new Date(h.created).toLocaleTimeString(); - const button = ``; - return `
${i+1}. Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}
`; - }).join(""); + const alert = mapHistory + .map(function (h, i) { + const created = new Date(h.created).toLocaleTimeString(); + const button = ``; + return `
${i + 1}. Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}
`; + }) + .join(""); alertMessage.innerHTML = alert; $("#alert").dialog({ - resizable: false, title: "Seed history", - width: fitContent(), position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Seed history", + width: fitContent(), + position: {my: "center", at: "center", of: "svg"} }); } @@ -268,13 +302,14 @@ function restoreDefaultZoomExtent() { function copyMapURL() { const locked = document.querySelectorAll("i.icon-lock").length; // check if some options are locked - const search = `?seed=${optionsSeed.value}&width=${graphWidth}&height=${graphHeight}${locked?'':'&options=default'}`; - navigator.clipboard.writeText(location.host+location.pathname+search) - .then(() => { - tip("Map URL is copied to clipboard", false, "success", 3000); - //window.history.pushState({}, null, search); - }) - .catch(err => tip("Could not copy URL: "+err, false, "error", 5000)); + const search = `?seed=${optionsSeed.value}&width=${graphWidth}&height=${graphHeight}${locked ? "" : "&options=default"}`; + navigator.clipboard + .writeText(location.host + location.pathname + search) + .then(() => { + tip("Map URL is copied to clipboard", false, "success", 3000); + //window.history.pushState({}, null, search); + }) + .catch(err => tip("Could not copy URL: " + err, false, "error", 5000)); } function changeCellsDensity(value) { @@ -292,7 +327,7 @@ function changeCellsDensity(value) { if (v == 11) return 80000; if (v == 12) return 90000; if (v == 13) return 100000; - } + }; const cells = convert(value); pointsInput.setAttribute("data-cells", cells); @@ -302,7 +337,7 @@ function changeCellsDensity(value) { function changeCultureSet() { const max = culturesSet.selectedOptions[0].dataset.max; - culturesInput.max = culturesOutput.max = max + culturesInput.max = culturesOutput.max = max; if (+culturesOutput.value > +max) culturesInput.value = culturesOutput.value = max; } @@ -312,14 +347,14 @@ function changeEmblemShape(emblemShape) { shapePath ? image.setAttribute("d", shapePath) : image.removeAttribute("d"); const specificShape = ["culture", "state", "random"].includes(emblemShape) ? null : emblemShape; - if (emblemShape === "random") pack.cultures.filter(c => !c.removed).forEach(c => c.shield = Cultures.getRandomShield()); + if (emblemShape === "random") pack.cultures.filter(c => !c.removed).forEach(c => (c.shield = Cultures.getRandomShield())); const rerenderCOA = (id, coa) => { const coaEl = document.getElementById(id); if (!coaEl) return; // not rendered coaEl.remove(); COArenderer.trigger(id, coa); - } + }; pack.states.forEach(state => { if (!state.i || state.removed || !state.coa || state.coa === "custom") return; @@ -342,7 +377,7 @@ function changeEmblemShape(emblemShape) { if (!burg.i || burg.removed || !burg.coa || burg.coa === "custom") return; const newShield = specificShape || COA.getShield(burg.culture, burg.state); if (newShield === burg.coa.shield) return; - burg.coa.shield = newShield + burg.coa.shield = newShield; rerenderCOA("burgCOA" + burg.i, burg.coa); }); } @@ -359,7 +394,7 @@ function changeBurgsNumberSlider(value) { } function changeUIsize(value) { - if (isNaN(+value) || +value < .5) return; + if (isNaN(+value) || +value < 0.5) return; const max = getUImaxSize(); if (+value > max) value = max; @@ -384,19 +419,20 @@ function changeDialogsTransparency(value) { const alpha = (100 - +value) / 100; const optionsColor = "rgba(164, 139, 149, " + alpha + ")"; const dialogsColor = "rgba(255, 255, 255, " + alpha + ")"; - const optionButtonsColor = "rgba(145, 110, 127, " + Math.min(alpha + .3, 1) + ")"; - const optionLiColor = "rgba(153, 123, 137, " + Math.min(alpha + .3, 1) + ")"; + const optionButtonsColor = "rgba(145, 110, 127, " + Math.min(alpha + 0.3, 1) + ")"; + const optionLiColor = "rgba(153, 123, 137, " + Math.min(alpha + 0.3, 1) + ")"; document.getElementById("options").style.backgroundColor = optionsColor; document.getElementById("dialogs").style.backgroundColor = dialogsColor; - document.querySelectorAll(".tabcontent button").forEach(el => el.style.backgroundColor = optionButtonsColor); - document.querySelectorAll(".tabcontent li").forEach(el => el.style.backgroundColor = optionLiColor); - document.querySelectorAll("button.options").forEach(el => el.style.backgroundColor = optionLiColor); + document.querySelectorAll(".tabcontent button").forEach(el => (el.style.backgroundColor = optionButtonsColor)); + document.querySelectorAll(".tabcontent li").forEach(el => (el.style.backgroundColor = optionLiColor)); + document.querySelectorAll("button.options").forEach(el => (el.style.backgroundColor = optionLiColor)); } function changeZoomExtent(value) { - const min = Math.max(+zoomExtentMin.value, .01), max = Math.min(+zoomExtentMax.value, 200); + const min = Math.max(+zoomExtentMin.value, 0.01), + max = Math.min(+zoomExtentMax.value, 200); zoom.scaleExtent([min, max]); - const scale = Math.max(Math.min(+value, 200), .01); + const scale = Math.max(Math.min(+value, 200), 0.01); zoom.scaleTo(svg, scale); } @@ -410,20 +446,25 @@ function applyStoredOptions() { if (localStorage.getItem("distanceUnit")) applyOption(distanceUnitInput, localStorage.getItem("distanceUnit")); if (localStorage.getItem("heightUnit")) applyOption(heightUnit, localStorage.getItem("heightUnit")); - for (let i=0; i < localStorage.length; i++) { - const stored = localStorage.key(i), value = localStorage.getItem(stored); + for (let i = 0; i < localStorage.length; i++) { + const stored = localStorage.key(i), + value = localStorage.getItem(stored); if (stored === "speakerVoice") continue; - const input = document.getElementById(stored+"Input") || document.getElementById(stored); - const output = document.getElementById(stored+"Output"); + const input = document.getElementById(stored + "Input") || document.getElementById(stored); + const output = document.getElementById(stored + "Output"); if (input) input.value = value; if (output) output.value = value; lock(stored); // add saved style presets to options - if(stored.slice(0,5) === "style") applyOption(stylePreset, stored, stored.slice(5)); + if (stored.slice(0, 5) === "style") applyOption(stylePreset, stored, stored.slice(5)); } - if (localStorage.getItem("winds")) options.winds = localStorage.getItem("winds").split(",").map(w => +w); + if (localStorage.getItem("winds")) + options.winds = localStorage + .getItem("winds") + .split(",") + .map(w => +w); if (localStorage.getItem("military")) options.military = JSON.parse(localStorage.getItem("military")); changeDialogsTransparency(localStorage.getItem("transparency") || 5); @@ -451,7 +492,10 @@ function randomizeOptions() { if (randomize || !locked("template")) randomizeHeightmapTemplate(); if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(15, 3, 2, 30); if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100); - if (randomize || !locked("manors")) {manorsInput.value = 1000; manorsOutput.value = "auto";} + if (randomize || !locked("manors")) { + manorsInput.value = 1000; + manorsOutput.value = "auto"; + } if (randomize || !locked("religions")) religionsInput.value = religionsOutput.value = gauss(5, 2, 2, 10); if (randomize || !locked("power")) powerInput.value = powerOutput.value = gauss(4, 2, 0, 10, 2); if (randomize || !locked("neutral")) neutralInput.value = neutralOutput.value = rn(1 + Math.random(), 1); @@ -460,7 +504,8 @@ function randomizeOptions() { // 'Configure World' settings if (randomize || !locked("prec")) precInput.value = precOutput.value = gauss(100, 40, 5, 500); - const tMax = 30, tMin = -30; // temperature extremes + const tMax = 30, + tMin = -30; // temperature extremes if (randomize || !locked("temperatureEquator")) temperatureEquatorOutput.value = temperatureEquatorInput.value = rand(tMax - 10, tMax); if (randomize || !locked("temperaturePole")) temperaturePoleOutput.value = temperaturePoleInput.value = rand(tMin, tMin + 30); @@ -479,17 +524,17 @@ function randomizeOptions() { // select heightmap template pseudo-randomly function randomizeHeightmapTemplate() { const templates = { - "Volcano": 3, - "High Island": 22, - "Low Island": 9, - "Continents": 20, - "Archipelago": 25, - "Mediterranean":3, - "Peninsula": 3, - "Pangea": 5, - "Isthmus": 2, - "Atoll": 1, - "Shattered": 7 + Volcano: 3, + "High Island": 22, + "Low Island": 9, + Continents: 20, + Archipelago: 25, + Mediterranean: 3, + Peninsula: 3, + Pangea: 5, + Isthmus: 2, + Atoll: 1, + Shattered: 7 }; document.getElementById("templateInput").value = rw(templates); } @@ -497,14 +542,15 @@ function randomizeHeightmapTemplate() { // select culture set pseudo-randomly function randomizeCultureSet() { const sets = { - "world": 10, - "european": 10, - "oriental": 2, - "english": 5, - "antique": 3, - "highFantasy": 11, - "darkFantasy": 3, - "random": 1}; + world: 10, + european: 10, + oriental: 2, + english: 5, + antique: 3, + highFantasy: 11, + darkFantasy: 3, + random: 1 + }; culturesSet.value = rw(sets); changeCultureSet(); } @@ -512,21 +558,30 @@ function randomizeCultureSet() { // generate current year and era name function generateEra() { if (!stored("year")) yearInput.value = rand(100, 2000); // current year - if (!stored("era")) eraInput.value = Names.getBaseShort(P(.7) ? 1 : rand(nameBases.length)) + " Era"; + if (!stored("era")) eraInput.value = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era"; options.year = +yearInput.value; options.era = eraInput.value; - options.eraShort = options.era.split(" ").map(w => w[0].toUpperCase()).join(""); // short name for era + options.eraShort = options.era + .split(" ") + .map(w => w[0].toUpperCase()) + .join(""); // short name for era } function regenerateEra() { unlock("era"); - options.era = eraInput.value = Names.getBaseShort(P(.7) ? 1 : rand(nameBases.length)) + " Era"; - options.eraShort = options.era.split(" ").map(w => w[0].toUpperCase()).join(""); + options.era = eraInput.value = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era"; + options.eraShort = options.era + .split(" ") + .map(w => w[0].toUpperCase()) + .join(""); } function changeYear() { if (!yearInput.value) return; - if (isNaN(+yearInput.value)) {tip("Current year should be a number", false, "error"); return;} + if (isNaN(+yearInput.value)) { + tip("Current year should be a number", false, "error"); + return; + } options.year = +yearInput.value; } @@ -543,7 +598,7 @@ function restoreDefaultOptions() { } // Sticked menu Options listeners -document.getElementById("sticked").addEventListener("click", function(event) { +document.getElementById("sticked").addEventListener("click", function (event) { const id = event.target.id; if (id === "newMapButton") regeneratePrompt(); else if (id === "saveButton") showSavePane(); @@ -552,24 +607,44 @@ document.getElementById("sticked").addEventListener("click", function(event) { }); function regeneratePrompt() { - if (customization) {tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error"); return;} + if (customization) { + tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error"); + return; + } const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes - if (workingTime < 5) {regenerateMap(); return;} + if (workingTime < 5) { + regenerateMap(); + return; + } alertMessage.innerHTML = `Are you sure you want to generate a new map?
All unsaved changes made to the current map will be lost`; - $("#alert").dialog({resizable: false, title: "Generate new map", + $("#alert").dialog({ + resizable: false, + title: "Generate new map", buttons: { - Cancel: function() {$(this).dialog("close");}, - Generate: function() {closeDialogs(); regenerateMap();} + Cancel: function () { + $(this).dialog("close"); + }, + Generate: function () { + closeDialogs(); + regenerateMap(); + } } }); } function showSavePane() { - $("#saveMapData").dialog({title: "Save map", resizable: false, width: "27em", + $("#saveMapData").dialog({ + title: "Save map", + resizable: false, + width: "27em", position: {my: "center", at: "center", of: "svg"}, - buttons: {Close: function() {$(this).dialog("close");}} + buttons: { + Close: function () { + $(this).dialog("close"); + } + } }); } @@ -578,21 +653,34 @@ function saveGeoJSON() { alertMessage.innerHTML = `You can export map data in GeoJSON format used in GIS tools such as QGIS. Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export", "wiki-page")} for guidance`; - $("#alert").dialog({title: "GIS data export", resizable: false, width: "35em", position: {my: "center", at: "center", of: "svg"}, + $("#alert").dialog({ + title: "GIS data export", + resizable: false, + width: "35em", + position: {my: "center", at: "center", of: "svg"}, buttons: { Cells: saveGeoJSON_Cells, Routes: saveGeoJSON_Routes, Rivers: saveGeoJSON_Rivers, Markers: saveGeoJSON_Markers, - Close: function() {$(this).dialog("close");} + Close: function () { + $(this).dialog("close"); + } } }); } function showLoadPane() { - $("#loadMapData").dialog({title: "Load map", resizable: false, width: "17em", + $("#loadMapData").dialog({ + title: "Load map", + resizable: false, + width: "17em", position: {my: "center", at: "center", of: "svg"}, - buttons: {Close: function() {$(this).dialog("close");}} + buttons: { + Close: function () { + $(this).dialog("close"); + } + } }); } @@ -602,21 +690,29 @@ function loadURL() {
Please note server should allow CORS for file to be loaded. If CORS is not allowed, save file to Dropbox and provide a direct link`; alertMessage.innerHTML = inner; - $("#alert").dialog({resizable: false, title: "Load map from URL", width: "27em", + $("#alert").dialog({ + resizable: false, + title: "Load map from URL", + width: "27em", buttons: { - Load: function() { + Load: function () { const value = mapURL.value; - if (!pattern.test(value)) {tip("Please provide a valid URL", false, "error"); return;} + if (!pattern.test(value)) { + tip("Please provide a valid URL", false, "error"); + return; + } loadMapFromURL(value); $(this).dialog("close"); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } // load map -document.getElementById("mapToLoad").addEventListener("change", function() { +document.getElementById("mapToLoad").addEventListener("change", function () { const fileToLoad = this.files[0]; this.value = ""; closeDialogs(); @@ -673,15 +769,17 @@ async function enter3dView(type) { canvas.onmouseenter = () => { const help = "Left mouse to change angle, middle mouse / mousewheel to zoom, right mouse to pan. O to toggle options"; +canvas.dataset.hovered > 2 ? tip("") : tip(help); - canvas.dataset.hovered = (+canvas.dataset.hovered|0) + 1; + canvas.dataset.hovered = (+canvas.dataset.hovered | 0) + 1; }; if (type === "heightmap3DView") { document.getElementById("preview3d").appendChild(canvas); $("#preview3d").dialog({ - title: "3D Preview", resizable: true, + title: "3D Preview", + resizable: true, position: {my: "left bottom", at: "left+10 bottom-20", of: "svg"}, - resizeStop: resize3d, close: enterStandardView + resizeStop: resize3d, + close: enterStandardView }); } else document.body.insertBefore(canvas, optionsContainer); @@ -696,9 +794,14 @@ function resize3d() { } function toggle3dOptions() { - if (options3dUpdate.offsetParent) {$("#options3d").dialog("close"); return;} + if (options3dUpdate.offsetParent) { + $("#options3d").dialog("close"); + return; + } $("#options3d").dialog({ - title: "3D mode settings", resizable: false, width: fitContent(), + title: "3D mode settings", + resizable: false, + width: fitContent(), position: {my: "right top", at: "right-30 top+10", of: "svg", collision: "fit"} }); From 67235bc41e5d8acf1aabc7d0e6cdbd99b779cb06 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 6 Jun 2021 01:29:58 +0300 Subject: [PATCH 03/34] make depressions resolve elevation change not that big --- index.css | 8 +- index.html | 3 + main.js | 856 ++++++++++++++++++++------------- modules/lakes.js | 166 ++++--- modules/river-generator.js | 642 +++++++++++++------------ modules/save-and-load.js | 458 ++++++++++-------- modules/ui/heightmap-editor.js | 522 +++++++++++--------- modules/ui/tools.js | 374 ++++++++------ 8 files changed, 1765 insertions(+), 1264 deletions(-) diff --git a/index.css b/index.css index 36869b25..771e6422 100644 --- a/index.css +++ b/index.css @@ -104,7 +104,7 @@ a { } #biomes { - stroke-width: .7; + stroke-width: 0.7; } #landmass { @@ -285,6 +285,12 @@ i.icon-lock { animation: dash 80s linear backwards; } +.arrow { + marker-end: url(#end-arrow-small); + stroke: #555; + stroke-width: 0.5; +} + @keyframes dash { to { stroke-dashoffset: 0; diff --git a/index.html b/index.html index 698d876a..eb363635 100644 --- a/index.html +++ b/index.html @@ -3451,6 +3451,9 @@ + + + diff --git a/main.js b/main.js index c37dffb2..4cba5ac2 100644 --- a/main.js +++ b/main.js @@ -110,7 +110,12 @@ legend.on("mousemove", () => tip("Drag to change the position. Click to hide the // main data variables let grid = {}; // initial grapg based on jittered square grid and data let pack = {}; // packed graph and data -let seed, mapId, mapHistory = [], elSelected, modules = {}, notes = []; +let seed, + mapId, + mapHistory = [], + elSelected, + modules = {}, + notes = []; let rulers = new Rulers(); let customization = 0; // 0 - no; 1 = heightmap draw; 2 - states draw; 3 - add state/burg; 4 - cultures draw @@ -122,30 +127,34 @@ let color = d3.scaleSequential(d3.interpolateSpectral); // default color scheme const lineGen = d3.line().curve(d3.curveBasis); // d3 line generator with default curve interpolation // d3 zoom behavior -let scale = 1, viewX = 0, viewY = 0; +let scale = 1, + viewX = 0, + viewY = 0; const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", zoomed); // default options -let options = {pinNotes:false}; // options object +let options = {pinNotes: false}; // options object let mapCoordinates = {}; // map coordinates on globe options.winds = [225, 45, 225, 315, 135, 315]; // default wind directions applyStoredOptions(); -let graphWidth = +mapWidthInput.value, graphHeight = +mapHeightInput.value; // voronoi graph extention, cannot be changed arter generation -let svgWidth = graphWidth, svgHeight = graphHeight; // svg canvas resolution, can be changed +let graphWidth = +mapWidthInput.value, + graphHeight = +mapHeightInput.value; // voronoi graph extention, cannot be changed arter generation +let svgWidth = graphWidth, + svgHeight = graphHeight; // svg canvas resolution, can be changed landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); -void function removeLoading() { +void (function removeLoading() { d3.select("#loading").transition().duration(4000).style("opacity", 0).remove(); d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove(); d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1); d3.select("#tooltip").transition().duration(4000).style("opacity", 1); -}() +})(); // decide which map should be loaded or generated on page load -void function checkLoadParameters() { +void (function checkLoadParameters() { const url = new URL(window.location.href); const params = url.searchParams; @@ -155,8 +164,10 @@ void function checkLoadParameters() { const maplink = params.get("maplink"); const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; const valid = pattern.test(maplink); - if (valid) {loadMapFromURL(maplink, 1); return;} - else showUploadErrorMessage("Map link is not a valid URL", maplink); + if (valid) { + loadMapFromURL(maplink, 1); + return; + } else showUploadErrorMessage("Map link is not a valid URL", maplink); } // if there is a seed (user of MFCG provided), generate map for it @@ -173,8 +184,7 @@ void function checkLoadParameters() { WARN && console.warn("Load last saved map"); try { uploadMap(blob); - } - catch(error) { + } catch (error) { ERROR && console.error(error); WARN && console.warn("Cannot load stored map, random map to be generated"); generateMapOnLoad(); @@ -189,16 +199,17 @@ void function checkLoadParameters() { WARN && console.warn("Generate random map"); generateMapOnLoad(); -}() +})(); function loadMapFromURL(maplink, random) { const URL = decodeURIComponent(maplink); - fetch(URL, {method: 'GET', mode: 'cors'}) + fetch(URL, {method: "GET", mode: "cors"}) .then(response => { - if(response.ok) return response.blob(); + if (response.ok) return response.blob(); throw new Error("Cannot load map from URL"); - }).then(blob => uploadMap(blob)) + }) + .then(blob => uploadMap(blob)) .catch(error => { showUploadErrorMessage(error.message, URL, random); if (random) generateMapOnLoad(); @@ -208,9 +219,17 @@ function loadMapFromURL(maplink, random) { function showUploadErrorMessage(error, URL, random) { ERROR && console.error(error); alertMessage.innerHTML = `Cannot load map from the ${link(URL, "link provided")}. - ${random?`A new random map is generated. `:''} + ${random ? `A new random map is generated. ` : ""} Please ensure the linked file is reachable and CORS is allowed on server side`; - $("#alert").dialog({title: "Loading error", width: "32em", buttons: {OK: function() {$(this).dialog("close");}}}); + $("#alert").dialog({ + title: "Loading error", + width: "32em", + buttons: { + OK: function () { + $(this).dialog("close"); + } + } + }); } function generateMapOnLoad() { @@ -257,8 +276,12 @@ function focusOn() { // find burg for MFCG and focus on it function findBurgForMFCG(params) { - const cells = pack.cells, burgs = pack.burgs; - if (pack.burgs.length < 2) {ERROR && console.error("Cannot select a burg for MFCG"); return;} + const cells = pack.cells, + burgs = pack.burgs; + if (pack.burgs.length < 2) { + ERROR && console.error("Cannot select a burg for MFCG"); + return; + } // used for selection const size = +params.get("size"); @@ -283,26 +306,32 @@ function findBurgForMFCG(params) { // select a burg with closest population from selection const selected = d3.scan(selection, (a, b) => Math.abs(a.population - size) - Math.abs(b.population - size)); const burgId = selection[selected].i; - if (!burgId) {ERROR && console.error("Cannot select a burg for MFCG"); return;} + if (!burgId) { + ERROR && console.error("Cannot select a burg for MFCG"); + return; + } const b = burgs[burgId]; const referrer = new URL(document.referrer); for (let p of referrer.searchParams) { - if (p[0] === "name") b.name = p[1]; else - if (p[0] === "size") b.population = +p[1]; else - if (p[0] === "seed") b.MFCG = +p[1]; else - if (p[0] === "shantytown") b.shanty = +p[1]; else - b[p[0]] = +p[1]; // other parameters + if (p[0] === "name") b.name = p[1]; + else if (p[0] === "size") b.population = +p[1]; + else if (p[0] === "seed") b.MFCG = +p[1]; + else if (p[0] === "shantytown") b.shanty = +p[1]; + else b[p[0]] = +p[1]; // other parameters } b.MFCGlink = document.referrer; // set direct link to MFCG if (params.get("name") && params.get("name") != "null") b.name = params.get("name"); const label = burgLabels.select("[data-id='" + burgId + "']"); if (label.size()) { - label.text(b.name).classed("drag", true).on("mouseover", function() { - d3.select(this).classed("drag", false); - label.on("mouseover", null); - }); + label + .text(b.name) + .classed("drag", true) + .on("mouseover", function () { + d3.select(this).classed("drag", false); + label.on("mouseover", null); + }); } zoomTo(b.x, b.y, 8, 1600); @@ -312,30 +341,33 @@ function findBurgForMFCG(params) { // apply default biomes data function applyDefaultBiomesSystem() { - const name = ["Marine","Hot desert","Cold desert","Savanna","Grassland","Tropical seasonal forest","Temperate deciduous forest","Tropical rainforest","Temperate rainforest","Taiga","Tundra","Glacier","Wetland"]; - const color = ["#466eab","#fbe79f","#b5b887","#d2d082","#c8d68f","#b6d95d","#29bc56","#7dcb35","#409c43","#4b6b32","#96784b","#d5e7eb","#0b9131"]; - const habitability = [0,4,10,22,30,50,100,80,90,12,4,0,12]; - const iconsDensity = [0,3,2,120,120,120,120,150,150,100,5,0,150]; - const icons = [{},{dune:3, cactus:6, deadTree:1},{dune:9, deadTree:1},{acacia:1, grass:9},{grass:1},{acacia:8, palm:1},{deciduous:1},{acacia:5, palm:3, deciduous:1, swamp:1},{deciduous:6, swamp:1},{conifer:1},{grass:1},{},{swamp:1}]; - const cost = [10,200,150,60,50,70,70,80,90,200,1000,5000,150]; // biome movement cost - const biomesMartix = [ // hot ↔ cold [>19°C; <-4°C]; dry ↕ wet - new Uint8Array([1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,10]), - new Uint8Array([3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,9,9,9,9,10,10,10]), - new Uint8Array([5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,9,9,9,9,9,10,10,10]), - new Uint8Array([5,6,6,6,6,6,6,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,10,10,10]), - new Uint8Array([7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,10,10]) + const name = ["Marine", "Hot desert", "Cold desert", "Savanna", "Grassland", "Tropical seasonal forest", "Temperate deciduous forest", "Tropical rainforest", "Temperate rainforest", "Taiga", "Tundra", "Glacier", "Wetland"]; + const color = ["#466eab", "#fbe79f", "#b5b887", "#d2d082", "#c8d68f", "#b6d95d", "#29bc56", "#7dcb35", "#409c43", "#4b6b32", "#96784b", "#d5e7eb", "#0b9131"]; + const habitability = [0, 4, 10, 22, 30, 50, 100, 80, 90, 12, 4, 0, 12]; + const iconsDensity = [0, 3, 2, 120, 120, 120, 120, 150, 150, 100, 5, 0, 150]; + const icons = [{}, {dune: 3, cactus: 6, deadTree: 1}, {dune: 9, deadTree: 1}, {acacia: 1, grass: 9}, {grass: 1}, {acacia: 8, palm: 1}, {deciduous: 1}, {acacia: 5, palm: 3, deciduous: 1, swamp: 1}, {deciduous: 6, swamp: 1}, {conifer: 1}, {grass: 1}, {}, {swamp: 1}]; + const cost = [10, 200, 150, 60, 50, 70, 70, 80, 90, 200, 1000, 5000, 150]; // biome movement cost + const biomesMartix = [ + // hot ↔ cold [>19°C; <-4°C]; dry ↕ wet + new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10]), + new Uint8Array([3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 10, 10, 10]), + new Uint8Array([5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 9, 9, 9, 9, 10, 10, 10]), + new Uint8Array([5, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10]), + new Uint8Array([7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10, 10]) ]; // parse icons weighted array into a simple array - for (let i=0; i < icons.length; i++) { + for (let i = 0; i < icons.length; i++) { const parsed = []; for (const icon in icons[i]) { - for (let j = 0; j < icons[i][icon]; j++) {parsed.push(icon);} + for (let j = 0; j < icons[i][icon]; j++) { + parsed.push(icon); + } } icons[i] = parsed; } - return {i:d3.range(0, name.length), name, color, biomesMartix, habitability, iconsDensity, icons, cost}; + return {i: d3.range(0, name.length), name, color, biomesMartix, habitability, iconsDensity, icons, cost}; } function showWelcomeMessage() { @@ -363,18 +395,24 @@ function showWelcomeMessage() {

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

Thanks for all supporters on ${patreon}!`; - $("#alert").dialog( - {resizable: false, title: "Fantasy Map Generator update", width: "28em", - buttons: {OK: function() {$(this).dialog("close")}}, + $("#alert").dialog({ + resizable: false, + title: "Fantasy Map Generator update", + width: "28em", + buttons: { + OK: function () { + $(this).dialog("close"); + } + }, position: {my: "center center-4em", at: "center", of: "svg"}, - close: () => localStorage.setItem("version", version)} - ); + close: () => localStorage.setItem("version", version) + }); } function zoomed() { const transform = d3.event.transform; const scaleDiff = scale - transform.k; - const positionDiff = viewX - transform.x | viewY - transform.y; + const positionDiff = (viewX - transform.x) | (viewY - transform.y); if (!positionDiff && !scaleDiff) return; scale = transform.k; @@ -417,7 +455,10 @@ function resetZoom(d = 1000) { function getViewBoxExtent() { // x = trX / scale * -1 + graphWidth / scale // y = trY / scale * -1 + graphHeight / scale - return [[Math.abs(viewX / scale), Math.abs(viewY / scale)], [Math.abs(viewX / scale) + graphWidth / scale, Math.abs(viewY / scale) + graphHeight / scale]]; + return [ + [Math.abs(viewX / scale), Math.abs(viewY / scale)], + [Math.abs(viewX / scale) + graphWidth / scale, Math.abs(viewY / scale) + graphHeight / scale] + ]; } // active zooming feature @@ -430,7 +471,7 @@ function invokeActiveZooming() { // rescale lables on zoom if (labels.style("display") !== "none") { - labels.selectAll("g").each(function(d) { + labels.selectAll("g").each(function (d) { if (this.id === "burgLabels") return; const desired = +this.dataset.size; const relative = Math.max(rn((desired + desired / scale) / 2, 2), 1); @@ -440,19 +481,23 @@ function invokeActiveZooming() { else this.classList.remove("hidden"); }); } - + // rescale emblems on zoom if (emblems.style("display") !== "none") { - emblems.selectAll("g").each(function() { + emblems.selectAll("g").each(function () { const size = this.getAttribute("font-size") * scale; const hidden = hideEmblems.checked && (size < 25 || size > 300); - if (hidden) this.classList.add("hidden"); else this.classList.remove("hidden"); + if (hidden) this.classList.add("hidden"); + else this.classList.remove("hidden"); if (!hidden && window.COArenderer && this.children.length && !this.children[0].getAttribute("href")) renderGroupCOAs(this); }); } // turn off ocean pattern if scale is big (improves performance) - oceanPattern.select("rect").attr("fill", scale > 10 ? "#fff" : "url(#oceanic)").attr("opacity", scale > 10 ? .2 : null); + oceanPattern + .select("rect") + .attr("fill", scale > 10 ? "#fff" : "url(#oceanic)") + .attr("opacity", scale > 10 ? 0.2 : null); // change states halo width if (!customization) { @@ -462,45 +507,49 @@ function invokeActiveZooming() { // rescale map markers if (+markers.attr("rescale") && markers.style("display") !== "none") { - markers.selectAll("use").each(function(d) { - const x = +this.dataset.x, y = +this.dataset.y, desired = +this.dataset.size; + markers.selectAll("use").each(function (d) { + const x = +this.dataset.x, + y = +this.dataset.y, + desired = +this.dataset.size; const size = Math.max(desired * 5 + 25 / scale, 1); - d3.select(this).attr("x", x - size/2).attr("y", y - size).attr("width", size).attr("height", size); + d3.select(this) + .attr("x", x - size / 2) + .attr("y", y - size) + .attr("width", size) + .attr("height", size); }); } // rescale rulers to have always the same size if (ruler.style("display") !== "none") { - const size = rn(10 / scale ** .3 * 2, 2); + const size = rn((10 / scale ** 0.3) * 2, 2); ruler.selectAll("text").attr("font-size", size); } } async function renderGroupCOAs(g) { - const [group, type] = g.id === "burgEmblems" ? [pack.burgs, "burg"] : - g.id === "provinceEmblems" ? [pack.provinces, "province"] : - [pack.states, "state"]; + const [group, type] = g.id === "burgEmblems" ? [pack.burgs, "burg"] : g.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"]; for (let use of g.children) { const i = +use.dataset.i; - const id = type+"COA"+i; + const id = type + "COA" + i; COArenderer.trigger(id, group[i].coa); - use.setAttribute("href", "#"+id); + use.setAttribute("href", "#" + id); } } // add drag to upload logic, pull request from @evyatron -void function addDragToUpload() { - document.addEventListener("dragover", function(e) { +void (function addDragToUpload() { + document.addEventListener("dragover", function (e) { e.stopPropagation(); e.preventDefault(); document.getElementById("mapOverlay").style.display = null; }); - document.addEventListener('dragleave', function(e) { + document.addEventListener("dragleave", function (e) { document.getElementById("mapOverlay").style.display = "none"; }); - document.addEventListener("drop", function(e) { + document.addEventListener("drop", function (e) { e.stopPropagation(); e.preventDefault(); @@ -508,11 +557,18 @@ void function addDragToUpload() { overlay.style.display = "none"; if (e.dataTransfer.items == null || e.dataTransfer.items.length !== 1) return; // no files or more than one const file = e.dataTransfer.items[0].getAsFile(); - if (file.name.indexOf('.map') == -1) { // not a .map file - alertMessage.innerHTML = 'Please upload a .map file you have previously downloaded'; + if (file.name.indexOf(".map") == -1) { + // not a .map file + alertMessage.innerHTML = "Please upload a .map file you have previously downloaded"; $("#alert").dialog({ - resizable: false, title: "Invalid file format", position: {my: "center", at: "center", of: "svg"}, - buttons: {Close: function() {$(this).dialog("close");}} + resizable: false, + title: "Invalid file format", + position: {my: "center", at: "center", of: "svg"}, + buttons: { + Close: function () { + $(this).dialog("close"); + } + } }); return; } @@ -526,7 +582,7 @@ void function addDragToUpload() { overlay.innerHTML = "Drop a .map file to open"; }); }); -}() +})(); function generate() { try { @@ -541,7 +597,7 @@ function generate() { drawScaleBar(); HeightmapGenerator.generate(); markFeatures(); - getSignedDistanceField(); + markupGridOcean(); openNearSeaLakes(); OceanLayers(); defineMapSize(); @@ -576,11 +632,10 @@ function generate() { addZones(); Names.getMapName(); - WARN && console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`); + WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); showStatistics(); INFO && console.groupEnd("Generated Map " + seed); - } - catch(error) { + } catch (error) { ERROR && console.error(error); clearMainTip(); @@ -588,14 +643,25 @@ function generate() {
If error is critical, clear the stored data and try again.

${parseError(error)}

`; $("#alert").dialog({ - resizable: false, title: "Generation error", width:"32em", buttons: { - "Clear data": function() {localStorage.clear(); localStorage.setItem("version", version);}, - Regenerate: function() {regenerateMap(); $(this).dialog("close");}, - Ignore: function() {$(this).dialog("close");} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Generation error", + width: "32em", + buttons: { + "Clear data": function () { + localStorage.clear(); + localStorage.setItem("version", version); + }, + Regenerate: function () { + regenerateMap(); + $(this).dialog("close"); + }, + Ignore: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); } - } // generate map seed (string!) or get it from URL searchParams @@ -604,7 +670,7 @@ function generateSeed() { const url = new URL(window.location.href); const params = url.searchParams; const urlSeed = url.searchParams.get("seed"); - if (first && params.get("from") === "MFCG" && urlSeed.length === 13) seed = urlSeed.slice(0,-4); + if (first && params.get("from") === "MFCG" && urlSeed.length === 13) seed = urlSeed.slice(0, -4); else if (first && urlSeed) seed = urlSeed; else if (optionsSeed.value && optionsSeed.value != seed) seed = optionsSeed.value; else seed = Math.floor(Math.random() * 1e9).toString(); @@ -617,7 +683,7 @@ function placePoints() { TIME && console.time("placePoints"); const cellsDesired = +pointsInput.dataset.cells; - const spacing = grid.spacing = rn(Math.sqrt(graphWidth * graphHeight / cellsDesired), 2); // spacing between points before jirrering + const spacing = (grid.spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2)); // spacing between points before jirrering grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing); grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid grid.cellsX = Math.floor((graphWidth + 0.5 * spacing) / spacing); @@ -646,7 +712,8 @@ function markFeatures() { TIME && console.time("markFeatures"); Math.random = aleaPRNG(seed); // get the same result on heightmap edit in Erase mode - const cells = grid.cells, heights = grid.cells.h; + const cells = grid.cells, + heights = grid.cells.h; cells.f = new Uint16Array(cells.i.length); // cell feature number cells.t = new Int8Array(cells.i.length); // cell type: 1 = land coast; -1 = water near coast grid.features = [0]; @@ -680,35 +747,33 @@ function markFeatures() { TIME && console.timeEnd("markFeatures"); } -function getSignedDistanceField() { - TIME && console.time("getSignedDistanceField"); - const cells = grid.cells, pointsN = cells.i.length; - markup(-2, -1, -10); - markup(2, 1, 0); +function markupGridOcean() { + TIME && console.time("markupGridOcean"); + markup(grid.cells, -2, -1, -10); + TIME && console.timeEnd("markupGridOcean"); +} - // build signed distance field - function markup(start, increment, limit) { - for (let t = start, count = Infinity; count > 0 && t > limit; t += increment) { - count = 0; - const prevT = t - increment; - for (let i = 0; i < pointsN; i++) { - if (cells.t[i] !== prevT) continue; +function markup(cells, start, increment, limit) { + for (let t = start, count = Infinity; count > 0 && t > limit; t += increment) { + count = 0; + const prevT = t - increment; + for (let i = 0; i < cells.i.length; i++) { + if (cells.t[i] !== prevT) continue; - for (const c of cells.c[i]) { - if (cells.t[c]) continue; - cells.t[c] = t; - count++; - } + for (const c of cells.c[i]) { + if (cells.t[c]) continue; + cells.t[c] = t; + count++; } } } - TIME && console.timeEnd("getSignedDistanceField"); } // near sea lakes usually get a lot of water inflow, most of them should brake treshold and flow out to sea (see Ancylus Lake) function openNearSeaLakes() { if (templateInput.value === "Atoll") return; // no need for Atolls - const cells = grid.cells, features = grid.features; + const cells = grid.cells, + features = grid.features; if (!features.find(f => f.type === "lake")) return; // no lakes TIME && console.time("openLakes"); const LIMIT = 22; // max height that can be breached by water @@ -717,8 +782,7 @@ function openNearSeaLakes() { const lake = cells.f[i]; if (features[lake].type !== "lake") continue; // not a lake cell - check_neighbours: - for (const c of cells.c[i]) { + check_neighbours: for (const c of cells.c[i]) { if (cells.t[c] !== 1 || cells.h[c] > LIMIT) continue; // water cannot brake this for (const n of cells.c[c]) { @@ -734,7 +798,7 @@ function openNearSeaLakes() { cells.h[treshold] = 19; cells.t[treshold] = -1; cells.f[treshold] = ocean; - cells.c[treshold].forEach(function(c) { + cells.c[treshold].forEach(function (c) { if (cells.h[c] >= 20) cells.t[c] = 1; // mark as coastline }); features[lake].type = "ocean"; // mark former lake as ocean @@ -754,15 +818,15 @@ function defineMapSize() { const template = document.getElementById("templateInput").value; // heightmap template const part = grid.features.some(f => f.land && f.border); // if land goes over map borders const max = part ? 80 : 100; // max size - const lat = () => gauss(P(.5) ? 40 : 60, 15, 25, 75); // latiture shift + const lat = () => gauss(P(0.5) ? 40 : 60, 15, 25, 75); // latiture shift if (!part) { if (template === "Pangea") return [100, 50]; - if (template === "Shattered" && P(.7)) return [100, 50]; - if (template === "Continents" && P(.5)) return [100, 50]; - if (template === "Archipelago" && P(.35)) return [100, 50]; - if (template === "High Island" && P(.25)) return [100, 50]; - if (template === "Low Island" && P(.1)) return [100, 50]; + if (template === "Shattered" && P(0.7)) return [100, 50]; + if (template === "Continents" && P(0.5)) return [100, 50]; + if (template === "Archipelago" && P(0.35)) return [100, 50]; + if (template === "High Island" && P(0.25)) return [100, 50]; + if (template === "Low Island" && P(0.1)) return [100, 50]; } if (template === "Pangea") return [gauss(70, 20, 30, max), lat()]; @@ -781,30 +845,30 @@ function calculateMapCoordinates() { const size = +document.getElementById("mapSizeOutput").value; const latShift = +document.getElementById("latitudeOutput").value; - const latT = size / 100 * 180; - const latN = 90 - (180 - latT) * latShift / 100; + const latT = (size / 100) * 180; + const latN = 90 - ((180 - latT) * latShift) / 100; const latS = latN - latT; - const lon = Math.min(graphWidth / graphHeight * latT / 2, 180); - mapCoordinates = {latT, latN, latS, lonT: lon*2, lonW: -lon, lonE: lon}; + const lon = Math.min(((graphWidth / graphHeight) * latT) / 2, 180); + mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon}; } // temperature model function calculateTemperatures() { - TIME && console.time('calculateTemperatures'); + TIME && console.time("calculateTemperatures"); const cells = grid.cells; cells.temp = new Int8Array(cells.i.length); // temperature array const tEq = +temperatureEquatorInput.value; const tPole = +temperaturePoleInput.value; const tDelta = tEq - tPole; - const int = d3.easePolyInOut.exponent(.5); // interpolation function + const int = d3.easePolyInOut.exponent(0.5); // interpolation function - d3.range(0, cells.i.length, grid.cellsX).forEach(function(r) { + d3.range(0, cells.i.length, grid.cellsX).forEach(function (r) { const y = grid.points[r][1]; - const lat = Math.abs(mapCoordinates.latN - y / graphHeight * mapCoordinates.latT); // [0; 90] + const lat = Math.abs(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT); // [0; 90] const initTemp = tEq - int(lat / 90) * tDelta; - for (let i = r; i < r+grid.cellsX; i++) { + for (let i = r; i < r + grid.cellsX; i++) { cells.temp[i] = Math.max(Math.min(initTemp - convertToFriendly(cells.h[i]), 127), -128); } }); @@ -814,41 +878,46 @@ function calculateTemperatures() { if (h < 20) return 0; const exponent = +heightExponentInput.value; const height = Math.pow(h - 18, exponent); - return rn(height / 1000 * 6.5); + return rn((height / 1000) * 6.5); } - TIME && console.timeEnd('calculateTemperatures'); + TIME && console.timeEnd("calculateTemperatures"); } // simplest precipitation model function generatePrecipitation() { - TIME && console.time('generatePrecipitation'); + TIME && console.time("generatePrecipitation"); prec.selectAll("*").remove(); const cells = grid.cells; cells.prec = new Uint8Array(cells.i.length); // precipitation array const modifier = precInput.value / 100; // user's input - const cellsX = grid.cellsX, cellsY = grid.cellsY; - let westerly = [], easterly = [], southerly = 0, northerly = 0; + const cellsX = grid.cellsX, + cellsY = grid.cellsY; + let westerly = [], + easterly = [], + southerly = 0, + northerly = 0; - {// latitude bands - // x4 = 0-5 latitude: wet through the year (rising zone) - // x2 = 5-20 latitude: wet summer (rising zone), dry winter (sinking zone) - // x1 = 20-30 latitude: dry all year (sinking zone) - // x2 = 30-50 latitude: wet winter (rising zone), dry summer (sinking zone) - // x3 = 50-60 latitude: wet all year (rising zone) - // x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone) - // x1 = 70-90 latitude: dry all year (sinking zone) + { + // latitude bands + // x4 = 0-5 latitude: wet through the year (rising zone) + // x2 = 5-20 latitude: wet summer (rising zone), dry winter (sinking zone) + // x1 = 20-30 latitude: dry all year (sinking zone) + // x2 = 30-50 latitude: wet winter (rising zone), dry summer (sinking zone) + // x3 = 50-60 latitude: wet all year (rising zone) + // x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone) + // x1 = 70-90 latitude: dry all year (sinking zone) } - const lalitudeModifier = [4,2,2,2,1,1,2,2,2,2,3,3,2,2,1,1,1,0.5]; // by 5d step + const lalitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5]; // by 5d step // difine wind directions based on cells latitude and prevailing winds there - d3.range(0, cells.i.length, cellsX).forEach(function(c, i) { - const lat = mapCoordinates.latN - i / cellsY * mapCoordinates.latT; - const band = (Math.abs(lat) - 1) / 5 | 0; + d3.range(0, cells.i.length, cellsX).forEach(function (c, i) { + const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT; + const band = ((Math.abs(lat) - 1) / 5) | 0; const latMod = lalitudeModifier[band]; - const tier = Math.abs(lat - 89) / 30 | 0; // 30d tiers from 0 to 5 from N to S + const tier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S if (options.winds[tier] > 40 && options.winds[tier] < 140) westerly.push([c, latMod, tier]); - else if (options.winds[tier] > 220 && options.winds[tier] < 320) easterly.push([c + cellsX -1, latMod, tier]); + else if (options.winds[tier] > 220 && options.winds[tier] < 320) easterly.push([c + cellsX - 1, latMod, tier]); if (options.winds[tier] > 100 && options.winds[tier] < 260) northerly++; else if (options.winds[tier] > 280 || options.winds[tier] < 80) southerly++; }); @@ -856,24 +925,27 @@ function generatePrecipitation() { // distribute winds by direction if (westerly.length) passWind(westerly, 120 * modifier, 1, cellsX); if (easterly.length) passWind(easterly, 120 * modifier, -1, cellsX); - const vertT = (southerly + northerly); + const vertT = southerly + northerly; if (northerly) { - const bandN = (Math.abs(mapCoordinates.latN) - 1) / 5 | 0; + const bandN = ((Math.abs(mapCoordinates.latN) - 1) / 5) | 0; const latModN = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandN]; - const maxPrecN = northerly / vertT * 60 * modifier * latModN; + const maxPrecN = (northerly / vertT) * 60 * modifier * latModN; passWind(d3.range(0, cellsX, 1), maxPrecN, cellsX, cellsY); } if (southerly) { - const bandS = (Math.abs(mapCoordinates.latS) - 1) / 5 | 0; + const bandS = ((Math.abs(mapCoordinates.latS) - 1) / 5) | 0; const latModS = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandS]; - const maxPrecS = southerly / vertT * 60 * modifier * latModS; + const maxPrecS = (southerly / vertT) * 60 * modifier * latModS; passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY); } function passWind(source, maxPrec, next, steps) { const maxPrecInit = maxPrec; for (let first of source) { - if (first[0]) {maxPrec = Math.min(maxPrecInit * first[1], 255); first = first[0];} + if (first[0]) { + maxPrec = Math.min(maxPrecInit * first[1], 255); + first = first[0]; + } let humidity = maxPrec - cells.h[first]; // initial water amount if (humidity <= 0) continue; // if first cell in row is too elevated cosdired wind dry for (let s = 0, current = first; s < steps; s++, current += next) { @@ -881,8 +953,8 @@ function generatePrecipitation() { if (cells.temp[current] < -5) continue; // water cell if (cells.h[current] < 20) { - if (cells.h[current+next] >= 20) { - cells.prec[current+next] += Math.max(humidity / rand(10, 20), 1); // coastal precipitation + if (cells.h[current + next] >= 20) { + cells.prec[current + next] += Math.max(humidity / rand(10, 20), 1); // coastal precipitation } else { humidity = Math.min(humidity + 5 * modifier, maxPrec); // wind gets more humidity passing water cell cells.prec[current] += 5 * modifier; // water cells precipitation (need to correctly pour water through lakes) @@ -900,21 +972,22 @@ function generatePrecipitation() { } function getPrecipitation(humidity, i, n) { - if (cells.h[i+n] > 85) return humidity; // 85 is max passable height + if (cells.h[i + n] > 85) return humidity; // 85 is max passable height const normalLoss = Math.max(humidity / (10 * modifier), 1); // precipitation in normal conditions - const diff = Math.max(cells.h[i+n] - cells.h[i], 0); // difference in height - const mod = (cells.h[i+n] / 70) ** 2; // 50 stands for hills, 70 for mountains + const diff = Math.max(cells.h[i + n] - cells.h[i], 0); // difference in height + const mod = (cells.h[i + n] / 70) ** 2; // 50 stands for hills, 70 for mountains return Math.min(Math.max(normalLoss + diff * mod, 1), humidity); } - void function drawWindDirection() { - const wind = prec.append("g").attr("id", "wind"); + void (function drawWindDirection() { + const wind = prec.append("g").attr("id", "wind"); - d3.range(0, 6).forEach(function(t) { + d3.range(0, 6).forEach(function (t) { if (westerly.length > 1) { const west = westerly.filter(w => w[2] === t); if (west && west.length > 3) { - const from = west[0][0], to = west[west.length-1][0]; + const from = west[0][0], + to = west[west.length - 1][0]; const y = (grid.points[from][1] + grid.points[to][1]) / 2; wind.append("text").attr("x", 20).attr("y", y).text("\u21C9"); } @@ -922,32 +995,47 @@ function generatePrecipitation() { if (easterly.length > 1) { const east = easterly.filter(w => w[2] === t); if (east && east.length > 3) { - const from = east[0][0], to = east[east.length-1][0]; + const from = east[0][0], + to = east[east.length - 1][0]; const y = (grid.points[from][1] + grid.points[to][1]) / 2; - wind.append("text").attr("x", graphWidth - 52).attr("y", y).text("\u21C7"); + wind + .append("text") + .attr("x", graphWidth - 52) + .attr("y", y) + .text("\u21C7"); } } }); - if (northerly) wind.append("text").attr("x", graphWidth / 2).attr("y", 42).text("\u21CA"); - if (southerly) wind.append("text").attr("x", graphWidth / 2).attr("y", graphHeight - 20).text("\u21C8"); - }(); + if (northerly) + wind + .append("text") + .attr("x", graphWidth / 2) + .attr("y", 42) + .text("\u21CA"); + if (southerly) + wind + .append("text") + .attr("x", graphWidth / 2) + .attr("y", graphHeight - 20) + .text("\u21C8"); + })(); - TIME && console.timeEnd('generatePrecipitation'); + TIME && console.timeEnd("generatePrecipitation"); } // recalculate Voronoi Graph to pack cells function reGraph() { TIME && console.time("reGraph"); let {cells, points, features} = grid; - const newCells = {p:[], g:[], h:[]}; // to store new data + const newCells = {p: [], g: [], h: []}; // to store new data const spacing2 = grid.spacing ** 2; for (const i of cells.i) { const height = cells.h[i]; const type = cells.t[i]; if (height < 20 && type !== -1 && type !== -2) continue; // exclude all deep ocean points - if (type === -2 && (i%4=== 0 || features[cells.f[i]].type === "lake")) continue; // exclude non-coastal lake points + if (type === -2 && (i % 4 === 0 || features[cells.f[i]].type === "lake")) continue; // exclude non-coastal lake points const [x, y] = points[i]; addNewPoint(i, x, y, height); @@ -955,7 +1043,7 @@ function reGraph() { // add additional points for cells along coast if (type === 1 || type === -1) { if (cells.b[i]) continue; // not for near-border cells - cells.c[i].forEach(function(e) { + cells.c[i].forEach(function (e) { if (i > e) return; if (cells.t[e] === type) { const dist2 = (y - points[e][1]) ** 2 + (x - points[e][0]) ** 2; @@ -981,18 +1069,25 @@ function reGraph() { cells.q = d3.quadtree(cells.p.map((p, d) => [p[0], p[1], d])); // points quadtree for fast search cells.h = new Uint8Array(newCells.h); // heights cells.area = new Uint16Array(cells.i.length); // cell area - cells.i.forEach(i => cells.area[i] = Math.abs(d3.polygonArea(getPackPolygon(i)))); + cells.i.forEach(i => (cells.area[i] = Math.abs(d3.polygonArea(getPackPolygon(i))))); TIME && console.timeEnd("reGraph"); } // Detect and draw the coasline function drawCoastline() { - TIME && console.time('drawCoastline'); + TIME && console.time("drawCoastline"); reMarkFeatures(); - const cells = pack.cells, vertices = pack.vertices, n = cells.i.length, features = pack.features; + + const cells = pack.cells, + vertices = pack.vertices, + n = cells.i.length, + features = pack.features; const used = new Uint8Array(features.length); // store conneted features - const largestLand = d3.scan(features.map(f => f.land ? f.cells : 0), (a, b) => b - a); + const largestLand = d3.scan( + features.map(f => (f.land ? f.cells : 0)), + (a, b) => b - a + ); const landMask = defs.select("#land"); const waterMask = defs.select("#water"); lineGen.curve(d3.curveBasisClosed); @@ -1010,7 +1105,10 @@ function drawCoastline() { let vchain = connectVertices(start, type); if (features[f].type === "lake") relax(vchain, 1.2); used[f] = 1; - let points = clipPoly(vchain.map(v => vertices.p[v]), 1); + let points = clipPoly( + vchain.map(v => vertices.p[v]), + 1 + ); const area = d3.polygonArea(points); // area with lakes/islands if (area > 0 && features[f].type === "lake") { points = points.reverse(); @@ -1022,14 +1120,36 @@ function drawCoastline() { const path = round(lineGen(points)); if (features[f].type === "lake") { - landMask.append("path").attr("d", path).attr("fill", "black").attr("id", "land_"+f); + landMask + .append("path") + .attr("d", path) + .attr("fill", "black") + .attr("id", "land_" + f); // waterMask.append("path").attr("d", path).attr("fill", "white").attr("id", "water_"+id); // uncomment to show over lakes - lakes.select("#freshwater").append("path").attr("d", path).attr("id", "lake_"+f).attr("data-f", f); // draw the lake + lakes + .select("#freshwater") + .append("path") + .attr("d", path) + .attr("id", "lake_" + f) + .attr("data-f", f); // draw the lake } else { - landMask.append("path").attr("d", path).attr("fill", "white").attr("id", "land_"+f); - waterMask.append("path").attr("d", path).attr("fill", "black").attr("id", "water_"+f); + landMask + .append("path") + .attr("d", path) + .attr("fill", "white") + .attr("id", "land_" + f); + waterMask + .append("path") + .attr("d", path) + .attr("fill", "black") + .attr("id", "water_" + f); const g = features[f].group === "lake_island" ? "lake_island" : "sea_island"; - coastline.select("#"+g).append("path").attr("d", path).attr("id", "island_"+f).attr("data-f", f); // draw the coastline + coastline + .select("#" + g) + .append("path") + .attr("d", path) + .attr("id", "island_" + f) + .attr("data-f", f); // draw the coastline } // draw ruler to cover the biggest land piece @@ -1051,31 +1171,36 @@ function drawCoastline() { // 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 < 50000; i++) { - const prev = chain[chain.length-1]; // previous vertex in chain + for (let i = 0, current = start; i === 0 || (current !== start && i < 50000); 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 - const v = vertices.v[current] // neighboring vertices + const c = vertices.c[current]; // cells adjacent to vertex + const v = vertices.v[current]; // neighboring vertices const c0 = c[0] >= n || cells.t[c[0]] === t; const c1 = c[1] >= n || cells.t[c[1]] === t; const c2 = c[2] >= n || cells.t[c[2]] === t; - 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 (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; + } } return chain; } // move vertices that are too close to already added ones function relax(vchain, r) { - const p = vertices.p, tree = d3.quadtree(); + const p = vertices.p, + tree = d3.quadtree(); - for (let i=0; i < vchain.length; i++) { + for (let i = 0; i < vchain.length; i++) { const v = vchain[i]; let [x, y] = [p[v][0], p[v][1]]; - if (i && vchain[i+1] && tree.find(x, y, r) !== undefined) { - const v1 = vchain[i-1], v2 = vchain[i+1]; + if (i && vchain[i + 1] && tree.find(x, y, r) !== undefined) { + const v1 = vchain[i - 1], + v2 = vchain[i + 1]; const [x1, y1] = [p[v1][0], p[v1][1]]; const [x2, y2] = [p[v2][0], p[v2][1]]; [x, y] = [(x1 + x2) / 2, (y1 + y2) / 2]; @@ -1085,19 +1210,20 @@ function drawCoastline() { } } - TIME && console.timeEnd('drawCoastline'); + TIME && console.timeEnd("drawCoastline"); } // Re-mark features (ocean, lakes, islands) function reMarkFeatures() { TIME && console.time("reMarkFeatures"); - const cells = pack.cells, features = pack.features = [0]; + const cells = pack.cells, + features = (pack.features = [0]); cells.f = new Uint16Array(cells.i.length); // cell feature number cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast; - cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length);// cell haven (opposite water cell); + cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell); cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells); - for (let i=1, queue=[0]; queue[0] !== -1; i++) { + for (let i = 1, queue = [0]; queue[0] !== -1; i++) { const start = queue[0]; // first cell cells.f[start] = i; // assign feature number const land = cells.h[start] >= 20; @@ -1107,7 +1233,7 @@ function reMarkFeatures() { while (queue.length) { const q = queue.pop(); if (cells.b[q]) border = true; - cells.c[q].forEach(function(e) { + cells.c[q].forEach(function (e) { const eLand = cells.h[e] >= 20; if (land && !eLand) { cells.t[q] = 1; @@ -1134,6 +1260,9 @@ function reMarkFeatures() { queue[0] = cells.f.findIndex(f => !f); // find unmarked cell } + // markupPackLand + markup(pack.cells, 3, 1, 0); + function defineOceanGroup(number) { if (number > grid.cells.i.length / 25) return "ocean"; if (number > grid.cells.i.length / 100) return "sea"; @@ -1141,7 +1270,7 @@ function reMarkFeatures() { } function defineIslandGroup(cell, number) { - if (cell && features[cells.f[cell-1]].type === "lake") return "lake_island"; + if (cell && features[cells.f[cell - 1]].type === "lake") return "lake_island"; if (number > grid.cells.i.length / 10) return "continent"; if (number > grid.cells.i.length / 1000) return "island"; return "isle"; @@ -1153,7 +1282,10 @@ function reMarkFeatures() { // assign biome id for each cell function defineBiomes() { TIME && console.time("defineBiomes"); - const cells = pack.cells, f = pack.features, temp = grid.cells.temp, prec = grid.cells.prec; + const cells = pack.cells, + f = pack.features, + temp = grid.cells.temp, + prec = grid.cells.prec; cells.biome = new Uint8Array(cells.i.length); // biomes array for (const i of cells.i) { @@ -1166,7 +1298,10 @@ function defineBiomes() { function calculateMoisture(i) { let moist = prec[cells.g[i]]; if (cells.r[i]) moist += Math.max(cells.fl[i] / 20, 2); - const n = cells.c[i].filter(isLand).map(c => prec[cells.g[c]]).concat([moist]); + const n = cells.c[i] + .filter(isLand) + .map(c => prec[cells.g[c]]) + .concat([moist]); return rn(4 + d3.mean(n)); } @@ -1177,20 +1312,21 @@ function defineBiomes() { function getBiomeId(moisture, temperature, height) { if (temperature < -5) return 11; // permafrost biome, including sea ice if (height < 20) return 0; // marine biome: liquid water cells - if (moisture > 40 && temperature > -2 && (height < 25 || moisture > 24 && height > 24)) return 12; // wetland biome - const m = Math.min(moisture / 5 | 0, 4); // moisture band from 0 to 4 + if (moisture > 40 && temperature > -2 && (height < 25 || (moisture > 24 && height > 24))) return 12; // wetland biome + const m = Math.min((moisture / 5) | 0, 4); // moisture band from 0 to 4 const t = Math.min(Math.max(20 - temperature, 0), 25); // temparature band from 0 to 25 return biomesData.biomesMartix[m][t]; } // assess cells suitability to calculate population and rand cells for culture center and burgs placement function rankCells() { - TIME && console.time('rankCells'); + TIME && console.time("rankCells"); const {cells, features} = pack; cells.s = new Int16Array(cells.i.length); // cell suitability array cells.pop = new Float32Array(cells.i.length); // cell population array - const flMean = d3.median(cells.fl.filter(f => f)) || 0, flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux + const flMean = d3.median(cells.fl.filter(f => f)) || 0, + flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux const areaMean = d3.mean(cells.area); // to adjust population by cell area for (const i of cells.i) { @@ -1218,56 +1354,62 @@ function rankCells() { cells.s[i] = s / 5; // general population rate // cell rural population is suitability adjusted by cell area - cells.pop[i] = cells.s[i] > 0 ? cells.s[i] * cells.area[i] / areaMean : 0; + cells.pop[i] = cells.s[i] > 0 ? (cells.s[i] * cells.area[i]) / areaMean : 0; } - TIME && console.timeEnd('rankCells'); + TIME && console.timeEnd("rankCells"); } // generate some markers function addMarkers(number = 1) { if (!number) return; TIME && console.time("addMarkers"); - const cells = pack.cells, states = pack.states; + const cells = pack.cells, + states = pack.states; - void function addVolcanoes() { - let mounts = Array.from(cells.i).filter(i => cells.h[i] > 70).sort((a, b) => cells.h[b] - cells.h[a]); - let count = mounts.length < 10 ? 0 : Math.ceil(mounts.length / 300 * number); + void (function addVolcanoes() { + let mounts = Array.from(cells.i) + .filter(i => cells.h[i] > 70) + .sort((a, b) => cells.h[b] - cells.h[a]); + let count = mounts.length < 10 ? 0 : Math.ceil((mounts.length / 300) * number); if (count) addMarker("volcano", "🌋", 52, 50, 13); while (count && mounts.length) { - const cell = mounts.splice(biased(0, mounts.length-1, 5), 1); - const x = cells.p[cell][0], y = cells.p[cell][1]; + const cell = mounts.splice(biased(0, mounts.length - 1, 5), 1); + const x = cells.p[cell][0], + y = cells.p[cell][1]; const id = appendMarker(cell, "volcano"); const proper = Names.getCulture(cells.culture[cell]); - const name = P(.3) ? "Mount " + proper : Math.random() > .3 ? proper + " Volcano" : proper; - notes.push({id, name, legend:`Active volcano. Height: ${getFriendlyHeight([x, y])}`}); + const name = P(0.3) ? "Mount " + proper : Math.random() > 0.3 ? proper + " Volcano" : proper; + notes.push({id, name, legend: `Active volcano. Height: ${getFriendlyHeight([x, y])}`}); count--; } - }() + })(); - void function addHotSprings() { - let springs = Array.from(cells.i).filter(i => cells.h[i] > 50).sort((a, b) => cells.h[b]-cells.h[a]); - let count = springs.length < 30 ? 0 : Math.ceil(springs.length / 1000 * number); + void (function addHotSprings() { + let springs = Array.from(cells.i) + .filter(i => cells.h[i] > 50) + .sort((a, b) => cells.h[b] - cells.h[a]); + let count = springs.length < 30 ? 0 : Math.ceil((springs.length / 1000) * number); if (count) addMarker("hot_springs", "♨️", 50, 52, 12.5); while (count && springs.length) { - const cell = springs.splice(biased(1, springs.length-1, 3), 1); + const cell = springs.splice(biased(1, springs.length - 1, 3), 1); const id = appendMarker(cell, "hot_springs"); const proper = Names.getCulture(cells.culture[cell]); - const temp = convertTemperature(gauss(30,15,20,100)); - notes.push({id, name: proper + " Hot Springs", legend:`A hot springs area. Temperature: ${temp}`}); + const temp = convertTemperature(gauss(30, 15, 20, 100)); + notes.push({id, name: proper + " Hot Springs", legend: `A hot springs area. Temperature: ${temp}`}); count--; } - }() + })(); - void function addMines() { + void (function addMines() { let hills = Array.from(cells.i).filter(i => cells.h[i] > 47 && cells.burg[i]); - let count = !hills.length ? 0 : Math.ceil(hills.length / 7 * number); + let count = !hills.length ? 0 : Math.ceil((hills.length / 7) * number); if (!count) return; addMarker("mine", "⛏️", 48, 50, 13.5); - const resources = {"salt":5, "gold":2, "silver":4, "copper":2, "iron":3, "lead":1, "tin":1}; + const resources = {salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1}; while (count && hills.length) { const cell = hills.splice(Math.floor(Math.random() * hills.length), 1); @@ -1280,17 +1422,17 @@ function addMarkers(number = 1) { notes.push({id, name, legend}); count--; } - }() + })(); - void function addBridges() { + void (function addBridges() { const meanRoad = d3.mean(cells.road.filter(r => r)); const meanFlux = d3.mean(cells.fl.filter(fl => fl)); let bridges = Array.from(cells.i) .filter(i => cells.burg[i] && cells.h[i] >= 20 && cells.r[i] && cells.fl[i] > meanFlux && cells.road[i] > meanRoad) - .sort((a, b) => (cells.road[b] + cells.fl[b] / 10) - (cells.road[a] + cells.fl[a] / 10)); + .sort((a, b) => cells.road[b] + cells.fl[b] / 10 - (cells.road[a] + cells.fl[a] / 10)); - let count = !bridges.length ? 0 : Math.ceil(bridges.length / 12 * number); + let count = !bridges.length ? 0 : Math.ceil((bridges.length / 12) * number); if (count) addMarker("bridge", "🌉", 50, 50, 14); while (count && bridges.length) { @@ -1299,14 +1441,14 @@ function addMarkers(number = 1) { const burg = pack.burgs[cells.burg[cell]]; const river = pack.rivers.find(r => r.i === pack.cells.r[cell]); const riverName = river ? `${river.name} ${river.type}` : "river"; - const name = river && P(.2) ? river.name : burg.name; - notes.push({id, name:`${name} Bridge`, legend:`A stone bridge over the ${riverName} near ${burg.name}`}); + const name = river && P(0.2) ? river.name : burg.name; + notes.push({id, name: `${name} Bridge`, legend: `A stone bridge over the ${riverName} near ${burg.name}`}); count--; } - }() + })(); - void function addInns() { - const maxRoad = d3.max(cells.road) * .9; + void (function addInns() { + const maxRoad = d3.max(cells.road) * 0.9; let taverns = Array.from(cells.i).filter(i => cells.crossroad[i] && cells.h[i] >= 20 && cells.road[i] > maxRoad); if (!taverns.length) return; const count = Math.ceil(4 * number); @@ -1316,45 +1458,46 @@ function addMarkers(number = 1) { const animal = ["Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "Crane", "Crocodile", "Crow", "Deer", "Dog", "Eagle", "Elk", "Fox", "Goat", "Goose", "Hare", "Hawk", "Heron", "Horse", "Hyena", "Ibis", "Jackal", "Jaguar", "Lark", "Leopard", "Lion", "Mantis", "Marten", "Moose", "Mule", "Narwhal", "Owl", "Panther", "Rat", "Raven", "Rook", "Scorpion", "Shark", "Sheep", "Snake", "Spider", "Swan", "Tiger", "Turtle", "Wolf", "Wolverine", "Camel", "Falcon", "Hound", "Ox"]; const adj = ["New", "Good", "High", "Old", "Great", "Big", "Major", "Happy", "Main", "Huge", "Far", "Beautiful", "Fair", "Prime", "Ancient", "Golden", "Proud", "Lucky", "Fat", "Honest", "Giant", "Distant", "Friendly", "Loud", "Hungry", "Magical", "Superior", "Peaceful", "Frozen", "Divine", "Favorable", "Brave", "Sunny", "Flying"]; - for (let i=0; i < taverns.length && i < count; i++) { + for (let i = 0; i < taverns.length && i < count; i++) { const cell = taverns.splice(Math.floor(Math.random() * taverns.length), 1); const id = appendMarker(cell, "inn"); - const type = P(.3) ? "inn" : "tavern"; - const name = P(.5) ? ra(color) + " " + ra(animal) : P(.6) ? ra(adj) + " " + ra(animal) : ra(adj) + " " + capitalize(type); - notes.push({id, name: "The " + name, legend:`A big and famous roadside ${type}`}); + const type = P(0.3) ? "inn" : "tavern"; + const name = P(0.5) ? ra(color) + " " + ra(animal) : P(0.6) ? ra(adj) + " " + ra(animal) : ra(adj) + " " + capitalize(type); + notes.push({id, name: "The " + name, legend: `A big and famous roadside ${type}`}); } - }() + })(); - void function addLighthouses() { + void (function addLighthouses() { const lands = cells.i.filter(i => cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && cells.road[c])); const lighthouses = Array.from(lands).map(i => [i, cells.v[i][cells.c[i].findIndex(c => cells.h[c] < 20 && cells.road[c])]]); if (lighthouses.length) addMarker("lighthouse", "🚨", 50, 50, 16); const count = Math.ceil(4 * number); - for (let i=0; i < lighthouses.length && i < count; i++) { - const cell = lighthouses[i][0], vertex = lighthouses[i][1]; + for (let i = 0; i < lighthouses.length && i < count; i++) { + const cell = lighthouses[i][0], + vertex = lighthouses[i][1]; const id = appendMarker(cell, "lighthouse"); const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); - notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend:`A lighthouse to keep the navigation safe`}); + notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to keep the navigation safe`}); } - }() + })(); - void function addWaterfalls() { + void (function addWaterfalls() { const waterfalls = cells.i.filter(i => cells.r[i] && cells.h[i] > 70); if (waterfalls.length) addMarker("waterfall", "⟱", 50, 54, 16.5); const count = Math.ceil(3 * number); - for (let i=0; i < waterfalls.length && i < count; i++) { + for (let i = 0; i < waterfalls.length && i < count; i++) { const cell = waterfalls[i]; const id = appendMarker(cell, "waterfall"); const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); - notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend:`An extremely beautiful waterfall`}); + notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend: `An extremely beautiful waterfall`}); } - }() + })(); - void function addBattlefields() { + void (function addBattlefields() { let battlefields = Array.from(cells.i).filter(i => cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25); - let count = battlefields.length < 100 ? 0 : Math.ceil(battlefields.length / 500 * number); + let count = battlefields.length < 100 ? 0 : Math.ceil((battlefields.length / 500) * number); if (count) addMarker("battlefield", "⚔️", 50, 52, 12); while (count && battlefields.length) { @@ -1367,28 +1510,48 @@ function addMarkers(number = 1) { notes.push({id, name, legend}); count--; } - }() + })(); function addMarker(id, icon, x, y, size) { const markers = svg.select("#defs-markers"); - if (markers.select("#marker_"+id).size()) return; + if (markers.select("#marker_" + id).size()) return; - const symbol = markers.append("symbol").attr("id", "marker_"+id).attr("viewBox", "0 0 30 30"); + const symbol = markers + .append("symbol") + .attr("id", "marker_" + id) + .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", x+"%").attr("y", y+"%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0) - .attr("font-size", size+"px").attr("dominant-baseline", "central").text(icon); + symbol + .append("text") + .attr("x", x + "%") + .attr("y", y + "%") + .attr("fill", "#000000") + .attr("stroke", "#3200ff") + .attr("stroke-width", 0) + .attr("font-size", size + "px") + .attr("dominant-baseline", "central") + .text(icon); } function appendMarker(cell, type) { - const x = cells.p[cell][0], y = cells.p[cell][1]; + const x = cells.p[cell][0], + y = cells.p[cell][1]; const id = getNextId("markerElement"); const name = "#marker_" + type; - markers.append("use").attr("id", id) - .attr("xlink:href", name).attr("data-id", name) - .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30) - .attr("data-size", 1).attr("width", 30).attr("height", 30); + markers + .append("use") + .attr("id", id) + .attr("xlink:href", name) + .attr("data-id", name) + .attr("data-x", x) + .attr("data-y", y) + .attr("x", x - 15) + .attr("y", y - 30) + .attr("data-size", 1) + .attr("width", 30) + .attr("height", 30); return id; } @@ -1399,20 +1562,23 @@ function addMarkers(number = 1) { // regenerate some zones function addZones(number = 1) { TIME && console.time("addZones"); - const data = [], cells = pack.cells, states = pack.states, burgs = pack.burgs; + const data = [], + cells = pack.cells, + states = pack.states, + burgs = pack.burgs; const used = new Uint8Array(cells.i.length); // to store used cells - for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addInvasion(); // invasion of enemy lands - for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addRebels(); // rebels along a state border - for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addProselytism(); // proselitism of organized religion - for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addCrusade(); // crusade on heresy lands - for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addDisease(); // disease starting in a random city - for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addDisaster(); // disaster starting in a random city - for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addEruption(); // volcanic eruption aroung volcano - for (let i=0; i < rn(Math.random() * 1.0 * number); i++) addAvalanche(); // avalanche impacting highland road - for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addFault(); // fault line in elevated areas - for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addFlood() // flood on river banks - for (let i=0; i < rn(Math.random() * 1.2 * number); i++) addTsunami() // tsunami starting near coast + for (let i = 0; i < rn(Math.random() * 1.8 * number); i++) addInvasion(); // invasion of enemy lands + for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addRebels(); // rebels along a state border + for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addProselytism(); // proselitism of organized religion + for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addCrusade(); // crusade on heresy lands + for (let i = 0; i < rn(Math.random() * 1.8 * number); i++) addDisease(); // disease starting in a random city + for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addDisaster(); // disaster starting in a random city + for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addEruption(); // volcanic eruption aroung volcano + for (let i = 0; i < rn(Math.random() * 1.0 * number); i++) addAvalanche(); // avalanche impacting highland road + for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addFault(); // fault line in elevated areas + for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addFlood(); // flood on river banks + for (let i = 0; i < rn(Math.random() * 1.2 * number); i++) addTsunami(); // tsunami starting near coast function addInvasion() { const atWar = states.filter(s => s.diplomacy && s.diplomacy.some(d => d === "Enemy")); @@ -1424,10 +1590,12 @@ function addZones(number = 1) { const cell = ra(cells.i.filter(i => cells.state[i] === target && cells.c[i].some(c => cells.state[c] === invader.i))); if (!cell) return; - const cellsArray = [], queue = [cell], power = rand(5, 30); + const cellsArray = [], + queue = [cell], + power = rand(5, 30); while (queue.length) { - const q = P(.4) ? queue.shift() : queue.pop(); + const q = P(0.4) ? queue.shift() : queue.pop(); cellsArray.push(q); if (cellsArray.length > power) break; @@ -1439,10 +1607,9 @@ function addZones(number = 1) { }); } - const invasion = rw({"Invasion":4, "Occupation":3, "Raid":2, "Conquest":2, - "Subjugation":1, "Foray":1, "Skirmishes":1, "Incursion":2, "Pillaging":1, "Intervention":1}); + const invasion = rw({Invasion: 4, Occupation: 3, Raid: 2, Conquest: 2, Subjugation: 1, Foray: 1, Skirmishes: 1, Incursion: 2, Pillaging: 1, Intervention: 1}); const name = getAdjective(invader.name) + " " + invasion; - data.push({name, type:"Invasion", cells:cellsArray, fill:"url(#hatch1)"}); + data.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"}); } function addRebels() { @@ -1451,7 +1618,9 @@ function addZones(number = 1) { const neib = ra(state.neighbors.filter(n => n)); const cell = cells.i.find(i => cells.state[i] === state.i && cells.c[i].some(c => cells.state[c] === neib)); - const cellsArray = [], queue = [cell], power = rand(10, 30); + const cellsArray = [], + queue = [cell], + power = rand(10, 30); while (queue.length) { const q = queue.shift(); @@ -1462,15 +1631,14 @@ function addZones(number = 1) { if (used[e]) return; if (cells.state[e] !== state.i) return; used[e] = 1; - if (e%4 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return; + if (e % 4 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return; queue.push(e); }); } - const rebels = rw({"Rebels":5, "Insurgents":2, "Mutineers":1, "Rioters":1, "Separatists":1, - "Secessionists":1, "Insurrection":2, "Rebellion":1, "Conspiracy":2}); + const rebels = rw({Rebels: 5, Insurgents: 2, Mutineers: 1, Rioters: 1, Separatists: 1, Secessionists: 1, Insurrection: 2, Rebellion: 1, Conspiracy: 2}); const name = getAdjective(states[neib].name) + " " + rebels; - data.push({name, type:"Rebels", cells:cellsArray, fill:"url(#hatch3)"}); + data.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"}); } function addProselytism() { @@ -1480,7 +1648,9 @@ function addZones(number = 1) { const cell = ra(cells.i.filter(i => cells.religion[i] && cells.religion[i] !== organized.i && cells.c[i].some(c => cells.religion[c] === organized.i))); if (!cell) return; const target = cells.religion[cell]; - const cellsArray = [], queue = [cell], power = rand(10, 30); + const cellsArray = [], + queue = [cell], + power = rand(10, 30); while (queue.length) { const q = queue.shift(); @@ -1498,7 +1668,7 @@ function addZones(number = 1) { } const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism"; - data.push({name, type:"Proselytism", cells:cellsArray, fill:"url(#hatch6)"}); + data.push({name, type: "Proselytism", cells: cellsArray, fill: "url(#hatch6)"}); } function addCrusade() { @@ -1507,26 +1677,28 @@ function addZones(number = 1) { const cellsArray = cells.i.filter(i => !used[i] && cells.religion[i] === heresy.i); if (!cellsArray.length) return; - cellsArray.forEach(i => used[i] = 1); + cellsArray.forEach(i => (used[i] = 1)); const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade"; - data.push({name, type:"Crusade", cells:cellsArray, fill:"url(#hatch6)"}); + data.push({name, type: "Crusade", cells: cellsArray, fill: "url(#hatch6)"}); } function addDisease() { const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg if (!burg) return; - const cellsArray = [], cost = [], power = rand(20, 37); + const cellsArray = [], + cost = [], + power = rand(20, 37); const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); - queue.queue({e:burg.cell, p:0}); + queue.queue({e: burg.cell, p: 0}); while (queue.length) { const next = queue.dequeue(); if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e); used[next.e] = 1; - cells.c[next.e].forEach(function(e) { + cells.c[next.e].forEach(function (e) { const r = cells.road[next.e]; const c = r ? Math.max(10 - r, 1) : 100; const p = next.p + c; @@ -1543,25 +1715,27 @@ function addZones(number = 1) { const animal = () => ra(["Ape", "Bear", "Boar", "Cat", "Cow", "Dog", "Pig", "Fox", "Bird", "Horse", "Rat", "Raven", "Sheep", "Spider", "Wolf"]); const color = () => ra(["Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]); - const type = rw({"Fever":5, "Pestilence":2, "Flu":2, "Pox":2, "Smallpox":2, "Plague":4, "Cholera":2, "Dropsy":1, "Leprosy":2}); - const name = rw({[color()]:4, [animal()]:2, [adjective()]:1}) + " " + type; - data.push({name, type:"Disease", cells:cellsArray, fill:"url(#hatch12)"}); + const type = rw({Fever: 5, Pestilence: 2, Flu: 2, Pox: 2, Smallpox: 2, Plague: 4, Cholera: 2, Dropsy: 1, Leprosy: 2}); + const name = rw({[color()]: 4, [animal()]: 2, [adjective()]: 1}) + " " + type; + data.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"}); } function addDisaster() { const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg if (!burg) return; - const cellsArray = [], cost = [], power = rand(5, 25); + const cellsArray = [], + cost = [], + power = rand(5, 25); const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); - queue.queue({e:burg.cell, p:0}); + queue.queue({e: burg.cell, p: 0}); while (queue.length) { const next = queue.dequeue(); if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e); used[next.e] = 1; - cells.c[next.e].forEach(function(e) { + cells.c[next.e].forEach(function (e) { const c = rand(1, 10); const p = next.p + c; if (p > power) return; @@ -1573,26 +1747,30 @@ function addZones(number = 1) { }); } - const type = rw({"Famine":5, "Dearth":1, "Drought":3, "Earthquake":3, "Tornadoes":1, "Wildfires":1}); + const type = rw({Famine: 5, Dearth: 1, Drought: 3, Earthquake: 3, Tornadoes: 1, Wildfires: 1}); const name = getAdjective(burg.name) + " " + type; - data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch5)"}); + data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"}); } function addEruption() { const volcano = document.getElementById("markers").querySelector("use[data-id='#marker_volcano']"); if (!volcano) return; - const x = +volcano.dataset.x, y = +volcano.dataset.y, cell = findCell(x, y); + const x = +volcano.dataset.x, + y = +volcano.dataset.y, + cell = findCell(x, y); const id = volcano.id; const note = notes.filter(n => n.id === id); if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano"); const name = note[0] ? note[0].name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption"; - const cellsArray = [], queue = [cell], power = rand(10, 30); + const cellsArray = [], + queue = [cell], + power = rand(10, 30); while (queue.length) { - const q = P(.5) ? queue.shift() : queue.pop(); + const q = P(0.5) ? queue.shift() : queue.pop(); cellsArray.push(q); if (cellsArray.length > power) break; cells.c[q].forEach(e => { @@ -1602,7 +1780,7 @@ function addZones(number = 1) { }); } - data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch7)"}); + data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch7)"}); } function addAvalanche() { @@ -1610,10 +1788,12 @@ function addZones(number = 1) { if (!roads.length) return; const cell = +ra(roads); - const cellsArray = [], queue = [cell], power = rand(3, 15); + const cellsArray = [], + queue = [cell], + power = rand(3, 15); while (queue.length) { - const q = P(.3) ? queue.shift() : queue.pop(); + const q = P(0.3) ? queue.shift() : queue.pop(); cellsArray.push(q); if (cellsArray.length > power) break; cells.c[q].forEach(e => { @@ -1625,7 +1805,7 @@ function addZones(number = 1) { const proper = getAdjective(Names.getCultureShort(cells.culture[cell])); const name = proper + " Avalanche"; - data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch5)"}); + data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"}); } function addFault() { @@ -1633,7 +1813,9 @@ function addZones(number = 1) { if (!elevated.length) return; const cell = ra(elevated); - const cellsArray = [], queue = [cell], power = rand(3, 15); + const cellsArray = [], + queue = [cell], + power = rand(3, 15); while (queue.length) { const q = queue.pop(); @@ -1648,16 +1830,22 @@ function addZones(number = 1) { const proper = getAdjective(Names.getCultureShort(cells.culture[cell])); const name = proper + " Fault"; - data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch2)"}); + data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch2)"}); } function addFlood() { - const fl = cells.fl.filter(fl => fl), meanFlux = d3.mean(fl), maxFlux = d3.max(fl), flux = (maxFlux - meanFlux) / 2 + meanFlux; + const fl = cells.fl.filter(fl => fl), + meanFlux = d3.mean(fl), + maxFlux = d3.max(fl), + flux = (maxFlux - meanFlux) / 2 + meanFlux; const rivers = cells.i.filter(i => !used[i] && cells.h[i] < 50 && cells.r[i] && cells.fl[i] > flux && cells.burg[i]); if (!rivers.length) return; - const cell = +ra(rivers), river = cells.r[cell]; - const cellsArray = [], queue = [cell], power = rand(5, 30); + const cell = +ra(rivers), + river = cells.r[cell]; + const cellsArray = [], + queue = [cell], + power = rand(5, 30); while (queue.length) { const q = queue.pop(); @@ -1672,7 +1860,7 @@ function addZones(number = 1) { } const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood"; - data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch13)"}); + data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"}); } function addTsunami() { @@ -1680,7 +1868,9 @@ function addZones(number = 1) { if (!coastal.length) return; const cell = +ra(coastal); - const cellsArray = [], queue = [cell], power = rand(10, 30); + const cellsArray = [], + queue = [cell], + power = rand(10, 30); while (queue.length) { const q = queue.shift(); @@ -1698,16 +1888,29 @@ function addZones(number = 1) { const proper = getAdjective(Names.getCultureShort(cells.culture[cell])); const name = proper + " Tsunami"; - data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch13)"}); + data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"}); } - void function drawZones() { - zones.selectAll("g").data(data).enter().append("g") - .attr("id", (d, i) => "zone"+i).attr("data-description", d => d.name).attr("data-type", d => d.type) - .attr("data-cells", d => d.cells.join(",")).attr("fill", d => d.fill) - .selectAll("polygon").data(d => d.cells).enter().append("polygon") - .attr("points", d => getPackPolygon(d)).attr("id", function(d) {return this.parentNode.id+"_"+d}); - }() + void (function drawZones() { + zones + .selectAll("g") + .data(data) + .enter() + .append("g") + .attr("id", (d, i) => "zone" + i) + .attr("data-description", d => d.name) + .attr("data-type", d => d.type) + .attr("data-cells", d => d.cells.join(",")) + .attr("fill", d => d.fill) + .selectAll("polygon") + .data(d => d.cells) + .enter() + .append("polygon") + .attr("points", d => getPackPolygon(d)) + .attr("id", function (d) { + return this.parentNode.id + "_" + d; + }); + })(); TIME && console.timeEnd("addZones"); } @@ -1722,19 +1925,19 @@ function showStatistics() { Points: ${grid.points.length} Cells: ${pack.cells.i.length} Map size: ${mapSizeOutput.value}% - States: ${pack.states.length-1} - Provinces: ${pack.provinces.length-1} - Burgs: ${pack.burgs.length-1} - Religions: ${pack.religions.length-1} + States: ${pack.states.length - 1} + Provinces: ${pack.provinces.length - 1} + Burgs: ${pack.burgs.length - 1} + Religions: ${pack.religions.length - 1} Culture set: ${culturesSet.selectedOptions[0].innerText} - Cultures: ${pack.cultures.length-1}`; + Cultures: ${pack.cultures.length - 1}`; mapId = Date.now(); // unique map id is it's creation date number - mapHistory.push({seed, width:graphWidth, height:graphHeight, template, created:mapId}); + mapHistory.push({seed, width: graphWidth, height: graphHeight, template, created: mapId}); INFO && console.log(stats); } -const regenerateMap = debounce(function() { +const regenerateMap = debounce(function () { WARN && console.warn("Generate new random map"); closeDialogs("#worldConfigurator, #options3d"); customization = 0; @@ -1749,7 +1952,10 @@ const regenerateMap = debounce(function() { // clear the map function undraw() { viewbox.selectAll("path, circle, polygon, line, text, use, #zones > g, #armies > g, #ruler > g").remove(); - document.getElementById("deftemp").querySelectorAll("path, clipPath, svg").forEach(el => el.remove()); + document + .getElementById("deftemp") + .querySelectorAll("path, clipPath, svg") + .forEach(el => el.remove()); document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems notes = []; rulers = new Rulers(); diff --git a/modules/lakes.js b/modules/lakes.js index 7138cbf8..f4bc8c6f 100644 --- a/modules/lakes.js +++ b/modules/lakes.js @@ -1,90 +1,110 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Lakes = factory()); -}(this, (function () {'use strict'; + typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Lakes = factory()); +})(this, function () { + "use strict"; -const setClimateData = function(h) { - const cells = pack.cells; - const lakeOutCells = new Uint16Array(cells.i.length); + const setClimateData = function (h) { + const cells = pack.cells; + const lakeOutCells = new Uint16Array(cells.i.length); - pack.features.forEach(f => { - if (f.type !== "lake") return; + pack.features.forEach(f => { + if (f.type !== "lake") return; - // default flux: sum of precipition around lake first cell - f.flux = rn(d3.sum(f.shoreline.map(c => grid.cells.prec[cells.g[c]])) / 2); - - // temperature and evaporation to detect closed lakes - f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1); - const height = (f.height - 18) ** heightExponentInput.value; // height in meters - const evaporation = (700 * (f.temp + .006 * height) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11] - f.evaporation = rn(evaporation * f.cells); + // default flux: sum of precipition around lake first cell + f.flux = rn(d3.sum(f.shoreline.map(c => grid.cells.prec[cells.g[c]])) / 2); - // lake outlet cell - f.outCell = f.shoreline[d3.scan(f.shoreline, (a,b) => h[a] - h[b])]; - lakeOutCells[f.outCell] = f.i; - }); + // temperature and evaporation to detect closed lakes + f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1); + const height = (f.height - 18) ** heightExponentInput.value; // height in meters + const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11] + f.evaporation = rn(evaporation * f.cells); - return lakeOutCells; -} + // no outlet for lakes in depressed areas + if (f.closed) return; -const cleanupLakeData = function() { - for (const feature of pack.features) { - if (feature.type !== "lake") continue; - delete feature.river; - delete feature.enteringFlux; - delete feature.shoreline; - delete feature.outCell; - feature.height = rn(feature.height); + // lake outlet cell + f.outCell = f.shoreline[d3.scan(f.shoreline, (a, b) => h[a] - h[b])]; + lakeOutCells[f.outCell] = f.i; + }); - const inlets = feature.inlets?.filter(r => pack.rivers.find(river => river.i === r)); - if (!inlets || !inlets.length) delete feature.inlets; - else feature.inlets = inlets; + return lakeOutCells; + }; - const outlet = feature.outlet && pack.rivers.find(river => river.i === feature.outlet); - if (!outlet) delete feature.outlet; - } -} + // get array of land cells aroound lake + const getShoreline = function (lake) { + const queue = [lake.firstCell]; + const used = [queue[0]]; + const landCellsAround = []; + while (queue.length) { + const q = queue.pop(); + for (const c of pack.cells.c[q]) { + if (used[c]) continue; + used[c] = true; + if (pack.cells.f[c] === lake.i) queue.push(c); + if (pack.cells.h[c] >= 20) landCellsAround.push(c); + } + } -const defineGroup = function() { - for (const feature of pack.features) { - if (feature.type !== "lake") continue; - const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node(); - if (!lakeEl) continue; + lake.shoreline = landCellsAround; + }; - feature.group = getGroup(feature); - document.getElementById(feature.group).appendChild(lakeEl); - } -} + const cleanupLakeData = function () { + for (const feature of pack.features) { + if (feature.type !== "lake") continue; + delete feature.river; + delete feature.enteringFlux; + delete feature.shoreline; + delete feature.outCell; + delete feature.closed; + feature.height = rn(feature.height); -const generateName = function() { - Math.random = aleaPRNG(seed); - for (const feature of pack.features) { - if (feature.type !== "lake") continue; - feature.name = getName(feature); - } -} + const inlets = feature.inlets?.filter(r => pack.rivers.find(river => river.i === r)); + if (!inlets || !inlets.length) delete feature.inlets; + else feature.inlets = inlets; -const getName = function(feature) { - const landCell = pack.cells.c[feature.firstCell].find(c => pack.cells.h[c] >= 20); - const culture = pack.cells.culture[landCell]; - return Names.getCulture(culture); -} + const outlet = feature.outlet && pack.rivers.find(river => river.i === feature.outlet); + if (!outlet) delete feature.outlet; + } + }; -function getGroup(feature) { - if (feature.temp < -3) return "frozen"; - if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 5 === 0) return "lava"; + const defineGroup = function () { + for (const feature of pack.features) { + if (feature.type !== "lake") continue; + const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node(); + if (!lakeEl) continue; - if (!feature.inlets && !feature.outlet) { - if (feature.evaporation / 2 > feature.flux) return "dry"; - if (feature.cells < 3 && feature.firstCell % 5 === 0) return "sinkhole"; + feature.group = getGroup(feature); + document.getElementById(feature.group).appendChild(lakeEl); + } + }; + + const generateName = function () { + Math.random = aleaPRNG(seed); + for (const feature of pack.features) { + if (feature.type !== "lake") continue; + feature.name = getName(feature); + } + }; + + const getName = function (feature) { + const landCell = pack.cells.c[feature.firstCell].find(c => pack.cells.h[c] >= 20); + const culture = pack.cells.culture[landCell]; + return Names.getCulture(culture); + }; + + function getGroup(feature) { + if (feature.temp < -3) return "frozen"; + if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 5 === 0) return "lava"; + + if (!feature.inlets && !feature.outlet) { + if (feature.evaporation / 2 > feature.flux) return "dry"; + if (feature.cells < 3 && feature.firstCell % 5 === 0) return "sinkhole"; + } + + if (!feature.outlet && feature.evaporation > feature.flux) return "salt"; + + return "freshwater"; } - if (!feature.outlet && feature.evaporation > feature.flux) return "salt"; - - return "freshwater"; -} - -return {setClimateData, cleanupLakeData, defineGroup, generateName, getName}; - -}))); \ No newline at end of file + return {setClimateData, cleanupLakeData, defineGroup, generateName, getName, getShoreline}; +}); diff --git a/modules/river-generator.js b/modules/river-generator.js index e231871e..28e29dd9 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -1,355 +1,389 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Rivers = factory()); -}(this, (function () {'use strict'; + typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Rivers = factory()); +})(this, function () { + "use strict"; -const generate = function(changeHeights = true) { - TIME && console.time('generateRivers'); - Math.random = aleaPRNG(seed); - const cells = pack.cells, p = cells.p, features = pack.features; + const generate = function (changeHeights = true) { + TIME && console.time("generateRivers"); + Math.random = aleaPRNG(seed); + const cells = pack.cells, + p = cells.p, + features = pack.features; - const riversData = []; // rivers data - cells.fl = new Uint16Array(cells.i.length); // water flux array - cells.r = new Uint16Array(cells.i.length); // rivers array - cells.conf = new Uint8Array(cells.i.length); // confluences array - let riverNext = 1; // first river id is 1 + const riversData = []; // rivers data + cells.fl = new Uint16Array(cells.i.length); // water flux array + cells.r = new Uint16Array(cells.i.length); // rivers array + cells.conf = new Uint8Array(cells.i.length); // confluences array + let riverNext = 1; // first river id is 1 - const h = alterHeights(); - removeStoredLakeData(); - resolveDepressions(h); - drainWater(); - defineRivers(); - Lakes.cleanupLakeData(); + const h = alterHeights(); + prepareLakeData(); + resolveDepressions(h, 200); + drainWater(); + defineRivers(); + Lakes.cleanupLakeData(); - if (changeHeights) cells.h = Uint8Array.from(h); // apply changed heights as basic one + if (changeHeights) cells.h = Uint8Array.from(h); // apply changed heights as basic one - TIME && console.timeEnd('generateRivers'); + TIME && console.timeEnd("generateRivers"); - // height with added t value to make map less depressed - function alterHeights() { - const h = Array.from(cells.h) - .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100) - .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000); - return h; - } + function prepareLakeData() { + features.forEach(f => { + if (f.type !== "lake") return; + delete f.flux; + delete f.inlets; + delete f.outlet; + delete f.height; + !f.shoreline && Lakes.getShoreline(f); + }); + } - function removeStoredLakeData() { - features.forEach(f => { - delete f.flux; - delete f.inlets; - delete f.outlet; - delete f.height; - }); - } + function drainWater() { + const MIN_FLUX_TO_FORM_RIVER = 30; + const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]); + const lakeOutCells = Lakes.setClimateData(h); - function drainWater() { - const MIN_FLUX_TO_FORM_RIVER = 30; - const land = cells.i.filter(i => h[i] >= 20).sort((a,b) => h[b] - h[a]); - const lakeOutCells = Lakes.setClimateData(h); + land.forEach(function (i) { + cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation + const [x, y] = p[i]; - land.forEach(function(i) { - cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation - const x = p[i][0], y = p[i][1]; + // create lake outlet if lake is not in deep depression and flux > evaporation + const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : []; + for (const lake of lakes) { + const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i); - // create lake outlet if flux > evaporation - const lakes = !lakeOutCells[i] ? [] : features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation); - for (const lake of lakes) { - const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i); + cells.fl[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet - cells.fl[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet - - // allow chain lakes to retain identity - if (cells.r[lakeCell] !== lake.river) { - const sameRiver = cells.c[lakeCell].some(c => cells.r[c] === lake.river); - if (sameRiver) { - cells.r[lakeCell] = lake.river; - riversData.push({river: lake.river, cell: lakeCell, x: p[lakeCell][0], y: p[lakeCell][1], flux: cells.fl[lakeCell]}); - } else { - cells.r[lakeCell] = riverNext; - riversData.push({river: riverNext, cell: lakeCell, x: p[lakeCell][0], y: p[lakeCell][1], flux: cells.fl[lakeCell]}); - riverNext++; + // allow chain lakes to retain identity + if (cells.r[lakeCell] !== lake.river) { + const sameRiver = cells.c[lakeCell].some(c => cells.r[c] === lake.river); + if (sameRiver) { + cells.r[lakeCell] = lake.river; + riversData.push({river: lake.river, cell: lakeCell, x: p[lakeCell][0], y: p[lakeCell][1], flux: cells.fl[lakeCell]}); + } else { + cells.r[lakeCell] = riverNext; + riversData.push({river: riverNext, cell: lakeCell, x: p[lakeCell][0], y: p[lakeCell][1], flux: cells.fl[lakeCell]}); + riverNext++; + } } + + lake.outlet = cells.r[lakeCell]; + flowDown(i, cells.fl[i], cells.fl[lakeCell], lake.outlet); } - lake.outlet = cells.r[lakeCell]; - flowDown(i, cells.fl[i], cells.fl[lakeCell], lake.outlet); + // assign all tributary rivers to outlet basin + for (let outlet = lakes[0]?.outlet, l = 0; l < lakes.length; l++) { + lakes[l].inlets?.forEach(fork => (riversData.find(r => r.river === fork).parent = outlet)); + } + + // near-border cell: pour water out of the screen + if (cells.b[i] && cells.r[i]) { + let to = []; + const min = Math.min(y, graphHeight - y, x, graphWidth - x); + if (min === y) to = [x, 0]; + else if (min === graphHeight - y) to = [x, graphHeight]; + else if (min === x) to = [0, y]; + else if (min === graphWidth - x) to = [graphWidth, y]; + riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1], flux: cells.fl[i]}); + return; + } + + // downhill cell (make sure it's not in the source lake) + const min = lakeOutCells[i] ? cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c])).sort((a, b) => h[a] - h[b])[0] : cells.c[i].sort((a, b) => h[a] - h[b])[0]; + + if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) { + if (h[min] >= 20) cells.fl[min] += cells.fl[i]; + return; // flux is too small to operate as river + } + + // proclaim a new river + if (!cells.r[i]) { + cells.r[i] = riverNext; + riversData.push({river: riverNext, cell: i, x, y, flux: cells.fl[i]}); + riverNext++; + } + + flowDown(min, cells.fl[min], cells.fl[i], cells.r[i], i); + }); + } + + function flowDown(toCell, toFlux, fromFlux, river, fromCell = 0) { + if (cells.r[toCell]) { + // downhill cell already has river assigned + if (toFlux < fromFlux) { + cells.conf[toCell] = cells.fl[toCell]; // mark confluence + if (h[toCell] >= 20) riversData.find(r => r.river === cells.r[toCell]).parent = river; // min river is a tributary of current river + cells.r[toCell] = river; // re-assign river if downhill part has less flux + } else { + cells.conf[toCell] += fromFlux; // mark confluence + if (h[toCell] >= 20) riversData.find(r => r.river === river).parent = cells.r[toCell]; // current river is a tributary of min river + } + } else cells.r[toCell] = river; // assign the river to the downhill cell + + if (h[toCell] < 20) { + // pour water to the water body + const haven = fromCell ? cells.haven[fromCell] : toCell; + riversData.push({river, cell: haven, x: p[toCell][0], y: p[toCell][1], flux: fromFlux}); + + const waterBody = features[cells.f[toCell]]; + if (waterBody.type === "lake") { + if (!waterBody.river || fromFlux > waterBody.enteringFlux) { + waterBody.river = river; + waterBody.enteringFlux = fromFlux; + } + waterBody.flux = waterBody.flux + fromFlux; + waterBody.inlets ? waterBody.inlets.push(river) : (waterBody.inlets = [river]); + } + } else { + // propagate flux and add next river segment + cells.fl[toCell] += fromFlux; + riversData.push({river, cell: toCell, x: p[toCell][0], y: p[toCell][1], flux: fromFlux}); + } + } + + function defineRivers() { + cells.r = new Uint16Array(cells.i.length); // re-initiate rivers array + pack.rivers = []; // rivers data + const riverPaths = []; + + for (let r = 1; r <= riverNext; r++) { + const riverSegments = riversData.filter(d => d.river === r); + if (riverSegments.length < 3) continue; + + for (const segment of riverSegments) { + const i = segment.cell; + if (cells.r[i]) continue; + if (cells.h[i] < 20) continue; + cells.r[i] = r; + } + + const source = riverSegments[0].cell; + const mouth = riverSegments[riverSegments.length - 2].cell; + + const widthFactor = rn(0.8 + Math.random() * 0.4, 1); // river width modifier [.8, 1.2] + const sourceWidth = cells.h[source] >= 20 ? 0.1 : rn(Math.min(Math.max((cells.fl[source] / 500) ** 0.4, 0.5), 1.7), 2); + + const riverMeandered = addMeandering(riverSegments, sourceWidth * 10, 0.5); + const [path, length, offset] = getPath(riverMeandered, widthFactor, sourceWidth); + riverPaths.push([path, r]); + + const parent = riverSegments[0].parent || 0; + const width = rn(offset ** 2, 2); // mounth width in km + const discharge = last(riverSegments).flux; // in m3/s + pack.rivers.push({i: r, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent}); } - // assign all tributary rivers to outlet basin - for (let outlet = lakes[0]?.outlet, l = 0; l < lakes.length; l++) { - lakes[l].inlets?.forEach(fork => riversData.find(r => r.river === fork).parent = outlet); - } + // draw rivers + rivers.html(riverPaths.map(d => ``).join("")); + } + }; - // near-border cell: pour water out of the screen - if (cells.b[i] && cells.r[i]) { - const to = []; - const min = Math.min(y, graphHeight - y, x, graphWidth - x); - if (min === y) {to[0] = x; to[1] = 0;} else - if (min === graphHeight - y) {to[0] = x; to[1] = graphHeight;} else - if (min === x) {to[0] = 0; to[1] = y;} else - if (min === graphWidth - x) {to[0] = graphWidth; to[1] = y;} - riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1], flux: cells.fl[i]}); - return; - } - - // downhill cell (make sure it's not in the source lake) - const min = lakeOutCells[i] - ? cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c])).sort((a, b) => h[a] - h[b])[0] - : cells.c[i].sort((a, b) => h[a] - h[b])[0]; - - if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) { - if (h[min] >= 20) cells.fl[min] += cells.fl[i]; - return; // flux is too small to operate as river - } - - // proclaim a new river - if (!cells.r[i]) { - cells.r[i] = riverNext; - riversData.push({river: riverNext, cell: i, x, y, flux: cells.fl[i]}); - riverNext++; - } - - flowDown(min, cells.fl[min], cells.fl[i], cells.r[i], i); + // add distance to water value to land cells to make map less depressed + function alterHeights() { + const cells = pack.cells; + return Array.from(cells.h).map((h, i) => { + if (h < 20 || cells.t[i] < 1) return h; + return h + cells.t[i] / 100 + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000; }); } - function flowDown(toCell, toFlux, fromFlux, river, fromCell = 0) { - if (cells.r[toCell]) { - // downhill cell already has river assigned - if (toFlux < fromFlux) { - cells.conf[toCell] = cells.fl[toCell]; // mark confluence - if (h[toCell] >= 20) riversData.find(r => r.river === cells.r[toCell]).parent = river; // min river is a tributary of current river - cells.r[toCell] = river; // re-assign river if downhill part has less flux - } else { - cells.conf[toCell] += fromFlux; // mark confluence - if (h[toCell] >= 20) riversData.find(r => r.river === river).parent = cells.r[toCell]; // current river is a tributary of min river + // depression filling algorithm (for a correct water flux modeling) + const resolveDepressions = function (h, maxIterations) { + const {cells, features} = pack; + const height = i => features[cells.f[i]].height || h[i]; // height of lake or specific cell + + const lakes = features.filter(f => f.type === "lake"); + const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells + land.sort((a, b) => h[b] - h[a]); // highest cells go first + + const progress = []; + let depressions = Infinity; + let prevDepressions = null; + for (let iteration = 0; depressions && iteration < maxIterations; iteration++) { + if (progress.length > 5 && d3.sum(progress) > 0) { + // bad progress, abort and set heights back + h = alterHeights(); + depressions = progress[0]; + break; } - } else cells.r[toCell] = river; // assign the river to the downhill cell - if (h[toCell] < 20) { - // pour water to the water body - const haven = fromCell ? cells.haven[fromCell] : toCell; - riversData.push({river, cell: haven, x: p[toCell][0], y: p[toCell][1], flux: fromFlux}); + depressions = 0; - const waterBody = features[cells.f[toCell]]; - if (waterBody.type === "lake") { - if (!waterBody.river || fromFlux > waterBody.enteringFlux) { - waterBody.river = river; - waterBody.enteringFlux = fromFlux; + if (iteration < 180) { + for (const l of lakes) { + if (l.closed) continue; + const minHeight = d3.min(l.shoreline.map(s => h[s])); + if (minHeight >= 100 || l.height > minHeight) continue; + + if (iteration > 150) { + l.shoreline.forEach(i => (h[i] = cells.h[i])); + l.height = d3.min(l.shoreline.map(s => h[s])) - 1; + l.closed = true; + continue; + } + + depressions++; + l.height = minHeight + 0.2; } - waterBody.flux = waterBody.flux + fromFlux; - waterBody.inlets ? waterBody.inlets.push(river) : waterBody.inlets = [river]; - } - } else { - // propagate flux and add next river segment - cells.fl[toCell] += fromFlux; - riversData.push({river, cell: toCell, x: p[toCell][0], y: p[toCell][1], flux: fromFlux}); - } - } - - function defineRivers() { - cells.r = new Uint16Array(cells.i.length); // re-initiate rivers array - pack.rivers = []; // rivers data - const riverPaths = []; - - for (let r = 1; r <= riverNext; r++) { - const riverSegments = riversData.filter(d => d.river === r); - if (riverSegments.length < 3) continue; - - for (const segment of riverSegments) { - const i = segment.cell; - if (cells.r[i]) continue; - if (cells.h[i] < 20) continue; - cells.r[i] = r; } - const source = riverSegments[0].cell; - const mouth = riverSegments[riverSegments.length-2].cell; + for (const i of land) { + const minHeight = d3.min(cells.c[i].map(c => height(c))); + if (minHeight >= 100 || h[i] > minHeight) continue; - const widthFactor = rn(.8 + Math.random() * .4, 1); // river width modifier [.8, 1.2] - const sourceWidth = cells.h[source] >= 20 ? .1 : rn(Math.min(Math.max((cells.fl[source] / 500) ** .4, .5), 1.7), 2); + depressions++; + h[i] = minHeight + 0.1; + } - const riverMeandered = addMeandering(riverSegments, sourceWidth * 10, .5); - const [path, length, offset] = getPath(riverMeandered, widthFactor, sourceWidth); - riverPaths.push([path, r]); - - const parent = riverSegments[0].parent || 0; - const width = rn(offset ** 2, 2); // mounth width in km - const discharge = last(riverSegments).flux; // in m3/s - pack.rivers.push({i:r, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent}); + prevDepressions !== null && progress.push(depressions - prevDepressions); + prevDepressions = depressions; } - // draw rivers - rivers.html(riverPaths.map(d => ``).join("")); - } -} + if (!depressions) return; + WARN && console.warn(`Unresolved depressions: ${depressions}. Edit heightmap to fix`); + //const flow = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); + //flow[i] = min; + //debug.append("path").attr("class", "arrow").attr("d", `M${cells.p[i][0]},${cells.p[i][1]}L${cells.p[min][0]},${cells.p[min][1]}`); + }; -// depression filling algorithm (for a correct water flux modeling) -const resolveDepressions = function(h) { - const {cells, features} = pack; - const ITERATIONS = 150; + // add more river points on 1/3 and 2/3 of length + const addMeandering = function (segments, width = 1, meandering = 0.5) { + const riverMeandered = []; // to store enhanced segments - const lakes = features.filter(f => f.type === "lake"); - lakes.forEach(l => { - const uniqueCells = new Set(); - l.vertices.forEach(v => pack.vertices.c[v].forEach(c => cells.h[c] >= 20 && uniqueCells.add(c))); - l.shoreline = [...uniqueCells]; - }); + for (let s = 0; s < segments.length; s++, width++) { + const sX = segments[s].x, + sY = segments[s].y; // segment start coordinates + const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence + riverMeandered.push([sX, sY, c]); - const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells - land.sort((a,b) => h[b] - h[a]); // highest cells go first + if (s + 1 === segments.length) break; // do not meander last segment - let depressions = Infinity; - for (let l = 0; depressions && l < ITERATIONS; l++) { - depressions = 0; + const eX = segments[s + 1].x, + eY = segments[s + 1].y; // segment end coordinates + const angle = Math.atan2(eY - sY, eX - sX); + const sin = Math.sin(angle), + cos = Math.cos(angle); - for (const l of lakes) { - const minHeight = d3.min(l.shoreline.map(s => h[s])); - if (minHeight >= 100 || l.height > minHeight) continue; - l.height = minHeight + 1; - depressions++; + const meander = meandering + 1 / width + Math.random() * Math.max(meandering - width / 100, 0); + const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2; // square distance between segment start and end + + if (width < 10 && (dist2 > 64 || (dist2 > 36 && segments.length < 6))) { + // if dist2 is big or river is small add extra points at 1/3 and 2/3 of segment + const p1x = (sX * 2 + eX) / 3 + -sin * meander; + const p1y = (sY * 2 + eY) / 3 + cos * meander; + const p2x = (sX + eX * 2) / 3 + sin * meander; + const p2y = (sY + eY * 2) / 3 + cos * meander; + riverMeandered.push([p1x, p1y], [p2x, p2y]); + } else if (dist2 > 25 || segments.length < 6) { + // if dist is medium or river is small add 1 extra middlepoint + const p1x = (sX + eX) / 2 + -sin * meander; + const p1y = (sY + eY) / 2 + cos * meander; + riverMeandered.push([p1x, p1y]); + } } - for (const i of land) { - const minHeight = d3.min(cells.c[i].map(c => cells.t[c] > 0 ? h[c] : pack.features[cells.f[c]].height || h[c])); - if (minHeight >= 100 || h[i] > minHeight) continue; - h[i] = minHeight + 1; - depressions++; - } - } + return riverMeandered; + }; - depressions && ERROR && console.error("Heightmap is depressed. Issues with rivers expected. Remove depressed areas to resolve"); -} + const getPath = function (points, widthFactor = 1, sourceWidth = 0.1) { + let offset, + extraOffset = sourceWidth; // starting river width (to make river source visible) + const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0); // summ of segments length + const widening = 1000 + riverLength * 30; + const riverPointsLeft = [], + riverPointsRight = []; // store points on both sides to build a valid polygon + const last = points.length - 1; + const factor = riverLength / points.length; -// add more river points on 1/3 and 2/3 of length -const addMeandering = function(segments, width = 1, meandering = .5) { - const riverMeandered = []; // to store enhanced segments - - for (let s = 0; s < segments.length; s++, width++) { - const sX = segments[s].x, sY = segments[s].y; // segment start coordinates - const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence - riverMeandered.push([sX, sY, c]); - - if (s+1 === segments.length) break; // do not meander last segment - - const eX = segments[s+1].x, eY = segments[s+1].y; // segment end coordinates - const angle = Math.atan2(eY - sY, eX - sX); - const sin = Math.sin(angle), cos = Math.cos(angle); - - const meander = meandering + 1 / width + Math.random() * Math.max(meandering - width / 100, 0); - const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2; // square distance between segment start and end - - if (width < 10 && (dist2 > 64 || (dist2 > 36 && segments.length < 6))) { - // if dist2 is big or river is small add extra points at 1/3 and 2/3 of segment - const p1x = (sX * 2 + eX) / 3 + -sin * meander; - const p1y = (sY * 2 + eY) / 3 + cos * meander; - const p2x = (sX + eX * 2) / 3 + sin * meander; - const p2y = (sY + eY * 2) / 3 + cos * meander; - riverMeandered.push([p1x, p1y], [p2x, p2y]); - } else if (dist2 > 25 || segments.length < 6) { - // if dist is medium or river is small add 1 extra middlepoint - const p1x = (sX + eX) / 2 + -sin * meander; - const p1y = (sY + eY) / 2 + cos * meander; - riverMeandered.push([p1x, p1y]); - } - - } - - return riverMeandered; -} - -const getPath = function(points, widthFactor = 1, sourceWidth = .1) { - let offset, extraOffset = sourceWidth; // starting river width (to make river source visible) - const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i-1][0], v[1] - p[i-1][1]) : 0), 0); // summ of segments length - const widening = 1000 + riverLength * 30; - const riverPointsLeft = [], riverPointsRight = []; // store points on both sides to build a valid polygon - const last = points.length - 1; - const factor = riverLength / points.length; - - // first point - let x = points[0][0], y = points[0][1], c; - let angle = Math.atan2(y - points[1][1], x - points[1][0]); - let sin = Math.sin(angle), cos = Math.cos(angle); - let xLeft = x + -sin * extraOffset, yLeft = y + cos * extraOffset; - riverPointsLeft.push([xLeft, yLeft]); - let xRight = x + sin * extraOffset, yRight = y + -cos * extraOffset; - riverPointsRight.unshift([xRight, yRight]); - - // middle points - for (let p = 1; p < last; p++) { - x = points[p][0], y = points[p][1], c = points[p][2] || 0; - const xPrev = points[p-1][0], yPrev = points[p - 1][1]; - const xNext = points[p+1][0], yNext = points[p + 1][1]; - angle = Math.atan2(yPrev - yNext, xPrev - xNext); - sin = Math.sin(angle), cos = Math.cos(angle); - offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2 * widthFactor) + extraOffset; - const confOffset = Math.atan(c * 5 / widening); - extraOffset += confOffset; - xLeft = x + -sin * offset, yLeft = y + cos * (offset + confOffset); + // first point + let x = points[0][0], + y = points[0][1], + c; + let angle = Math.atan2(y - points[1][1], x - points[1][0]); + let sin = Math.sin(angle), + cos = Math.cos(angle); + let xLeft = x + -sin * extraOffset, + yLeft = y + cos * extraOffset; riverPointsLeft.push([xLeft, yLeft]); - xRight = x + sin * offset, yRight = y + -cos * offset; + let xRight = x + sin * extraOffset, + yRight = y + -cos * extraOffset; riverPointsRight.unshift([xRight, yRight]); - } - // end point - x = points[last][0], y = points[last][1], c = points[last][2]; - if (c) extraOffset += Math.atan(c * 10 / widening); // add extra width on river confluence - angle = Math.atan2(points[last-1][1] - y, points[last-1][0] - x); - sin = Math.sin(angle), cos = Math.cos(angle); - xLeft = x + -sin * offset, yLeft = y + cos * offset; - riverPointsLeft.push([xLeft, yLeft]); - xRight = x + sin * offset, yRight = y + -cos * offset; - riverPointsRight.unshift([xRight, yRight]); + // middle points + for (let p = 1; p < last; p++) { + (x = points[p][0]), (y = points[p][1]), (c = points[p][2] || 0); + const xPrev = points[p - 1][0], + yPrev = points[p - 1][1]; + const xNext = points[p + 1][0], + yNext = points[p + 1][1]; + angle = Math.atan2(yPrev - yNext, xPrev - xNext); + (sin = Math.sin(angle)), (cos = Math.cos(angle)); + offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2) * widthFactor + extraOffset; + const confOffset = Math.atan((c * 5) / widening); + extraOffset += confOffset; + (xLeft = x + -sin * offset), (yLeft = y + cos * (offset + confOffset)); + riverPointsLeft.push([xLeft, yLeft]); + (xRight = x + sin * offset), (yRight = y + -cos * offset); + riverPointsRight.unshift([xRight, yRight]); + } - // generate polygon path and return - lineGen.curve(d3.curveCatmullRom.alpha(0.1)); - const right = lineGen(riverPointsRight); - let left = lineGen(riverPointsLeft); - left = left.substring(left.indexOf("C")); - return [round(right + left, 2), rn(riverLength, 2), offset]; -} + // end point + (x = points[last][0]), (y = points[last][1]), (c = points[last][2]); + if (c) extraOffset += Math.atan((c * 10) / widening); // add extra width on river confluence + angle = Math.atan2(points[last - 1][1] - y, points[last - 1][0] - x); + (sin = Math.sin(angle)), (cos = Math.cos(angle)); + (xLeft = x + -sin * offset), (yLeft = y + cos * offset); + riverPointsLeft.push([xLeft, yLeft]); + (xRight = x + sin * offset), (yRight = y + -cos * offset); + riverPointsRight.unshift([xRight, yRight]); -const specify = function() { - const rivers = pack.rivers; - if (!rivers.length) return; - Math.random = aleaPRNG(seed); - const tresholdElement = Math.ceil(rivers.length * .15); - const smallLength = rivers.map(r => r.length || 0).sort((a, b) => a-b)[tresholdElement]; - const smallType = {"Creek":9, "River":3, "Brook":3, "Stream":1}; // weighted small river types + // generate polygon path and return + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); + const right = lineGen(riverPointsRight); + let left = lineGen(riverPointsLeft); + left = left.substring(left.indexOf("C")); + return [round(right + left, 2), rn(riverLength, 2), offset]; + }; - for (const r of rivers) { - r.basin = getBasin(r.i); - r.name = getName(r.mouth); - const small = r.length < smallLength; - r.type = r.parent && !(r.i%6) ? small ? "Branch" : "Fork" : small ? rw(smallType) : "River"; - } -} + const specify = function () { + const rivers = pack.rivers; + if (!rivers.length) return; + Math.random = aleaPRNG(seed); + const tresholdElement = Math.ceil(rivers.length * 0.15); + const smallLength = rivers.map(r => r.length || 0).sort((a, b) => a - b)[tresholdElement]; + const smallType = {Creek: 9, River: 3, Brook: 3, Stream: 1}; // weighted small river types -const getName = function(cell) { - return Names.getCulture(pack.cells.culture[cell]); -} + for (const r of rivers) { + r.basin = getBasin(r.i); + r.name = getName(r.mouth); + const small = r.length < smallLength; + r.type = r.parent && !(r.i % 6) ? (small ? "Branch" : "Fork") : small ? rw(smallType) : "River"; + } + }; -// remove river and all its tributaries -const remove = function(id) { - const cells = pack.cells; - const riversToRemove = pack.rivers.filter(r => r.i === id || r.parent === id || r.basin === id).map(r => r.i); - riversToRemove.forEach(r => rivers.select("#river"+r).remove()); - cells.r.forEach((r, i) => { - if (!r || !riversToRemove.includes(r)) return; - cells.r[i] = 0; - cells.fl[i] = grid.cells.prec[cells.g[i]]; - cells.conf[i] = 0; - }); - pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i)); -} + const getName = function (cell) { + return Names.getCulture(pack.cells.culture[cell]); + }; -const getBasin = function(r) { - const parent = pack.rivers.find(river => river.i === r)?.parent; - if (!parent || r === parent) return r; - return getBasin(parent); -} + // remove river and all its tributaries + const remove = function (id) { + const cells = pack.cells; + const riversToRemove = pack.rivers.filter(r => r.i === id || r.parent === id || r.basin === id).map(r => r.i); + riversToRemove.forEach(r => rivers.select("#river" + r).remove()); + cells.r.forEach((r, i) => { + if (!r || !riversToRemove.includes(r)) return; + cells.r[i] = 0; + cells.fl[i] = grid.cells.prec[cells.g[i]]; + cells.conf[i] = 0; + }); + pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i)); + }; -return {generate, resolveDepressions, addMeandering, getPath, specify, getName, getBasin, remove}; + const getBasin = function (r) { + const parent = pack.rivers.find(river => river.i === r)?.parent; + if (!parent || r === parent) return r; + return getBasin(parent); + }; -}))); \ No newline at end of file + return {generate, resolveDepressions, addMeandering, getPath, specify, getName, getBasin, remove}; +}); diff --git a/modules/save-and-load.js b/modules/save-and-load.js index f71fbc99..e30da762 100644 --- a/modules/save-and-load.js +++ b/modules/save-and-load.js @@ -27,19 +27,19 @@ async function savePNG() { const img = new Image(); img.src = url; - img.onload = function() { + img.onload = function () { ctx.drawImage(img, 0, 0, canvas.width, canvas.height); link.download = getFileName() + ".png"; - canvas.toBlob(function(blob) { - link.href = window.URL.createObjectURL(blob); - link.click(); - window.setTimeout(function() { - canvas.remove(); - window.URL.revokeObjectURL(link.href); - tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000); - }, 1000); + canvas.toBlob(function (blob) { + link.href = window.URL.createObjectURL(blob); + link.click(); + window.setTimeout(function () { + canvas.remove(); + window.URL.revokeObjectURL(link.href); + tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000); + }, 1000); }); - } + }; TIME && console.timeEnd("savePNG"); } @@ -55,9 +55,9 @@ async function saveJPEG() { const img = new Image(); img.src = url; - img.onload = async function() { + img.onload = async function () { canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height); - const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), .92); + const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92); const URL = await canvas.toDataURL("image/jpeg", quality); const link = document.createElement("a"); link.download = getFileName() + ".jpeg"; @@ -65,7 +65,7 @@ async function saveJPEG() { link.click(); tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000); window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000); - } + }; TIME && console.timeEnd("saveJPEG"); } @@ -81,7 +81,7 @@ async function getMapURL(type, subtype) { const cloneDefs = cloneEl.getElementsByTagName("defs")[0]; const svgDefs = document.getElementById("defElements"); - const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); if (subtype === "globe") clone.select("#scaleBar").remove(); if (subtype === "noWater") { @@ -99,37 +99,40 @@ async function getMapURL(type, subtype) { // remove unused filters const filters = cloneEl.querySelectorAll("filter"); - for (let i=0; i < filters.length; i++) { + for (let i = 0; i < filters.length; i++) { const id = filters[i].id; - if (cloneEl.querySelector("[filter='url(#"+id+")']")) continue; - if (cloneEl.getAttribute("filter") === "url(#"+id+")") continue; + if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue; + if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue; filters[i].remove(); } // remove unused patterns const patterns = cloneEl.querySelectorAll("pattern"); - for (let i=0; i < patterns.length; i++) { + for (let i = 0; i < patterns.length; i++) { const id = patterns[i].id; - if (cloneEl.querySelector("[fill='url(#"+id+")']")) continue; + if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue; patterns[i].remove(); } // remove unused symbols const symbols = cloneEl.querySelectorAll("symbol"); - for (let i=0; i < symbols.length; i++) { + for (let i = 0; i < symbols.length; i++) { const id = symbols[i].id; - if (cloneEl.querySelector("use[*|href='#"+id+"']")) continue; + if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue; symbols[i].remove(); } // add displayed emblems if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) { - cloneEl.getElementById("emblems")?.querySelectorAll("use").forEach(el => { - const href = el.getAttribute("href") || el.getAttribute("xlink:href"); - if (!href) return; - const emblem = document.getElementById(href.slice(1)); - if (emblem) cloneDefs.append(emblem.cloneNode(true)); - }); + cloneEl + .getElementById("emblems") + ?.querySelectorAll("use") + .forEach(el => { + const href = el.getAttribute("href") || el.getAttribute("xlink:href"); + if (!href) return; + const emblem = document.getElementById(href.slice(1)); + if (emblem) cloneDefs.append(emblem.cloneNode(true)); + }); } else { cloneDefs.querySelector("#defs-emblems")?.remove(); } @@ -150,7 +153,7 @@ async function getMapURL(type, subtype) { if (cloneEl.getElementById("terrain")) { const uniqueElements = new Set(); const terrainNodes = cloneEl.getElementById("terrain").childNodes; - for (let i=0; i < terrainNodes.length; i++) { + for (let i = 0; i < terrainNodes.length; i++) { const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href"); uniqueElements.add(href); } @@ -177,7 +180,7 @@ async function getMapURL(type, subtype) { // add grid pattern if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) { const type = cloneEl.getElementById("gridOverlay").getAttribute("type"); - const pattern = svgDefs.getElementById("pattern_"+type); + const pattern = svgDefs.getElementById("pattern_" + type); if (pattern) cloneDefs.appendChild(pattern.cloneNode(true)); } @@ -190,11 +193,11 @@ async function getMapURL(type, subtype) { if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", ""); const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts - if (fontStyle) clone.select("defs").append("style").text(fontStyle.join('\n')); // add font to style + if (fontStyle) clone.select("defs").append("style").text(fontStyle.join("\n")); // add font to style clone.remove(); - const serialized = `` + (new XMLSerializer()).serializeToString(cloneEl); - const blob = new Blob([serialized], {type: 'image/svg+xml;charset=utf-8'}); + const serialized = `` + new XMLSerializer().serializeToString(cloneEl); + const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"}); const url = window.URL.createObjectURL(blob); window.setTimeout(() => window.URL.revokeObjectURL(url), 5000); return url; @@ -205,10 +208,13 @@ function removeUnusedElements(clone) { if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove(); if (markers.style("display") === "none") clone.select("#defs-markers").remove(); - for (let empty = 1; empty;) { + for (let empty = 1; empty; ) { empty = 0; - clone.selectAll("g").each(function() { - if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) {empty++; this.remove();} + clone.selectAll("g").each(function () { + if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) { + empty++; + this.remove(); + } if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display"); }); } @@ -218,8 +224,14 @@ function updateMeshCells(clone) { const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20); const scheme = getColorScheme(); clone.select("#heights").attr("filter", "url(#blur1)"); - clone.select("#heights").selectAll("polygon").data(data).join("polygon").attr("points", d => getGridPolygon(d)) - .attr("id", d => "cell"+d).attr("stroke", d => getColor(grid.cells.h[d], scheme)); + clone + .select("#heights") + .selectAll("polygon") + .data(data) + .join("polygon") + .attr("points", d => getGridPolygon(d)) + .attr("id", d => "cell" + d) + .attr("stroke", d => getColor(grid.cells.h[d], scheme)); } // for each g element get inline style @@ -227,11 +239,11 @@ function inlineStyle(clone) { const emptyG = clone.append("g").node(); const defaultStyles = window.getComputedStyle(emptyG); - clone.selectAll("g, #ruler *, #scaleBar > text").each(function() { + clone.selectAll("g, #ruler *, #scaleBar > text").each(function () { const compStyle = window.getComputedStyle(this); let style = ""; - for (let i=0; i < compStyle.length; i++) { + for (let i = 0; i < compStyle.length; i++) { const key = compStyle[i]; const value = compStyle.getPropertyValue(key); @@ -244,7 +256,7 @@ function inlineStyle(clone) { if (key === "cursor") continue; // cursor should be default if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute if (value === defaultStyles.getPropertyValue(key)) continue; - style += key + ':' + value + ';'; + style += key + ":" + value + ";"; } for (const key in compStyle) { @@ -253,10 +265,10 @@ function inlineStyle(clone) { if (key === "cursor") continue; // cursor should be default if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute if (value === defaultStyles.getPropertyValue(key)) continue; - style += key + ':' + value + ';'; + style += key + ":" + value + ";"; } - if (style != "") this.setAttribute('style', style); + if (style != "") this.setAttribute("style", style); }); emptyG.remove(); @@ -267,7 +279,7 @@ function getFontsToLoad(clone) { const webSafe = ["Georgia", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New", "Verdana", "Arial", "Impact"]; // fonts to not fetch const fontsInUse = new Set(); // to store fonts currently in use - clone.selectAll("#labels > g").each(function() { + clone.selectAll("#labels > g").each(function () { if (!this.hasChildNodes()) return; const font = this.dataset.font; if (!font || webSafe.includes(font)) return; @@ -285,16 +297,16 @@ function GFontToDataURI(url) { return fetch(url) // first fecth the embed stylesheet page .then(resp => resp.text()) // we only need the text of it .then(text => { - let s = document.createElement('style'); + let s = document.createElement("style"); s.innerHTML = text; document.head.appendChild(s); const styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0]; const FontRule = rule => { - const src = rule.style.getPropertyValue('src'); - const url = src ? src.split('url(')[1].split(')')[0] : ""; + const src = rule.style.getPropertyValue("src"); + const url = src ? src.split("url(")[1].split(")")[0] : ""; return {rule, src, url: url.substring(url.length - 1, 1)}; - } + }; const fontProms = []; for (const r of styleSheet.cssRules) { @@ -303,16 +315,16 @@ function GFontToDataURI(url) { fontProms.push( fetch(fR.url) // fetch the actual font-file (.woff) - .then(resp => resp.blob()) - .then(blob => { - return new Promise(resolve => { - let f = new FileReader(); - f.onload = e => resolve(f.result); - f.readAsDataURL(blob); + .then(resp => resp.blob()) + .then(blob => { + return new Promise(resolve => { + let f = new FileReader(); + f.onload = e => resolve(f.result); + f.readAsDataURL(blob); + }); }) - }) - .then(dataURL => fR.rule.cssText.replace(fR.url, dataURL)) - ) + .then(dataURL => fR.rule.cssText.replace(fR.url, dataURL)) + ); } document.head.removeChild(s); // clean up return Promise.all(fontProms); // wait for all this has been done @@ -328,13 +340,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, - barSize.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].join("|"); + const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value, barSize.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].join("|"); const coords = JSON.stringify(mapCoordinates); const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|"); const notesData = JSON.stringify(notes); @@ -351,9 +357,9 @@ function getMapData() { // always remove rulers cloneEl.querySelector("#ruler").innerHTML = ""; - const svg_xml = (new XMLSerializer()).serializeToString(cloneEl); + const svg_xml = new XMLSerializer().serializeToString(cloneEl); - const gridGeneral = JSON.stringify({spacing:grid.spacing, cellsX:grid.cellsX, cellsY:grid.cellsY, boundary:grid.boundary, points:grid.points, features:grid.features}); + const gridGeneral = JSON.stringify({spacing: grid.spacing, cellsX: grid.cellsX, cellsY: grid.cellsY, boundary: grid.boundary, points: grid.points, features: grid.features}); const features = JSON.stringify(pack.features); const cultures = JSON.stringify(pack.cultures); const states = JSON.stringify(pack.states); @@ -364,22 +370,18 @@ function getMapData() { // store name array only if it is not the same as default const defaultNB = Names.getNameBases(); - const namesData = nameBases.map((b,i) => { - const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b; - return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`; - }).join("/"); + const namesData = nameBases + .map((b, i) => { + const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b; + return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`; + }) + .join("/"); // round population to save resources const pop = Array.from(pack.cells.pop).map(p => rn(p, 4)); // data format as below - const data = [params, settings, coords, biomes, notesData, svg_xml, - gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, - features, cultures, states, burgs, - pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, - pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, - pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, - namesData, rivers, rulersString].join("\r\n"); + const data = [params, settings, coords, biomes, notesData, svg_xml, gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, features, cultures, states, burgs, pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, namesData, rivers, rulersString].join("\r\n"); const blob = new Blob([data], {type: "text/plain"}); TIME && console.timeEnd("createMapDataBlob"); @@ -389,7 +391,10 @@ function getMapData() { // Download .map file async function saveMap() { - if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;} + if (customization) { + tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); + return; + } closeDialogs("#alert"); const blob = await getMapData(); @@ -405,8 +410,11 @@ async function saveMap() { function saveGeoJSON_Cells() { const json = {type: "FeatureCollection", features: []}; const cells = pack.cells; - const getPopulation = i => {const [r, u] = getCellPopulation(i); return rn(r+u)}; - const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]])); + const getPopulation = i => { + const [r, u] = getCellPopulation(i); + return rn(r + u); + }; + const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]])); cells.i.forEach(i => { const coordinates = getCellCoordinates(cells.v[i]); @@ -420,7 +428,7 @@ function saveGeoJSON_Cells() { const religion = cells.religion[i]; const neighbors = cells.c[i]; - const properties = {id:i, height, biome, type, population, state, province, culture, religion, neighbors} + const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors}; const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties}; json.features.push(feature); }); @@ -432,7 +440,7 @@ function saveGeoJSON_Cells() { function saveGeoJSON_Routes() { const json = {type: "FeatureCollection", features: []}; - routes.selectAll("g > path").each(function() { + routes.selectAll("g > path").each(function () { const coordinates = getRoutePoints(this); const id = this.id; const type = this.parentElement.id; @@ -448,7 +456,7 @@ function saveGeoJSON_Routes() { function saveGeoJSON_Rivers() { const json = {type: "FeatureCollection", features: []}; - rivers.selectAll("path").each(function() { + rivers.selectAll("path").each(function () { const coordinates = getRiverPoints(this); const id = this.id; const width = +this.dataset.increment; @@ -470,10 +478,10 @@ function saveGeoJSON_Rivers() { function saveGeoJSON_Markers() { const json = {type: "FeatureCollection", features: []}; - markers.selectAll("use").each(function() { + markers.selectAll("use").each(function () { const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y); const id = this.id; - const type = (this.dataset.id).substring(1); + const type = this.dataset.id.substring(1); const icon = document.getElementById(type).textContent; const note = notes.length ? notes.find(note => note.id === this.id) : null; const name = note ? note.name : ""; @@ -497,7 +505,7 @@ function getRoutePoints(node) { let points = []; const l = node.getTotalLength(); const increment = l / Math.ceil(l / 2); - for (let i=0; i <= l; i += increment) { + for (let i = 0; i <= l; i += increment) { const p = node.getPointAtLength(i); points.push(getQGIScoordinates(p.x, p.y)); } @@ -508,17 +516,20 @@ function getRiverPoints(node) { let points = []; const l = node.getTotalLength() / 2; // half-length const increment = 0.25; // defines density of points - for (let i=l, c=i; i >= 0; i -= increment, c += increment) { + for (let i = l, c = i; i >= 0; i -= increment, c += increment) { const p1 = node.getPointAtLength(i); const p2 = node.getPointAtLength(c); const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); - points.push([x,y]); + points.push([x, y]); } return points; } async function quickSave() { - if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;} + if (customization) { + tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); + return; + } const blob = await getMapData(); if (blob) ldb.set("lastMap", blob); // auto-save map tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000); @@ -537,14 +548,24 @@ function quickLoad() { function loadMapPrompt(blob) { const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes - if (workingTime < 5) {loadLastSavedMap(); return;} + if (workingTime < 5) { + loadLastSavedMap(); + return; + } alertMessage.innerHTML = `Are you sure you want to load saved map?
All unsaved changes made to the current map will be lost`; - $("#alert").dialog({resizable: false, title: "Load saved map", + $("#alert").dialog({ + resizable: false, + title: "Load saved map", buttons: { - Cancel: function() {$(this).dialog("close");}, - Load: function() {loadLastSavedMap(); $(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + }, + Load: function () { + loadLastSavedMap(); + $(this).dialog("close"); + } } }); @@ -552,31 +573,23 @@ function loadMapPrompt(blob) { WARN && console.warn("Load last saved map"); try { uploadMap(blob); - } - catch(error) { + } catch (error) { ERROR && console.error(error); tip("Cannot load last saved map", true, "error", 2000); } } } -const saveReminder = function() { +const saveReminder = function () { if (localStorage.getItem("noReminder")) return; - const message = ["Please don't forget to save your work as a .map file", - "Please remember to save work as a .map file", - "Saving in .map format will ensure your data won't be lost in case of issues", - "Safety is number one priority. Please save the map", - "Don't forget to save your map on a regular basis!", - "Just a gentle reminder for you to save the map", - "Please don't forget to save your progress (saving as .map is the best option)", - "Don't want to be reminded about need to save? Press CTRL+Q"]; + const message = ["Please don't forget to save your work as a .map file", "Please remember to save work as a .map file", "Saving in .map format will ensure your data won't be lost in case of issues", "Safety is number one priority. Please save the map", "Don't forget to save your map on a regular basis!", "Just a gentle reminder for you to save the map", "Please don't forget to save your progress (saving as .map is the best option)", "Don't want to be reminded about need to save? Press CTRL+Q"]; saveReminder.reminder = setInterval(() => { if (customization) return; tip(ra(message), true, "warn", 2500); }, 1e6); saveReminder.status = 1; -} +}; saveReminder(); @@ -597,7 +610,7 @@ function uploadMap(file, callback) { uploadMap.timeStart = performance.now(); const fileReader = new FileReader(); - fileReader.onload = function(fileLoadedEvent) { + fileReader.onload = function (fileLoadedEvent) { if (callback) callback(); document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems @@ -605,11 +618,15 @@ function uploadMap(file, callback) { const data = dataLoaded.split("\r\n"); const mapVersion = data[0].split("|")[0] || data[0]; - if (mapVersion === version) {parseLoadedData(data); return;} + if (mapVersion === version) { + parseLoadedData(data); + return; + } const archive = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "archived version"); const parsed = parseFloat(mapVersion); - let message = "", load = false; + let message = "", + load = false; if (isNaN(parsed) || data.length < 26 || !data[5]) { message = `The file you are trying to load is outdated or not a valid .map file.
Please try to open it using an ${archive}`; @@ -618,13 +635,20 @@ function uploadMap(file, callback) {
Please keep using an ${archive}`; } else { load = true; - message = `The map version (${mapVersion}) does not match the Generator version (${version}). + message = `The map version (${mapVersion}) does not match the Generator version (${version}).
Click OK to get map auto-updated. In case of issues please keep using an ${archive} of the Generator`; } alertMessage.innerHTML = message; - $("#alert").dialog({title: "Version conflict", width: "38em", buttons: { - OK: function() {$(this).dialog("close"); if (load) parseLoadedData(data);} - }}); + $("#alert").dialog({ + title: "Version conflict", + width: "38em", + buttons: { + OK: function () { + $(this).dialog("close"); + if (load) parseLoadedData(data); + } + } + }); }; fileReader.readAsText(file, "UTF-8"); @@ -640,17 +664,20 @@ function parseLoadedData(data) { const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons const hatching = document.getElementById("hatching").cloneNode(true); // save hatching - void function parseParameters() { + void (function parseParameters() { const params = data[0].split("|"); - if (params[3]) {seed = params[3]; optionsSeed.value = seed;} + if (params[3]) { + seed = params[3]; + optionsSeed.value = seed; + } if (params[4]) graphWidth = +params[4]; if (params[5]) graphHeight = +params[5]; mapId = params[6] ? +params[6] : Date.now(); - }() + })(); INFO && console.group("Loaded Map " + seed); - void function parseSettings() { + void (function parseSettings() { const settings = data[1].split("|"); if (settings[0]) applyOption(distanceUnitInput, settings[0]); if (settings[1]) distanceScaleInput.value = distanceScaleOutput.value = settings[1]; @@ -673,9 +700,9 @@ function parseLoadedData(data) { if (settings[18]) precInput.value = precOutput.value = settings[18]; if (settings[19]) options = JSON.parse(settings[19]); if (settings[20]) mapName.value = settings[20]; - }() + })(); - void function parseConfiguration() { + void (function parseConfiguration() { if (data[2]) mapCoordinates = JSON.parse(data[2]); if (data[4]) notes = JSON.parse(data[4]); if (data[33]) rulers.fromString(data[33]); @@ -687,20 +714,20 @@ function parseLoadedData(data) { biomesData.name = biomes[2].split(","); // push custom biomes if any - for (let i=biomesData.i.length; i < biomesData.name.length; i++) { + for (let i = biomesData.i.length; i < biomesData.name.length; i++) { biomesData.i.push(biomesData.i.length); biomesData.iconsDensity.push(0); biomesData.icons.push([]); biomesData.cost.push(50); } - }() + })(); - void function replaceSVG() { + void (function replaceSVG() { svg.remove(); document.body.insertAdjacentHTML("afterbegin", data[5]); - }() + })(); - void function redefineElements() { + void (function redefineElements() { svg = d3.select("#map"); defs = svg.select("#deftemp"); viewbox = svg.select("#viewbox"); @@ -750,9 +777,9 @@ function parseLoadedData(data) { fogging = viewbox.select("#fogging"); debug = viewbox.select("#debug"); burgLabels = labels.select("#burgLabels"); - }() + })(); - void function parseGridData() { + void (function parseGridData() { grid = JSON.parse(data[6]); calculateVoronoi(grid, grid.points); grid.cells.h = Uint8Array.from(data[7].split(",")); @@ -760,9 +787,9 @@ function parseLoadedData(data) { grid.cells.f = Uint16Array.from(data[9].split(",")); grid.cells.t = Int8Array.from(data[10].split(",")); grid.cells.temp = Int8Array.from(data[11].split(",")); - }() + })(); - void function parsePackData() { + void (function parsePackData() { pack = {}; reGraph(); reMarkFeatures(); @@ -795,19 +822,22 @@ function parseLoadedData(data) { const e = d.split("|"); if (!e.length) return; const b = e[5].split(",").length > 2 || !nameBases[i] ? e[5] : nameBases[i].b; - nameBases[i] = {name:e[0], min:e[1], max:e[2], d:e[3], m:e[4], b}; + nameBases[i] = {name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b}; }); } - }() + })(); const notHidden = selection => selection.node() && selection.style("display") !== "none"; const hasChildren = selection => selection.node()?.hasChildNodes(); const hasChild = (selection, selector) => selection.node()?.querySelector(selector); const turnOn = el => document.getElementById(el).classList.remove("buttonoff"); - void function restoreLayersState() { + void (function restoreLayersState() { // turn all layers off - document.getElementById("mapLayers").querySelectorAll("li").forEach(el => el.classList.add("buttonoff")); + document + .getElementById("mapLayers") + .querySelectorAll("li") + .forEach(el => el.classList.add("buttonoff")); // turn on active layers if (notHidden(texture) && hasChild(texture, "image")) turnOn("toggleTexture"); @@ -839,14 +869,14 @@ function parseLoadedData(data) { if (notHidden(scaleBar)) turnOn("toggleScaleBar"); getCurrentPreset(); - }() + })(); - void function restoreEvents() { + void (function restoreEvents() { scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits()); legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend()); - }() + })(); - void function resolveVersionConflicts() { + void (function resolveVersionConflicts() { const version = parseFloat(data[0].split("|")[0]); if (version < 0.9) { // 0.9 has additional relief icons to be included into older maps @@ -860,19 +890,17 @@ function parseLoadedData(data) { // 1.0 adds a legend box legend = svg.append("g").attr("id", "legend"); - legend.attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC") - .attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93) - .attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round"); + legend.attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93).attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round"); // 1.0 separated drawBorders fron drawStates() stateBorders = borders.append("g").attr("id", "stateBorders"); provinceBorders = borders.append("g").attr("id", "provinceBorders"); borders.attr("opacity", null).attr("stroke", null).attr("stroke-width", null).attr("stroke-dasharray", null).attr("stroke-linecap", null).attr("filter", null); - stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt"); - provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt"); + stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt"); + provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt"); // 1.0 adds state relations, provinces, forms and full names - provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", .6); + provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6); BurgsAndStates.collectStatistics(); BurgsAndStates.generateCampaigns(); BurgsAndStates.generateDiplomacy(); @@ -880,7 +908,7 @@ function parseLoadedData(data) { drawStates(); BurgsAndStates.generateProvinces(); drawBorders(); - if (!layerIsOn("toggleBorders")) $('#borders').fadeOut(); + if (!layerIsOn("toggleBorders")) $("#borders").fadeOut(); if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove(); // 1.0 adds hatching @@ -888,9 +916,12 @@ function parseLoadedData(data) { // 1.0 adds zones layer zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none"); - zones.attr("opacity", .6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt"); + zones.attr("opacity", 0.6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt"); addZones(); - if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");} + if (!markers.selectAll("*").size()) { + addMarkers(); + turnButtonOn("toggleMarkers"); + } // 1.0 add fogging layer (state focus) fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none"); @@ -904,7 +935,7 @@ function parseLoadedData(data) { } // 1.0 changed labels to multi-lined - labels.selectAll("textPath").each(function() { + labels.selectAll("textPath").each(function () { const text = this.textContent; const shift = this.getComputedTextLength() / -1.5; this.innerHTML = `${text}`; @@ -923,7 +954,7 @@ function parseLoadedData(data) { // v 1.0 initially has Sympathy status then relaced with Friendly for (const s of pack.states) { if (!s.diplomacy) continue; - s.diplomacy = s.diplomacy.map(r => r === "Sympathy" ? "Friendly" : r); + s.diplomacy = s.diplomacy.map(r => (r === "Sympathy" ? "Friendly" : r)); } // labels should be toggled via style attribute, so remove display attribute @@ -931,20 +962,22 @@ function parseLoadedData(data) { // v 1.0 added religions heirarchy tree if (pack.religions[1] && !pack.religions[1].code) { - pack.religions.filter(r => r.i).forEach(r => { - r.origin = 0; - r.code = r.name.slice(0, 2); - }); + pack.religions + .filter(r => r.i) + .forEach(r => { + r.origin = 0; + r.code = r.name.slice(0, 2); + }); } if (!document.getElementById("freshwater")) { lakes.append("g").attr("id", "freshwater"); - lakes.select("#freshwater").attr("opacity", .5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", .7).attr("filter", null); + lakes.select("#freshwater").attr("opacity", 0.5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", 0.7).attr("filter", null); } if (!document.getElementById("salt")) { lakes.append("g").attr("id", "salt"); - lakes.select("#salt").attr("opacity", .5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", .7).attr("filter", null); + lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null); } // v 1.1 added new lake and coast groups @@ -952,14 +985,14 @@ function parseLoadedData(data) { lakes.append("g").attr("id", "sinkhole"); lakes.append("g").attr("id", "frozen"); lakes.append("g").attr("id", "lava"); - lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", .7).attr("filter", null); - lakes.select("#frozen").attr("opacity", .95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null); - lakes.select("#lava").attr("opacity", .7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)"); + lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", 0.7).attr("filter", null); + lakes.select("#frozen").attr("opacity", 0.95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null); + lakes.select("#lava").attr("opacity", 0.7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)"); coastline.append("g").attr("id", "sea_island"); coastline.append("g").attr("id", "lake_island"); - coastline.select("#sea_island").attr("opacity", .5).attr("stroke", "#1f3846").attr("stroke-width", .7).attr("filter", "url(#dropShadow)"); - coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", .35).attr("filter", null); + coastline.select("#sea_island").attr("opacity", 0.5).attr("stroke", "#1f3846").attr("stroke-width", 0.7).attr("filter", "url(#dropShadow)"); + coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null); } // v 1.1 features stores more data @@ -979,10 +1012,12 @@ function parseLoadedData(data) { // v 1.11 added cultures heirarchy tree if (pack.cultures[1] && !pack.cultures[1].code) { - pack.cultures.filter(c => c.i).forEach(c => { - c.origin = 0; - c.code = c.name.slice(0, 2); - }); + pack.cultures + .filter(c => c.i) + .forEach(c => { + c.origin = 0; + c.code = c.name.slice(0, 2); + }); } // v 1.11 had an issue with fogging being displayed on load @@ -991,12 +1026,12 @@ function parseLoadedData(data) { // v 1.2 added new terrain attributes if (!terrain.attr("set")) terrain.attr("set", "simple"); if (!terrain.attr("size")) terrain.attr("size", 1); - if (!terrain.attr("density")) terrain.attr("density", .4); + if (!terrain.attr("density")) terrain.attr("density", 0.4); } if (version < 1.21) { // v 1.11 replaced "display" attribute by "display" style - viewbox.selectAll("g").each(function() { + viewbox.selectAll("g").each(function () { if (this.hasAttribute("display")) { this.removeAttribute("display"); this.style.display = "none"; @@ -1005,16 +1040,17 @@ function parseLoadedData(data) { // v 1.21 added rivers data to pack pack.rivers = []; // rivers data - rivers.selectAll("path").each(function() { + rivers.selectAll("path").each(function () { const i = +this.id.slice(5); const length = this.getTotalLength() / 2; - const s = this.getPointAtLength(length), e = this.getPointAtLength(0); - const source = findCell(s.x, s.y), mouth = findCell(e.x, e.y); + const s = this.getPointAtLength(length), + e = this.getPointAtLength(0); + const source = findCell(s.x, s.y), + mouth = findCell(e.x, e.y); const name = Rivers.getName(mouth); - const type = length < 25 ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River"; - pack.rivers.push({i, parent:0, length, source, mouth, basin:i, name, type}); + const type = length < 25 ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : "River"; + pack.rivers.push({i, parent: 0, length, source, mouth, basin: i, name, type}); }); - } if (version < 1.22) { @@ -1026,7 +1062,7 @@ function parseLoadedData(data) { // v 1.3 added global options object const winds = options.slice(); // previostly wind was saved in settings[19] const year = rand(100, 2000); - const era = Names.getBaseShort(P(.7) ? 1 : rand(nameBases.length)) + " Era"; + const era = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era"; const eraShort = era[0] + "E"; const military = Military.getDefaultOptions(); options = {winds, year, era, eraShort, military}; @@ -1036,7 +1072,7 @@ function parseLoadedData(data) { // v 1.3 added militry layer armies = viewbox.insert("g", "#icons").attr("id", "armies"); - armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", .3); + armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", 0.3); turnButtonOn("toggleMilitary"); Military.generate(); } @@ -1045,7 +1081,7 @@ function parseLoadedData(data) { // v 1.35 added dry lakes if (!lakes.select("#dry").size()) { lakes.append("g").attr("id", "dry"); - lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", .7).attr("filter", null); + lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null); } // v 1.4 added ice layer @@ -1071,7 +1107,7 @@ function parseLoadedData(data) { } // 1.4 added state reference for regiments - pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => r.state = s.i)); + pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => (r.state = s.i))); } if (version < 1.5) { @@ -1103,7 +1139,7 @@ function parseLoadedData(data) { toggleEmblems(); // v 1.5 changed releif icons data - terrain.selectAll("use").each(function() { + terrain.selectAll("use").each(function () { const type = this.getAttribute("data-type") || this.getAttribute("xlink:href"); this.removeAttribute("xlink:href"); this.removeAttribute("data-type"); @@ -1115,14 +1151,14 @@ function parseLoadedData(data) { if (version < 1.6) { // v 1.6 changed rivers data for (const river of pack.rivers) { - const el = document.getElementById("river"+river.i); + const el = document.getElementById("river" + river.i); if (el) { river.widthFactor = +el.getAttribute("data-width"); el.removeAttribute("data-width"); el.removeAttribute("data-increment"); river.discharge = pack.cells.fl[river.mouth] || 1; river.width = rn(river.length / 100, 2); - river.sourceWidth = .1; + river.sourceWidth = 0.1; } else { Rivers.remove(river.i); } @@ -1137,7 +1173,7 @@ function parseLoadedData(data) { f.temp = grid.cells.temp[pack.cells.g[f.firstCell]]; f.height = f.height || d3.min(pack.cells.c[f.firstCell].map(c => pack.cells.h[c]).filter(h => h >= 20)); const height = (f.height - 18) ** heightExponentInput.value; - const evaporation = (700 * (f.temp + .006 * height) / 50 + 75) / (80 - f.temp); + const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp); f.evaporation = rn(evaporation * f.cells); f.name = f.name || Lakes.getName(f); delete f.river; @@ -1149,31 +1185,34 @@ function parseLoadedData(data) { ruler.style("display", null); rulers = new Rulers(); - ruler.selectAll(".ruler > .white").each(function() { + ruler.selectAll(".ruler > .white").each(function () { const x1 = +this.getAttribute("x1"); const y1 = +this.getAttribute("y1"); const x2 = +this.getAttribute("x2"); const y2 = +this.getAttribute("y2"); if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) return; - const points = [[x1, y1], [x2, y2]]; + const points = [ + [x1, y1], + [x2, y2] + ]; rulers.create(Ruler, points); }); - ruler.selectAll("g.opisometer").each(function() { + ruler.selectAll("g.opisometer").each(function () { const pointsString = this.dataset.points; if (!pointsString) return; const points = JSON.parse(pointsString); rulers.create(Opisometer, points); }); - ruler.selectAll("path.planimeter").each(function() { + ruler.selectAll("path.planimeter").each(function () { const length = this.getTotalLength(); if (length < 30) return; const step = length > 1000 ? 40 : length > 400 ? 20 : 10; const increment = length / Math.ceil(length / step); const points = []; - for (let i=0; i <= length; i += increment) { + for (let i = 0; i <= length; i += increment) { const point = this.getPointAtLength(i); points.push([point.x | 0, point.y | 0]); } @@ -1193,16 +1232,16 @@ function parseLoadedData(data) { const filter = pattern.firstElementChild.getAttribute("filter"); const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : ""; pattern.innerHTML = ``; - document.getElementById("oceanPattern").setAttribute("opacity", .2); + document.getElementById("oceanPattern").setAttribute("opacity", 0.2); } - }() + })(); if (version < 1.62) { // v 1.62 changed grid data gridOverlay.attr("size", null); } - void function checkDataIntegrity() { + void (function checkDataIntegrity() { const cells = pack.cells; if (pack.cells.i.length !== pack.cells.state.length) { @@ -1212,28 +1251,28 @@ function parseLoadedData(data) { const invalidStates = [...new Set(cells.state)].filter(s => !pack.states[s] || pack.states[s].removed); invalidStates.forEach(s => { const invalidCells = cells.i.filter(i => cells.state[i] === s); - invalidCells.forEach(i => cells.state[i] = 0); + invalidCells.forEach(i => (cells.state[i] = 0)); ERROR && console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells); }); const invalidProvinces = [...new Set(cells.province)].filter(p => p && (!pack.provinces[p] || pack.provinces[p].removed)); invalidProvinces.forEach(p => { const invalidCells = cells.i.filter(i => cells.province[i] === p); - invalidCells.forEach(i => cells.province[i] = 0); + invalidCells.forEach(i => (cells.province[i] = 0)); ERROR && console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells); }); const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed); invalidCultures.forEach(c => { const invalidCells = cells.i.filter(i => cells.culture[i] === c); - invalidCells.forEach(i => cells.province[i] = 0); + invalidCells.forEach(i => (cells.province[i] = 0)); ERROR && console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells); }); const invalidReligions = [...new Set(cells.religion)].filter(r => !pack.religions[r] || pack.religions[r].removed); invalidReligions.forEach(r => { const invalidCells = cells.i.filter(i => cells.religion[i] === r); - invalidCells.forEach(i => cells.religion[i] = 0); + invalidCells.forEach(i => (cells.religion[i] = 0)); ERROR && console.error("Data Integrity Check. Invalid religion", c, "is assigned to cells", invalidCells); }); @@ -1247,26 +1286,29 @@ function parseLoadedData(data) { const invalidBurgs = [...new Set(cells.burg)].filter(b => b && (!pack.burgs[b] || pack.burgs[b].removed)); invalidBurgs.forEach(b => { const invalidCells = cells.i.filter(i => cells.burg[i] === b); - invalidCells.forEach(i => cells.burg[i] = 0); + invalidCells.forEach(i => (cells.burg[i] = 0)); ERROR && console.error("Data Integrity Check. Invalid burg", b, "is assigned to cells", invalidCells); }); const invalidRivers = [...new Set(cells.r)].filter(r => r && !pack.rivers.find(river => river.i === r)); invalidRivers.forEach(r => { const invalidCells = cells.i.filter(i => cells.r[i] === r); - invalidCells.forEach(i => cells.r[i] = 0); - rivers.select("river"+r).remove(); + invalidCells.forEach(i => (cells.r[i] = 0)); + rivers.select("river" + r).remove(); ERROR && console.error("Data Integrity Check. Invalid river", r, "is assigned to cells", invalidCells); }); pack.burgs.forEach(b => { if (!b.i || b.removed) return; - if (b.port < 0) {ERROR && console.error("Data Integrity Check. Burg", b.i, "has invalid port value", b.port); b.port = 0;} + if (b.port < 0) { + ERROR && console.error("Data Integrity Check. Burg", b.i, "has invalid port value", b.port); + b.port = 0; + } if (b.cell >= cells.i.length) { ERROR && console.error("Data Integrity Check. Burg", b.i, "is linked to invalid cell", b.cell); b.cell = findCell(b.x, b.y); - cells.i.filter(i => cells.burg[i] === b.i).forEach(i => cells.burg[i] = 0); + cells.i.filter(i => cells.burg[i] === b.i).forEach(i => (cells.burg[i] = 0)); cells.burg[b.cell] = b.i; } @@ -1282,7 +1324,7 @@ function parseLoadedData(data) { ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state); p.removed = true; // remove incorrect province }); - }() + })(); changeMapSize(); @@ -1301,12 +1343,11 @@ function parseLoadedData(data) { focusOn(); // based on searchParams focus on point, cell or burg invokeActiveZooming(); - WARN && console.warn(`TOTAL: ${rn((performance.now()-uploadMap.timeStart)/1000,2)}s`); + WARN && console.warn(`TOTAL: ${rn((performance.now() - uploadMap.timeStart) / 1000, 2)}s`); showStatistics(); INFO && console.groupEnd("Loaded Map " + seed); tip("Map is successfully loaded", true, "success", 7000); - } - catch(error) { + } catch (error) { ERROR && console.error(error); clearMainTip(); @@ -1314,12 +1355,23 @@ function parseLoadedData(data) {
generate a new random map or cancel the loading

${parseError(error)}

`; $("#alert").dialog({ - resizable: false, title: "Loading error", maxWidth:"50em", buttons: { - "Select file": function() {$(this).dialog("close"); mapToLoad.click();}, - "New map": function() {$(this).dialog("close"); regenerateMap();}, - Cancel: function() {$(this).dialog("close")} - }, position: {my: "center", at: "center", of: "svg"} + resizable: false, + title: "Loading error", + maxWidth: "50em", + buttons: { + "Select file": function () { + $(this).dialog("close"); + mapToLoad.click(); + }, + "New map": function () { + $(this).dialog("close"); + regenerateMap(); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} }); } - } diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index f62d2ab0..0b7ef617 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -2,7 +2,7 @@ "use strict"; function editHeightmap() { - void function selectEditMode() { + void (function selectEditMode() { alertMessage.innerHTML = `Heightmap is a core element on which all other data (rivers, burgs, states etc) is based. So the best edit approach is to erase the secondary data and let the system automatically regenerate it on edit completion.

Erase mode also allows you Convert an Image into a heightmap or use Template Editor.

@@ -11,15 +11,26 @@ function editHeightmap() {

Please save the map before editing the heightmap!

Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.

`; - $("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em", + $("#alert").dialog({ + resizable: false, + title: "Edit Heightmap", + width: "28em", buttons: { - Erase: function() {enterHeightmapEditMode("erase");}, - Keep: function() {enterHeightmapEditMode("keep");}, - Risk: function() {enterHeightmapEditMode("risk");}, - Cancel: function() {$(this).dialog("close");} + Erase: function () { + enterHeightmapEditMode("erase"); + }, + Keep: function () { + enterHeightmapEditMode("keep"); + }, + Risk: function () { + enterHeightmapEditMode("risk"); + }, + Cancel: function () { + $(this).dialog("close"); + } } }); - }() + })(); restartHistory(); viewbox.insert("g", "#terrs").attr("id", "heights"); @@ -35,8 +46,8 @@ function editHeightmap() { document.getElementById("heightmap3DView").addEventListener("click", changeViewMode); document.getElementById("finalizeHeightmap").addEventListener("click", finalizeHeightmap); document.getElementById("renderOcean").addEventListener("click", mockHeightmap); - document.getElementById("templateUndo").addEventListener("click", () => restoreHistory(edits.n-1)); - document.getElementById("templateRedo").addEventListener("click", () => restoreHistory(edits.n+1)); + document.getElementById("templateUndo").addEventListener("click", () => restoreHistory(edits.n - 1)); + document.getElementById("templateRedo").addEventListener("click", () => restoreHistory(edits.n + 1)); function enterHeightmapEditMode(type) { editHeightmap.layers = Array.from(mapLayers.querySelectorAll("li:not(.buttonoff)")).map(node => node.id); // store layers preset @@ -77,9 +88,7 @@ function editHeightmap() { exitCustomization.style.bottom = svgHeight / 2 + "px"; exitCustomization.style.transform = "scale(2)"; exitCustomization.style.display = "block"; - d3.select("#exitCustomization") - .transition().duration(1000).style("opacity", 1) - .transition().duration(2000).ease(d3.easeSinInOut).style("right", "10px").style("bottom", "10px").style("transform", "scale(1)"); + d3.select("#exitCustomization").transition().duration(1000).style("opacity", 1).transition().duration(2000).ease(d3.easeSinInOut).style("right", "10px").style("bottom", "10px").style("transform", "scale(1)"); } else exitCustomization.style.display = "block"; openBrushesPanel(); @@ -91,7 +100,8 @@ function editHeightmap() { } function moveCursor() { - const p = d3.mouse(this), cell = findGridCell(p[0], p[1]); + const p = d3.mouse(this), + cell = findGridCell(p[0], p[1]); heightmapInfoX.innerHTML = rn(p[0]); heightmapInfoY.innerHTML = rn(p[1]); heightmapInfoCell.innerHTML = cell; @@ -108,12 +118,13 @@ function editHeightmap() { function getHeight(h) { const unit = heightUnit.value; let unitRatio = 3.281; // default calculations are in feet - if (unit === "m") unitRatio = 1; // if meter + if (unit === "m") unitRatio = 1; + // if meter else if (unit === "f") unitRatio = 0.5468; // if fathom let height = -990; if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value); - else if (h < 20 && h > 0) height = (h - 20) / h * 50; + else if (h < 20 && h > 0) height = ((h - 20) / h) * 50; return rn(height * unitRatio) + " " + unit; } @@ -156,10 +167,14 @@ function editHeightmap() { //viewbox.select("#heights").remove(); document.getElementById("heights").remove(); turnButtonOff("toggleHeight"); - document.getElementById("mapLayers").querySelectorAll("li").forEach(function(e) { - if (editHeightmap.layers.includes(e.id) && !layerIsOn(e.id)) e.click(); // turn on - else if (!editHeightmap.layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off - }); + document + .getElementById("mapLayers") + .querySelectorAll("li") + .forEach(function (e) { + if (editHeightmap.layers.includes(e.id) && !layerIsOn(e.id)) e.click(); + // turn on + else if (!editHeightmap.layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off + }); getCurrentPreset(); } @@ -169,7 +184,7 @@ function editHeightmap() { const change = changeHeights.checked; markFeatures(); - getSignedDistanceField(); + markupGridOcean(); if (change) openNearSeaLakes(); OceanLayers(); calculateTemperatures(); @@ -275,7 +290,7 @@ function editHeightmap() { } // recalculate zones to grid - zones.selectAll("g").each(function() { + zones.selectAll("g").each(function () { const zone = d3.select(this); const dataCells = zone.attr("data-cells"); const cells = dataCells ? dataCells.split(",").map(i => +i) : []; @@ -285,7 +300,7 @@ function editHeightmap() { }); markFeatures(); - getSignedDistanceField(); + markupGridOcean(); OceanLayers(); calculateTemperatures(); generatePrecipitation(); @@ -339,14 +354,12 @@ function editHeightmap() { } // find closest land cell to burg - const findBurgCell = function(x, y) { + const findBurgCell = function (x, y) { let i = findCell(x, y); if (pack.cells.h[i] >= 20) return i; - const dist = pack.cells.c[i].map(c => - pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2 - ); + const dist = pack.cells.c[i].map(c => (pack.cells.h[c] < 20 ? Infinity : (pack.cells.p[c][0] - x) ** 2 + (pack.cells.p[c][1] - y) ** 2)); return pack.cells.c[i][d3.scan(dist)]; - } + }; // find best cell for burgs for (const b of pack.burgs) { @@ -366,13 +379,16 @@ function editHeightmap() { const state = p.state; const stateProvs = pack.states[state].provinces; if (stateProvs.includes(p.i)) pack.states[state].provinces.splice(stateProvs.indexOf(p), 1); - + p.removed = true; continue; } if (p.burg && !pack.burgs[p.burg].removed) p.center = pack.burgs[p.burg].cell; - else {p.center = provCells[0]; p.burg = pack.cells.burg[p.center];} + else { + p.center = provCells[0]; + p.burg = pack.cells.burg[p.center]; + } } for (const c of pack.cultures) { @@ -390,7 +406,7 @@ function editHeightmap() { } // restore zones from grid - zones.selectAll("g").each(function() { + zones.selectAll("g").each(function () { const zone = d3.select(this); const g = zone.attr("data-cells"); const gCells = g ? g.split(",").map(i => +i) : []; @@ -399,8 +415,13 @@ function editHeightmap() { 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); }); TIME && console.timeEnd("restoreRiskedData"); @@ -410,7 +431,7 @@ function editHeightmap() { // trigger heightmap redraw and history update if at least 1 cell is changed function updateHeightmap() { const prev = last(edits); - const changed = grid.cells.h.reduce((s, h, i) => h !== prev[i] ? s+1 : s, 0); + const changed = grid.cells.h.reduce((s, h, i) => (h !== prev[i] ? s + 1 : s), 0); tip("Cells changed: " + changed); if (!changed) return; @@ -429,8 +450,13 @@ function editHeightmap() { function mockHeightmap() { const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20); const scheme = getColorScheme(); - viewbox.select("#heights").selectAll("polygon").data(data).join("polygon") - .attr("points", d => getGridPolygon(d)).attr("id", d => "cell"+d) + viewbox + .select("#heights") + .selectAll("polygon") + .data(data) + .join("polygon") + .attr("points", d => getGridPolygon(d)) + .attr("id", d => "cell" + d) .attr("fill", d => getColor(grid.cells.h[d], scheme)); } @@ -439,18 +465,26 @@ function editHeightmap() { const ocean = renderOcean.checked; const scheme = getColorScheme(); - selection.forEach(function(i) { - let cell = viewbox.select("#heights").select("#cell"+i); - if (!ocean && grid.cells.h[i] < 20) {cell.remove(); return;} - if (!cell.size()) cell = viewbox.select("#heights").append("polygon").attr("points", getGridPolygon(i)).attr("id", "cell"+i); + selection.forEach(function (i) { + let cell = viewbox.select("#heights").select("#cell" + i); + if (!ocean && grid.cells.h[i] < 20) { + cell.remove(); + return; + } + if (!cell.size()) + cell = viewbox + .select("#heights") + .append("polygon") + .attr("points", getGridPolygon(i)) + .attr("id", "cell" + i); cell.attr("fill", getColor(grid.cells.h[i], scheme)); }); } function updateStatistics() { - const landCells = grid.cells.h.reduce((s, h) => h >= 20 ? s+1 : s); - landmassCounter.innerHTML = `${landCells} (${rn(landCells/grid.cells.i.length*100)}%)`; - landmassAverage.innerHTML = rn(d3.mean(grid.cells.h)); + const landCells = grid.cells.h.reduce((s, h) => (h >= 20 ? s + 1 : s)); + landmassCounter.innerHTML = `${landCells} (${rn((landCells / grid.cells.i.length) * 100)}%)`; + landmassAverage.innerHTML = rn(d3.mean(grid.cells.h)); } function updateHistory(noStat) { @@ -477,7 +511,7 @@ function editHeightmap() { grid.cells.h = edits[edits.n - 1].slice(); mockHeightmap(); updateStatistics(); - + if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened if (document.getElementById("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened } @@ -493,10 +527,13 @@ function editHeightmap() { function openBrushesPanel() { if ($("#brushesPanel").is(":visible")) return; - $("#brushesPanel").dialog({ - title: "Paint Brushes", resizable: false, - position: {my: "right top", at: "right-10 top+10", of: "svg"} - }).on('dialogclose', exitBrushMode); + $("#brushesPanel") + .dialog({ + title: "Paint Brushes", + resizable: false, + position: {my: "right top", at: "right-10 top+10", of: "svg"} + }) + .on("dialogclose", exitBrushMode); if (modules.openBrushesPanel) return; modules.openBrushesPanel = true; @@ -504,8 +541,8 @@ function editHeightmap() { // add listeners document.getElementById("brushesButtons").addEventListener("click", e => toggleBrushMode(e)); document.getElementById("changeOnlyLand").addEventListener("click", e => changeOnlyLandClick(e)); - document.getElementById("undo").addEventListener("click", () => restoreHistory(edits.n-1)); - document.getElementById("redo").addEventListener("click", () => restoreHistory(edits.n+1)); + document.getElementById("undo").addEventListener("click", () => restoreHistory(edits.n - 1)); + document.getElementById("redo").addEventListener("click", () => restoreHistory(edits.n + 1)); document.getElementById("rescaleShow").addEventListener("click", () => { document.getElementById("modifyButtons").style.display = "none"; document.getElementById("rescaleSection").style.display = "block"; @@ -513,8 +550,8 @@ function editHeightmap() { document.getElementById("rescaleHide").addEventListener("click", () => { document.getElementById("modifyButtons").style.display = "block"; document.getElementById("rescaleSection").style.display = "none"; - }); - document.getElementById("rescaler").addEventListener("change", (e) => rescale(e.target.valueAsNumber)); + }); + document.getElementById("rescaler").addEventListener("change", e => rescale(e.target.valueAsNumber)); document.getElementById("rescaleCondShow").addEventListener("click", () => { document.getElementById("modifyButtons").style.display = "none"; document.getElementById("rescaleCondSection").style.display = "block"; @@ -527,7 +564,7 @@ function editHeightmap() { document.getElementById("smoothHeights").addEventListener("click", smoothAllHeights); document.getElementById("disruptHeights").addEventListener("click", disruptAllHeights); document.getElementById("brushClear").addEventListener("click", startFromScratch); - + function exitBrushMode() { const pressed = document.querySelector("#brushesButtons > button.pressed"); if (!pressed) return; @@ -539,7 +576,10 @@ function editHeightmap() { } function toggleBrushMode(e) { - if (e.target.classList.contains("pressed")) {exitBrushMode(); return;} + if (e.target.classList.contains("pressed")) { + exitBrushMode(); + return; + } exitBrushMode(); document.getElementById("brushesSliders").style.display = "block"; e.target.classList.add("pressed"); @@ -568,17 +608,19 @@ function editHeightmap() { const power = brushPower.valueAsNumber; const interpolate = d3.interpolateRound(power, 1); const land = changeOnlyLand.checked; - function lim(v) {return Math.max(Math.min(v, 100), land ? 20 : 0);} + function lim(v) { + return Math.max(Math.min(v, 100), land ? 20 : 0); + } const h = grid.cells.h; const brush = document.querySelector("#brushesButtons > button.pressed").id; - if (brush === "brushRaise") s.forEach(i => h[i] = h[i] < 20 ? 20 : lim(h[i] + power)); else - if (brush === "brushElevate") s.forEach((i,d) => h[i] = lim(h[i] + interpolate(d/Math.max(s.length-1, 1)))); else - if (brush === "brushLower") s.forEach(i => h[i] = lim(h[i] - power)); else - if (brush === "brushDepress") s.forEach((i,d) => h[i] = lim(h[i] - interpolate(d/Math.max(s.length-1, 1)))); else - if (brush === "brushAlign") s.forEach(i => h[i] = lim(h[start])); else - if (brush === "brushSmooth") s.forEach(i => h[i] = rn((d3.mean(grid.cells.c[i].filter(i => land ? h[i] >= 20 : 1).map(c => h[c])) + h[i]*(10-power) + .6) / (11-power),1)); else - if (brush === "brushDisrupt") s.forEach(i => h[i] = h[i] < 15 ? h[i] : lim(h[i] + power/1.6 - Math.random()*power)); + if (brush === "brushRaise") s.forEach(i => (h[i] = h[i] < 20 ? 20 : lim(h[i] + power))); + else if (brush === "brushElevate") s.forEach((i, d) => (h[i] = lim(h[i] + interpolate(d / Math.max(s.length - 1, 1))))); + else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power))); + else if (brush === "brushDepress") s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1))))); + else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start]))); + else if (brush === "brushSmooth") s.forEach(i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1))); + else if (brush === "brushDisrupt") s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power))); mockHeightmapSelection(s); // updateHistory(); uncomment to update history every step @@ -592,7 +634,7 @@ function editHeightmap() { function rescale(v) { const land = changeOnlyLand.checked; - grid.cells.h = grid.cells.h.map(h => land && (h < 20 || h+v < 20) ? h : lim(h+v)); + grid.cells.h = grid.cells.h.map(h => (land && (h < 20 || h + v < 20) ? h : lim(h + v))); updateHeightmap(); document.getElementById("rescaler").value = 0; } @@ -601,15 +643,21 @@ function editHeightmap() { const range = rescaleLower.value + "-" + rescaleHigher.value; const operator = conditionSign.value; const operand = rescaleModifier.valueAsNumber; - if (Number.isNaN(operand)) {tip("Operand should be a number", false, "error"); return;} - if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) {tip("Operand should be an integer", false, "error"); return;} + if (Number.isNaN(operand)) { + tip("Operand should be a number", false, "error"); + return; + } + if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) { + tip("Operand should be an integer", false, "error"); + return; + } + + if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0); + else if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0); + else if (operator === "add") HeightmapGenerator.modify(range, operand, 1, 0); + else if (operator === "subtract") HeightmapGenerator.modify(range, -1 * operand, 1, 0); + else if (operator === "exponent") HeightmapGenerator.modify(range, 0, 1, operand); - if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0); else - if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0); else - if (operator === "add") HeightmapGenerator.modify(range, operand, 1, 0); else - if (operator === "subtract") HeightmapGenerator.modify(range, -1 * operand, 1, 0); else - if (operator === "exponent") HeightmapGenerator.modify(range, 0, 1, operand); - updateHeightmap(); } @@ -619,19 +667,24 @@ function editHeightmap() { } function disruptAllHeights() { - grid.cells.h = grid.cells.h.map(h => h < 15 ? h : lim(h + 2.5 - Math.random() * 4)); + grid.cells.h = grid.cells.h.map(h => (h < 15 ? h : lim(h + 2.5 - Math.random() * 4))); updateHeightmap(); } - + function startFromScratch() { - if (changeOnlyLand.checked) {tip("Not allowed when 'Change only land cells' mode is set", false, "error"); return;} + if (changeOnlyLand.checked) { + tip("Not allowed when 'Change only land cells' mode is set", false, "error"); + return; + } const someHeights = grid.cells.h.some(h => h); - if (!someHeights) {tip("Heightmap is already cleared, please do not click twice if not required", false, "error"); return;} + if (!someHeights) { + tip("Heightmap is already cleared, please do not click twice if not required", false, "error"); + return; + } grid.cells.h = new Uint8Array(grid.cells.i.length); viewbox.select("#heights").selectAll("*").remove(); updateHistory(); } - } function openTemplateEditor() { @@ -639,22 +692,25 @@ function editHeightmap() { const body = document.getElementById("templateBody"); $("#templateEditor").dialog({ - title: "Template Editor", minHeight: "auto", width: "fit-content", resizable: false, + title: "Template Editor", + minHeight: "auto", + width: "fit-content", + resizable: false, position: {my: "right top", at: "right-10 top+10", of: "svg"} }); if (modules.openTemplateEditor) return; modules.openTemplateEditor = true; - + $("#templateBody").sortable({items: "> div", handle: ".icon-resize-vertical", containment: "#templateBody", axis: "y"}); // add listeners - body.addEventListener("click", function(ev) { + body.addEventListener("click", function (ev) { const el = ev.target; if (el.classList.contains("icon-check")) { el.classList.remove("icon-check"); el.classList.add("icon-check-empty"); - el.parentElement.style.opacity = .5; + el.parentElement.style.opacity = 0.5; body.dataset.changed = 1; return; } @@ -665,7 +721,8 @@ function editHeightmap() { return; } if (el.classList.contains("icon-trash-empty")) { - el.parentElement.remove(); return; + el.parentElement.remove(); + return; } }); @@ -674,7 +731,9 @@ function editHeightmap() { document.getElementById("templateRun").addEventListener("click", executeTemplate); document.getElementById("templateSave").addEventListener("click", downloadTemplate); document.getElementById("templateLoad").addEventListener("click", () => templateToLoad.click()); - document.getElementById("templateToLoad").addEventListener("change", function() {uploadFile(this, uploadTemplate)}); + document.getElementById("templateToLoad").addEventListener("change", function () { + uploadFile(this, uploadTemplate); + }); function addStepOnClick(e) { if (e.target.tagName !== "BUTTON") return; @@ -684,12 +743,14 @@ function editHeightmap() { } function addStep(type, count, dist, arg4, arg5) { - const body = document.getElementById("templateBody"); + const body = document.getElementById("templateBody"); body.insertAdjacentHTML("beforeend", getStepHTML(type, count, dist, arg4, arg5)); const elDist = body.querySelector("div:last-child").querySelector(".templateDist"); if (elDist) elDist.addEventListener("change", setRange); if (dist && elDist && elDist.tagName === "SELECT") { - for (const o of elDist.options) {if (o.value === dist) elDist.value = dist;} + for (const o of elDist.options) { + if (o.value === dist) elDist.value = dist; + } if (elDist.value !== dist) { const opt = document.createElement("option"); opt.value = opt.innerHTML = dist; @@ -705,23 +766,23 @@ function editHeightmap() { const Reorder = ``; const common = `
${Hide}
${type}
${Trash}${Reorder}`; - const TempY = `y:`; - const TempX = `x:`; - const Height = `h:`; - const Count = `n:`; + const TempY = `y:`; + const TempX = `x:`; + const Height = `h:`; + const Count = `n:`; const blob = `${common}${TempY}${TempX}${Height}${Count}
`; if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return blob; - if (type === "Strait") return `${common}d:w:`; - if (type === "Add") return `${common}to:v:`; - if (type === "Multiply") return `${common}to:v:`; - if (type === "Smooth") return `${common}f:`; + if (type === "Strait") return `${common}d:w:`; + if (type === "Add") return `${common}to:v:`; + if (type === "Multiply") return `${common}to:v:`; + if (type === "Smooth") return `${common}f:`; } function setRange(event) { if (event.target.value !== "interval") return; - prompt("Set a height interval. Avoid space, use hyphen as a separator", {default:"17-20"}, v => { + prompt("Set a height interval. Avoid space, use hyphen as a separator", {default: "17-20"}, v => { const opt = document.createElement("option"); opt.value = opt.innerHTML = v; event.target.add(opt); @@ -734,13 +795,24 @@ function editHeightmap() { const steps = body.querySelectorAll("div").length; const changed = +body.getAttribute("data-changed"); const template = e.target.value; - if (!steps || !changed) {changeTemplate(template); return;} + if (!steps || !changed) { + changeTemplate(template); + return; + } alertMessage.innerHTML = "Are you sure you want to select a different template? All changes will be lost."; - $("#alert").dialog({resizable: false, title: "Change Template", + $("#alert").dialog({ + resizable: false, + title: "Change Template", buttons: { - Change: function() {changeTemplate(template); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");}} + Change: function () { + changeTemplate(template); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + } }); } @@ -751,15 +823,13 @@ function editHeightmap() { if (template === "templateVolcano") { addStep("Hill", "1", "90-100", "44-56", "40-60"); - addStep("Multiply", .8, "50-100"); + addStep("Multiply", 0.8, "50-100"); addStep("Range", "1.5", "30-55", "45-55", "40-60"); addStep("Smooth", 2); addStep("Hill", "1.5", "25-35", "25-30", "20-75"); addStep("Hill", "1", "25-35", "75-80", "25-75"); addStep("Hill", "0.5", "20-25", "10-15", "20-25"); - } - - else if (template === "templateHighIsland") { + } else if (template === "templateHighIsland") { addStep("Hill", "1", "90-100", "65-75", "47-53"); addStep("Add", 5, "all"); addStep("Hill", "6", "20-23", "25-55", "45-55"); @@ -769,13 +839,11 @@ function editHeightmap() { addStep("Trough", "2-3", "20-30", "60-80", "70-80"); addStep("Hill", "1", "10-15", "60-60", "50-50"); addStep("Hill", "1.5", "13-16", "15-20", "20-75"); - addStep("Multiply", .8, "20-100"); + addStep("Multiply", 0.8, "20-100"); addStep("Range", "1.5", "30-40", "15-85", "30-40"); addStep("Range", "1.5", "30-40", "15-85", "60-70"); addStep("Pit", "2-3", "10-15", "15-85", "20-80"); - } - - else if (template === "templateLowIsland") { + } else if (template === "templateLowIsland") { addStep("Hill", "1", "90-99", "60-80", "45-55"); addStep("Hill", "4-5", "25-35", "20-65", "40-60"); addStep("Range", "1", "40-50", "45-55", "45-55"); @@ -785,13 +853,11 @@ function editHeightmap() { addStep("Hill", "1.5", "10-15", "5-15", "20-80"); addStep("Hill", "1", "10-15", "85-95", "70-80"); addStep("Pit", "3-5", "10-15", "15-85", "20-80"); - addStep("Multiply", .4, "20-100"); - } - - else if (template === "templateContinents") { + addStep("Multiply", 0.4, "20-100"); + } else if (template === "templateContinents") { addStep("Hill", "1", "80-85", "75-80", "40-60"); addStep("Hill", "1", "80-85", "20-25", "40-60"); - addStep("Multiply", .22, "20-100"); + addStep("Multiply", 0.22, "20-100"); addStep("Hill", "5-6", "15-20", "25-75", "20-82"); addStep("Range", ".8", "30-60", "5-15", "20-45"); addStep("Range", ".8", "30-60", "5-15", "55-80"); @@ -802,9 +868,7 @@ function editHeightmap() { addStep("Trough", "1-2", "5-10", "45-55", "45-55"); addStep("Pit", "3-4", "10-15", "15-85", "20-80"); addStep("Hill", "1", "5-10", "40-60", "40-60"); - } - - else if (template === "templateArchipelago") { + } else if (template === "templateArchipelago") { addStep("Add", 11, "all"); addStep("Range", "2-3", "40-60", "20-80", "20-80"); addStep("Hill", "5", "15-20", "10-90", "30-70"); @@ -814,18 +878,14 @@ function editHeightmap() { addStep("Trough", "10", "20-30", "5-95", "5-95"); addStep("Strait", "2", "vertical"); addStep("Strait", "2", "horizontal"); - } - - else if (template === "templateAtoll") { + } else if (template === "templateAtoll") { addStep("Hill", "1", "75-80", "50-60", "45-55"); addStep("Hill", "1.5", "30-50", "25-75", "30-70"); addStep("Hill", ".5", "30-50", "25-35", "30-70"); addStep("Smooth", 1); - addStep("Multiply", .2, "25-100"); - addStep("Hill", ".5", "10-20", "50-55", "48-52"); - } - - else if (template === "templateMediterranean") { + addStep("Multiply", 0.2, "25-100"); + addStep("Hill", ".5", "10-20", "50-55", "48-52"); + } else if (template === "templateMediterranean") { addStep("Range", "3-4", "30-50", "0-100", "0-10"); addStep("Range", "3-4", "30-50", "0-100", "90-100"); addStep("Hill", "5-6", "30-70", "0-100", "0-5"); @@ -833,12 +893,10 @@ function editHeightmap() { addStep("Smooth", 1); addStep("Hill", "2-3", "30-70", "0-5", "20-80"); addStep("Hill", "2-3", "30-70", "95-100", "20-80"); - addStep("Multiply", .8, "land"); + addStep("Multiply", 0.8, "land"); addStep("Trough", "3-5", "40-50", "0-100", "0-10"); addStep("Trough", "3-5", "40-50", "0-100", "90-100"); - } - - else if (template === "templatePeninsula") { + } else if (template === "templatePeninsula") { addStep("Range", "2-3", "20-35", "40-50", "0-15"); addStep("Add", 5, "all"); addStep("Hill", "1", "90-100", "10-90", "0-5"); @@ -847,22 +905,18 @@ function editHeightmap() { addStep("Hill", "1-2", "3-5", "5-95", "40-60"); addStep("Trough", "5-6", "10-25", "5-95", "5-95"); addStep("Smooth", 3); - } - - else if (template === "templatePangea") { + } else if (template === "templatePangea") { addStep("Hill", "1-2", "25-40", "15-50", "0-10"); addStep("Hill", "1-2", "5-40", "50-85", "0-10"); addStep("Hill", "1-2", "25-40", "50-85", "90-100"); addStep("Hill", "1-2", "5-40", "15-50", "90-100"); addStep("Hill", "8-12", "20-40", "20-80", "48-52"); addStep("Smooth", 2); - addStep("Multiply", .7, "land"); + addStep("Multiply", 0.7, "land"); addStep("Trough", "3-4", "25-35", "5-95", "10-20"); addStep("Trough", "3-4", "25-35", "5-95", "80-90"); addStep("Range", "5-6", "30-40", "10-90", "35-65"); - } - - else if (template === "templateIsthmus") { + } else if (template === "templateIsthmus") { addStep("Hill", "5-10", "15-30", "0-30", "0-20"); addStep("Hill", "5-10", "15-30", "10-50", "20-40"); addStep("Hill", "5-10", "15-30", "30-70", "40-60"); @@ -874,15 +928,12 @@ function editHeightmap() { addStep("Trough", "4-8", "15-30", "30-70", "40-60"); addStep("Trough", "4-8", "15-30", "50-90", "60-80"); addStep("Trough", "4-8", "15-30", "70-100", "80-100"); - } - - else if (template === "templateShattered") { + } else if (template === "templateShattered") { addStep("Hill", "8", "35-40", "15-85", "30-70"); addStep("Trough", "10-20", "40-50", "5-95", "5-95"); addStep("Range", "5-7", "30-40", "10-90", "20-80"); addStep("Pit", "12-20", "30-40", "15-85", "20-80"); } - } function executeTemplate() { @@ -893,7 +944,7 @@ function editHeightmap() { grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights for (const s of steps) { - if (s.style.opacity == .5) continue; + if (s.style.opacity == 0.5) continue; const type = s.getAttribute("data-type"); const elCount = s.querySelector(".templateCount") || ""; const elHeight = s.querySelector(".templateHeight") || ""; @@ -906,14 +957,14 @@ function editHeightmap() { const templateY = s.querySelector(".templateY"); const y = templateY ? templateY.value : null; - if (type === "Hill") HeightmapGenerator.addHill(elCount.value, elHeight.value, x, y); else - if (type === "Pit") HeightmapGenerator.addPit(elCount.value, elHeight.value, x, y); else - if (type === "Range") HeightmapGenerator.addRange(elCount.value, elHeight.value, x, y); else - if (type === "Trough") HeightmapGenerator.addTrough(elCount.value, elHeight.value, x, y); else - if (type === "Strait") HeightmapGenerator.addStrait(elCount.value, dist); else - if (type === "Add") HeightmapGenerator.modify(dist, +elCount.value, 1); else - if (type === "Multiply") HeightmapGenerator.modify(dist, 0, +elCount.value); else - if (type === "Smooth") HeightmapGenerator.smooth(+elCount.value); + if (type === "Hill") HeightmapGenerator.addHill(elCount.value, elHeight.value, x, y); + else if (type === "Pit") HeightmapGenerator.addPit(elCount.value, elHeight.value, x, y); + else if (type === "Range") HeightmapGenerator.addRange(elCount.value, elHeight.value, x, y); + else if (type === "Trough") HeightmapGenerator.addTrough(elCount.value, elHeight.value, x, y); + else if (type === "Strait") HeightmapGenerator.addStrait(elCount.value, dist); + else if (type === "Add") HeightmapGenerator.modify(dist, +elCount.value, 1); + else if (type === "Multiply") HeightmapGenerator.modify(dist, 0, +elCount.value); + else if (type === "Smooth") HeightmapGenerator.smooth(+elCount.value); updateHistory("noStat"); // update history every step } @@ -932,7 +983,7 @@ function editHeightmap() { let data = ""; for (const s of steps) { - if (s.style.opacity == .5) continue; + if (s.style.opacity == 0.5) continue; const type = s.getAttribute("data-type"); const elCount = s.querySelector(".templateCount"); const count = elCount ? elCount.value : "0"; @@ -952,14 +1003,19 @@ function editHeightmap() { function uploadTemplate(dataLoaded) { const steps = dataLoaded.split("\r\n"); - if (!steps.length) {tip("Cannot parse the template, please check the file", false, "error"); return;} + if (!steps.length) { + tip("Cannot parse the template, please check the file", false, "error"); + return; + } templateBody.innerHTML = ""; for (const s of steps) { const step = s.split(" "); - if (step.length !== 5) {ERROR && console.error("Cannot parse step, wrong arguments count", s); continue;} + if (step.length !== 5) { + ERROR && console.error("Cannot parse step, wrong arguments count", s); + continue; + } addStep(step[0], step[1], step[2], step[3], step[4]); } - } } @@ -969,7 +1025,10 @@ function editHeightmap() { closeDialogs("#imageConverter"); $("#imageConverter").dialog({ - title: "Image Converter", maxHeight: svgHeight*.8, minHeight: "auto", width: "20em", + title: "Image Converter", + maxHeight: svgHeight * 0.8, + minHeight: "auto", + width: "20em", position: {my: "right top", at: "right-10 top+10", of: "svg"}, beforeClose: closeImageConverter }); @@ -983,7 +1042,7 @@ function editHeightmap() { setOverlayOpacity(0); clearMainTip(); - tip('Image Converter is opened. Upload image and assign height value for each color', false, "warn"); // main tip + tip("Image Converter is opened. Upload image and assign height value for each color", false, "warn"); // main tip // remove all heights grid.cells.h = new Uint8Array(grid.cells.i.length); @@ -994,13 +1053,18 @@ function editHeightmap() { modules.openImageConverter = true; // add color pallete - void function createColorPallete() { - d3.select("#imageConverterPalette").selectAll("div").data(d3.range(101)) - .enter().append("div").attr("data-color", i => i) - .style("background-color", i => color(1-(i < 20 ? i-5 : i) / 100)) - .style("width", i => i < 40 || i > 68 ? ".2em" : ".1em") - .on("touchmove mousemove", showPalleteHeight).on("click", assignHeight); - }() + void (function createColorPallete() { + d3.select("#imageConverterPalette") + .selectAll("div") + .data(d3.range(101)) + .enter() + .append("div") + .attr("data-color", i => i) + .style("background-color", i => color(1 - (i < 20 ? i - 5 : i) / 100)) + .style("width", i => (i < 40 || i > 68 ? ".2em" : ".1em")) + .on("touchmove mousemove", showPalleteHeight) + .on("click", assignHeight); + })(); // add listeners document.getElementById("convertImageLoad").addEventListener("click", () => imageToLoad.click()); @@ -1011,14 +1075,18 @@ function editHeightmap() { document.getElementById("convertColorsButton").addEventListener("click", setConvertColorsNumber); document.getElementById("convertComplete").addEventListener("click", applyConversion); document.getElementById("convertCancel").addEventListener("click", cancelConversion); - document.getElementById("convertOverlay").addEventListener("input", function() {setOverlayOpacity(this.value)}); - document.getElementById("convertOverlayNumber").addEventListener("input", function() {setOverlayOpacity(this.value)}); + document.getElementById("convertOverlay").addEventListener("input", function () { + setOverlayOpacity(this.value); + }); + document.getElementById("convertOverlayNumber").addEventListener("input", function () { + setOverlayOpacity(this.value); + }); function showPalleteHeight() { const height = +this.getAttribute("data-color"); colorsSelectValue.innerHTML = height; colorsSelectFriendly.innerHTML = getHeight(height); - const former = imageConverterPalette.querySelector(".hoveredColor") + const former = imageConverterPalette.querySelector(".hoveredColor"); if (former) former.className = ""; this.className = "hoveredColor"; } @@ -1028,15 +1096,15 @@ function editHeightmap() { this.value = ""; // reset input value to get triggered if the file is re-uploaded const reader = new FileReader(); - const img = new Image; - img.onload = function() { + const img = new Image(); + img.onload = function () { const ctx = document.getElementById("canvas").getContext("2d"); ctx.drawImage(img, 0, 0, graphWidth, graphHeight); heightsFromImage(+convertColors.value); resetZoom(); }; - reader.onloadend = () => img.src = reader.result; + reader.onloadend = () => (img.src = reader.result); reader.readAsDataURL(file); } @@ -1045,9 +1113,9 @@ function editHeightmap() { const sampleCanvas = document.createElement("canvas"); sampleCanvas.width = grid.cellsX; sampleCanvas.height = grid.cellsY; - sampleCanvas.getContext('2d').drawImage(sourceImage, 0, 0, grid.cellsX, grid.cellsY); + sampleCanvas.getContext("2d").drawImage(sourceImage, 0, 0, grid.cellsX, grid.cellsY); - const q = new RgbQuant({colors:count}); + const q = new RgbQuant({colors: count}); q.sample(sampleCanvas); const data = q.reduce(sampleCanvas); const pallete = q.palette(true); @@ -1059,15 +1127,26 @@ function editHeightmap() { colorsAssigned.style.display = "none"; sampleCanvas.remove(); // no need to keep - viewbox.select("#heights").selectAll("polygon").data(grid.cells.i).join("polygon") - .attr("points", d => getGridPolygon(d)).attr("id", d => "cell"+d) - .attr("fill", d => `rgb(${data[d*4]}, ${data[d*4+1]}, ${data[d*4+2]})`) + viewbox + .select("#heights") + .selectAll("polygon") + .data(grid.cells.i) + .join("polygon") + .attr("points", d => getGridPolygon(d)) + .attr("id", d => "cell" + d) + .attr("fill", d => `rgb(${data[d * 4]}, ${data[d * 4 + 1]}, ${data[d * 4 + 2]})`) .on("click", mapClicked); const colors = pallete.map(p => `rgb(${p[0]}, ${p[1]}, ${p[2]})`); - d3.select("#colorsUnassigned").selectAll("div").data(colors).enter().append("div") - .attr("data-color", i => i).style("background-color", i => i) - .attr("class", "color-div").on("click", colorClicked); + d3.select("#colorsUnassigned") + .selectAll("div") + .data(colors) + .enter() + .append("div") + .attr("data-color", i => i) + .style("background-color", i => i) + .attr("class", "color-div") + .on("click", colorClicked); document.getElementById("colorsUnassignedNumber").innerHTML = colors.length; } @@ -1100,21 +1179,27 @@ function editHeightmap() { const color = this.getAttribute("data-color"); viewbox.select("#heights").selectAll("polygon.selectedCell").classed("selectedCell", 0); - viewbox.select("#heights").selectAll("polygon[fill='" + color + "']").classed("selectedCell", 1); + viewbox + .select("#heights") + .selectAll("polygon[fill='" + color + "']") + .classed("selectedCell", 1); } function assignHeight() { const height = +this.dataset.color; - const rgb = color(1 - (height < 20 ? height-5 : height) / 100); + const rgb = color(1 - (height < 20 ? height - 5 : height) / 100); const selectedColor = imageConverter.querySelector("div.selectedColor"); selectedColor.style.backgroundColor = rgb; selectedColor.setAttribute("data-color", rgb); selectedColor.setAttribute("data-height", height); - viewbox.select("#heights").selectAll(".selectedCell").each(function() { - this.setAttribute("fill", rgb); - this.setAttribute("data-height", height); - }); + viewbox + .select("#heights") + .selectAll(".selectedCell") + .each(function () { + this.setAttribute("fill", rgb); + this.setAttribute("data-height", height); + }); if (selectedColor.parentNode.id === "colorsUnassigned") { colorsAssigned.appendChild(selectedColor); @@ -1123,7 +1208,6 @@ function editHeightmap() { document.getElementById("colorsUnassignedNumber").innerHTML = colorsUnassigned.childElementCount - 2; document.getElementById("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2; } - } // auto assign color based on luminosity or hue @@ -1133,42 +1217,49 @@ function editHeightmap() { heightsFromImage(+convertColors.value); unassigned = colorsUnassigned.querySelectorAll("div"); if (!unassigned.length) { - tip("No unassigned colors. Please load an image and click the button again", false, "error"); + tip("No unassigned colors. Please load an image and click the button again", false, "error"); return; } } - const getHeightByHue = function(color) { + const getHeightByHue = function (color) { let hue = d3.hsl(color).h; if (hue > 300) hue -= 360; - if (hue > 170) return Math.abs(hue-250) / 3 |0; // water - return Math.abs(hue-250+20) / 3 |0; // land - } + if (hue > 170) return (Math.abs(hue - 250) / 3) | 0; // water + return (Math.abs(hue - 250 + 20) / 3) | 0; // land + }; - const getHeightByLum = function(color) { + const getHeightByLum = function (color) { let lum = d3.lab(color).l; - if (lum < 13) return lum / 13 * 20 |0; // water - return lum|0; // land - } + if (lum < 13) return ((lum / 13) * 20) | 0; // water + return lum | 0; // land + }; const scheme = d3.range(101).map(i => getColor(i, color())); - const hues = scheme.map(rgb => d3.hsl(rgb).h|0); - const getHeightByScheme = function(color) { + const hues = scheme.map(rgb => d3.hsl(rgb).h | 0); + const getHeightByScheme = function (color) { let height = scheme.indexOf(color); if (height !== -1) return height; // exact match const hue = d3.hsl(color).h; const closest = hues.reduce((prev, curr) => (Math.abs(curr - hue) < Math.abs(prev - hue) ? curr : prev)); return hues.indexOf(closest); - } + }; const assinged = []; // store assigned heights unassigned.forEach(el => { const clr = el.dataset.color; const height = type === "hue" ? getHeightByHue(clr) : type === "lum" ? getHeightByLum(clr) : getHeightByScheme(clr); - const colorTo = color(1 - (height < 20 ? (height-5) / 100 : height / 100)); - viewbox.select("#heights").selectAll("polygon[fill='" + clr + "']").attr("fill", colorTo).attr("data-height", height); + const colorTo = color(1 - (height < 20 ? (height - 5) / 100 : height / 100)); + viewbox + .select("#heights") + .selectAll("polygon[fill='" + clr + "']") + .attr("fill", colorTo) + .attr("data-height", height); - if (assinged[height]) {el.remove(); return;} // if color is already added, remove it + if (assinged[height]) { + el.remove(); + return; + } // if color is already added, remove it el.style.backgroundColor = el.dataset.color = colorTo; el.dataset.height = height; colorsAssigned.appendChild(el); @@ -1186,8 +1277,7 @@ function editHeightmap() { } function setConvertColorsNumber() { - prompt(`Please set maximum number of colors.
An actual number is usually lower and depends on color scheme`, - {default:+convertColors.value, step:1, min:3, max:255}, number => { + prompt(`Please set maximum number of colors.
An actual number is usually lower and depends on color scheme`, {default: +convertColors.value, step: 1, min: 3, max: 255}, number => { convertColors.value = number; heightsFromImage(number); }); @@ -1204,11 +1294,14 @@ function editHeightmap() { return; } - viewbox.select("#heights").selectAll("polygon").each(function() { - const height = +this.dataset.height || 0; - const i = +this.id.slice(4); - grid.cells.h[i] = height; - }); + viewbox + .select("#heights") + .selectAll("polygon") + .each(function () { + const height = +this.dataset.height || 0; + const i = +this.id.slice(4); + grid.cells.h[i] = height; + }); viewbox.select("#heights").selectAll("polygon").remove(); updateHeightmap(); @@ -1218,7 +1311,7 @@ function editHeightmap() { function cancelConversion() { restoreImageConverterState(); viewbox.select("#heights").selectAll("polygon").remove(); - restoreHistory(edits.n-1); + restoreHistory(edits.n - 1); } function restoreImageConverterState() { @@ -1244,20 +1337,22 @@ function editHeightmap() { Click "Complete" to apply the conversion. Click "Close" to exit conversion mode and restore previous heightmap`; - $("#alert").dialog({resizable: false, title: "Close Image Converter", + $("#alert").dialog({ + resizable: false, + title: "Close Image Converter", buttons: { - Cancel: function() { + Cancel: function () { $(this).dialog("close"); }, - Complete: function() { + Complete: function () { $(this).dialog("close"); applyConversion(); }, - Close: function() { + Close: function () { $(this).dialog("close"); restoreImageConverterState(); viewbox.select("#heights").selectAll("polygon").remove(); - restoreHistory(edits.n-1); + restoreHistory(edits.n - 1); } } }); @@ -1285,11 +1380,11 @@ function editHeightmap() { grid.cells.h.forEach((height, i) => { let h = height < 20 ? Math.max(height / 1.5, 0) : height; - const v = h / 100 * 255; - imageData.data[i*4] = v; - imageData.data[i*4 + 1] = v; - imageData.data[i*4 + 2] = v; - imageData.data[i*4 + 3] = 255; + const v = (h / 100) * 255; + imageData.data[i * 4] = v; + imageData.data[i * 4 + 1] = v; + imageData.data[i * 4 + 2] = v; + imageData.data[i * 4 + 3] = 255; }); ctx.putImageData(imageData, 0, 0); @@ -1302,7 +1397,7 @@ function editHeightmap() { const img = new Image(); img.src = dataURL; - img.onload = function() { + img.onload = function () { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = svgWidth; @@ -1315,7 +1410,6 @@ function editHeightmap() { link.href = imgBig; link.click(); canvas.remove(); - } + }; } - } diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 1ab8f192..16b8fa87 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -1,44 +1,57 @@ // module to control the Tools options (click to edit, to re-geenerate, tp add) "use strict"; -toolsContent.addEventListener("click", function(event) { - if (customization) {tip("Please exit the customization mode first", false, "warning"); return;} +toolsContent.addEventListener("click", function (event) { + if (customization) { + tip("Please exit the customization mode first", false, "warning"); + return; + } if (event.target.tagName !== "BUTTON") return; const button = event.target.id; // Click to open Editor buttons - if (button === "editHeightmapButton") editHeightmap(); else - if (button === "editBiomesButton") editBiomes(); else - if (button === "editStatesButton") editStates(); else - if (button === "editProvincesButton") editProvinces(); else - if (button === "editDiplomacyButton") editDiplomacy(); else - if (button === "editCulturesButton") editCultures(); else - if (button === "editReligions") editReligions(); else - if (button === "editEmblemButton") openEmblemEditor(); else - if (button === "editNamesBaseButton") editNamesbase(); else - if (button === "editUnitsButton") editUnits(); else - if (button === "editNotesButton") editNotes(); else - if (button === "editZonesButton") editZones(); else - if (button === "overviewBurgsButton") overviewBurgs(); else - if (button === "overviewRiversButton") overviewRivers(); else - if (button === "overviewMilitaryButton") overviewMilitary(); else - if (button === "overviewCellsButton") viewCellDetails(); + if (button === "editHeightmapButton") editHeightmap(); + else if (button === "editBiomesButton") editBiomes(); + else if (button === "editStatesButton") editStates(); + else if (button === "editProvincesButton") editProvinces(); + else if (button === "editDiplomacyButton") editDiplomacy(); + else if (button === "editCulturesButton") editCultures(); + else if (button === "editReligions") editReligions(); + else if (button === "editEmblemButton") openEmblemEditor(); + else if (button === "editNamesBaseButton") editNamesbase(); + else if (button === "editUnitsButton") editUnits(); + else if (button === "editNotesButton") editNotes(); + else if (button === "editZonesButton") editZones(); + else if (button === "overviewBurgsButton") overviewBurgs(); + else if (button === "overviewRiversButton") overviewRivers(); + else if (button === "overviewMilitaryButton") overviewMilitary(); + else if (button === "overviewCellsButton") viewCellDetails(); // Click to Regenerate buttons if (event.target.parentNode.id === "regenerateFeature") { - if (sessionStorage.getItem("regenerateFeatureDontAsk")) {processFeatureRegeneration(event, button); return;} + if (sessionStorage.getItem("regenerateFeatureDontAsk")) { + processFeatureRegeneration(event, button); + return; + } - alertMessage.innerHTML = `Regeneration will remove all the custom changes for the element.

Are you sure you want to proceed?` - $("#alert").dialog({resizable: false, title: "Regenerate element", + alertMessage.innerHTML = `Regeneration will remove all the custom changes for the element.

Are you sure you want to proceed?`; + $("#alert").dialog({ + resizable: false, + title: "Regenerate element", buttons: { - Proceed: function() {processFeatureRegeneration(event, button); $(this).dialog("close");}, - Cancel: function() {$(this).dialog("close");} + Proceed: function () { + processFeatureRegeneration(event, button); + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } }, - open: function() { + open: function () { const pane = $(this).dialog("widget").find(".ui-dialog-buttonpane"); $('').prependTo(pane); }, - close: function() { + close: function () { const box = $(this).dialog("widget").find(".checkbox")[0]; if (!box) return; if (box.checked) sessionStorage.setItem("regenerateFeatureDontAsk", true); @@ -48,29 +61,35 @@ toolsContent.addEventListener("click", function(event) { } // Click to Add buttons - if (button === "addLabel") toggleAddLabel(); else - if (button === "addBurgTool") toggleAddBurg(); else - if (button === "addRiver") toggleAddRiver(); else - if (button === "addRoute") toggleAddRoute(); else - if (button === "addMarker") toggleAddMarker(); + if (button === "addLabel") toggleAddLabel(); + else if (button === "addBurgTool") toggleAddBurg(); + else if (button === "addRiver") toggleAddRiver(); + else if (button === "addRoute") toggleAddRoute(); + else if (button === "addMarker") toggleAddMarker(); }); function processFeatureRegeneration(event, button) { - if (button === "regenerateStateLabels") {BurgsAndStates.drawStateLabels(); if (!layerIsOn("toggleLabels")) toggleLabels();} else - if (button === "regenerateReliefIcons") {ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief();} else - if (button === "regenerateRoutes") {Routes.regenerate(); if (!layerIsOn("toggleRoutes")) toggleRoutes();} else - if (button === "regenerateRivers") regenerateRivers(); else - if (button === "regeneratePopulation") recalculatePopulation(); else - if (button === "regenerateStates") regenerateStates(); else - if (button === "regenerateProvinces") regenerateProvinces(); else - if (button === "regenerateBurgs") regenerateBurgs(); else - if (button === "regenerateEmblems") regenerateEmblems(); else - if (button === "regenerateReligions") regenerateReligions(); else - if (button === "regenerateCultures") regenerateCultures(); else - if (button === "regenerateMilitary") regenerateMilitary(); else - if (button === "regenerateIce") regenerateIce(); else - if (button === "regenerateMarkers") regenerateMarkers(event); else - if (button === "regenerateZones") regenerateZones(event); + if (button === "regenerateStateLabels") { + BurgsAndStates.drawStateLabels(); + if (!layerIsOn("toggleLabels")) toggleLabels(); + } else if (button === "regenerateReliefIcons") { + ReliefIcons(); + if (!layerIsOn("toggleRelief")) toggleRelief(); + } else if (button === "regenerateRoutes") { + Routes.regenerate(); + if (!layerIsOn("toggleRoutes")) toggleRoutes(); + } else if (button === "regenerateRivers") regenerateRivers(); + else if (button === "regeneratePopulation") recalculatePopulation(); + else if (button === "regenerateStates") regenerateStates(); + else if (button === "regenerateProvinces") regenerateProvinces(); + else if (button === "regenerateBurgs") regenerateBurgs(); + else if (button === "regenerateEmblems") regenerateEmblems(); + else if (button === "regenerateReligions") regenerateReligions(); + else if (button === "regenerateCultures") regenerateCultures(); + else if (button === "regenerateMilitary") regenerateMilitary(); + else if (button === "regenerateIce") regenerateIce(); + else if (button === "regenerateMarkers") regenerateMarkers(event); + else if (button === "regenerateZones") regenerateZones(event); } async function openEmblemEditor() { @@ -106,10 +125,10 @@ function recalculatePopulation() { if (!b.i || b.removed || b.lock) return; const i = b.cell; - b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3); + b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + (i % 100) / 1000, 0.1), 3); if (b.capital) b.population = b.population * 1.3; // increase capital population if (b.port) b.population = b.population * 1.3; // increase port population - b.population = rn(b.population * gauss(2,3,.6,20,3), 3); + b.population = rn(b.population * gauss(2, 3, 0.6, 20, 3), 3); }); } @@ -126,14 +145,19 @@ function regenerateStates() { } // burg local ids sorted by a bit randomized population: - const sorted = burgs.map((b, i) => [i, b.population * Math.random()]).sort((a, b) => b[1] - a[1]).map(b => b[0]); + const sorted = burgs + .map((b, i) => [i, b.population * Math.random()]) + .sort((a, b) => b[1] - a[1]) + .map(b => b[0]); const capitalsTree = d3.quadtree(); // turn all old capitals into towns - burgs.filter(b => b.capital).forEach(b => { - moveBurgToGroup(b.i, "towns"); - b.capital = 0; - }); + burgs + .filter(b => b.capital) + .forEach(b => { + moveBurgToGroup(b.i, "towns"); + b.capital = 0; + }); // remove emblems document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove()); @@ -145,7 +169,7 @@ function regenerateStates() { // if desired states number is 0 if (regionsInput.value == 0) { tip(`Cannot generate zero states. Please check the States Number option`, false, "warn"); - pack.states = pack.states.slice(0,1); // remove all except of neutrals + pack.states = pack.states.slice(0, 1); // remove all except of neutrals pack.states[0].diplomacy = []; // clear diplomacy pack.provinces = [0]; // remove all provinces pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data @@ -165,10 +189,12 @@ function regenerateStates() { pack.states = d3.range(count).map(i => { if (!i) return {i, name: neutral}; - let capital = null, x = 0, y = 0; + let capital = null, + x = 0, + y = 0; for (const i of sorted) { capital = burgs[i]; - x = capital.x, y = capital.y; + (x = capital.x), (y = capital.y); if (capitalsTree.find(x, y, spacing) === undefined) break; spacing = Math.max(spacing - 1, 1); } @@ -178,17 +204,17 @@ function regenerateStates() { moveBurgToGroup(capital.i, "cities"); const culture = capital.culture; - const basename = capital.name.length < 9 && capital.cell%5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); + const basename = capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); const name = Names.getState(basename, culture); const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]); const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" ? "Generic" : pack.cultures[culture].type; const expansionism = rn(Math.random() * powerInput.value + 1, 1); const cultureType = pack.cultures[culture].type; - const coa = COA.generate(capital.coa, .3, null, cultureType); + const coa = COA.generate(capital.coa, 0.3, null, cultureType); coa.shield = capital.coa.shield; - return {i, name, type, capital:capital.i, center:capital.cell, culture, expansionism, coa}; + return {i, name, type, capital: capital.i, center: capital.cell, culture, expansionism, coa}; }); BurgsAndStates.expandStates(); @@ -199,8 +225,10 @@ function regenerateStates() { BurgsAndStates.generateDiplomacy(); BurgsAndStates.defineStateForms(); BurgsAndStates.generateProvinces(true); - 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(); Military.generate(); if (layerIsOn("toggleEmblems")) drawEmblems(); // redrawEmblems @@ -224,43 +252,52 @@ function regenerateProvinces() { } function regenerateBurgs() { - const cells = pack.cells, states = pack.states, Lockedburgs = pack.burgs.filter(b =>b.lock); + const cells = pack.cells, + states = pack.states, + Lockedburgs = pack.burgs.filter(b => b.lock); rankCells(); cells.burg = new Uint16Array(cells.i.length); - const burgs = pack.burgs = [0]; // clear burgs array - states.filter(s => s.i).forEach(s => s.capital = 0); // clear state capitals - pack.provinces.filter(p => p.i).forEach(p => p.burg = 0); // clear province capitals + const burgs = (pack.burgs = [0]); // clear burgs array + states.filter(s => s.i).forEach(s => (s.capital = 0)); // clear state capitals + pack.provinces.filter(p => p.i).forEach(p => (p.burg = 0)); // clear province capitals const burgsTree = d3.quadtree(); const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes - const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** .8) + states.length : +manorsInput.value + states.length; - const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** .7 / 66); // base min distance between towns + const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) + states.length : +manorsInput.value + states.length; + const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** 0.7 / 66); // base min distance between towns //clear locked list since ids will change //burglock.selectAll("text").remove(); - for (let j=0; j < Lockedburgs.length; j++) { + for (let j = 0; j < Lockedburgs.length; j++) { const id = burgs.length; const oldBurg = Lockedburgs[j]; oldBurg.i = id; burgs.push(oldBurg); burgsTree.add([oldBurg.x, oldBurg.y]); cells.burg[oldBurg.cell] = id; - if (oldBurg.capital) {states[oldBurg.state].capital = id; states[oldBurg.state].center = oldBurg.cell;} + if (oldBurg.capital) { + states[oldBurg.state].capital = id; + states[oldBurg.state].center = oldBurg.cell; + } //burglock.append("text").attr("data-id", id); } - for (let i=0; i < sorted.length && burgs.length < burgsCount; i++) { + for (let i = 0; i < sorted.length && burgs.length < burgsCount; i++) { const id = burgs.length; const cell = sorted[i]; - const x = cells.p[cell][0], y = cells.p[cell][1]; + const x = cells.p[cell][0], + y = cells.p[cell][1]; - const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make the placement not uniform + const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg const state = cells.state[cell]; const capital = state && !states[state].capital; // if state doesn't have capital, make this burg a capital, no capital for neutral lands - if (capital) {states[state].capital = id; states[state].center = cell;} + if (capital) { + states[state].capital = id; + states[state].center = cell; + } const culture = cells.culture[cell]; const name = Names.getCulture(culture); @@ -270,16 +307,20 @@ function regenerateBurgs() { } // add a capital at former place for states without added capitals - states.filter(s => s.i && !s.removed && !s.capital).forEach(s => { - const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg - s.capital = burg; - s.center = pack.burgs[burg].cell; - pack.burgs[burg].capital = 1; - pack.burgs[burg].state = s.i; - moveBurgToGroup(burg, "cities"); - }); + states + .filter(s => s.i && !s.removed && !s.capital) + .forEach(s => { + const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg + s.capital = burg; + s.center = pack.burgs[burg].cell; + pack.burgs[burg].capital = 1; + pack.burgs[burg].state = s.i; + moveBurgToGroup(burg, "cities"); + }); - pack.features.forEach(f => {if (f.port) f.port = 0}); // reset features ports counter + pack.features.forEach(f => { + if (f.port) f.port = 0; + }); // reset features ports counter BurgsAndStates.specifyBurgs(); BurgsAndStates.defineBurgFeatures(); BurgsAndStates.drawBurgs(); @@ -313,10 +354,10 @@ function regenerateEmblems() { if (!burg.i || burg.removed) return; const state = pack.states[burg.state]; - let kinship = state ? .25 : 0; - if (burg.capital) kinship += .1; - else if (burg.port) kinship -= .1; - if (state && burg.culture !== state.culture) kinship -= .25; + let kinship = state ? 0.25 : 0; + if (burg.capital) kinship += 0.1; + else if (burg.port) kinship -= 0.1; + if (state && burg.culture !== state.culture) kinship -= 0.25; burg.coa = COA.generate(state ? state.coa : null, kinship, null, burg.type); burg.coa.shield = COA.getShield(burg.culture, state ? burg.state : 0); }); @@ -327,16 +368,16 @@ function regenerateEmblems() { let dominion = false; if (!province.burg) { - dominion = P(.2); - if (province.formName === "Colony") dominion = P(.95); else - if (province.formName === "Island") dominion = P(.6); else - if (province.formName === "Islands") dominion = P(.5); else - if (province.formName === "Territory") dominion = P(.4); else - if (province.formName === "Land") dominion = P(.3); + dominion = P(0.2); + if (province.formName === "Colony") dominion = P(0.95); + else if (province.formName === "Island") dominion = P(0.6); + else if (province.formName === "Islands") dominion = P(0.5); + else if (province.formName === "Territory") dominion = P(0.4); + else if (province.formName === "Land") dominion = P(0.3); } const nameByBurg = province.burg && province.name.slice(0, 3) === parent.name.slice(0, 3); - const kinship = dominion ? 0 : nameByBurg ? .8 : .4; + const kinship = dominion ? 0 : nameByBurg ? 0.8 : 0.4; const culture = pack.cells.culture[province.center]; const type = BurgsAndStates.getType(province.center, parent.port); province.coa = COA.generate(parent.coa, kinship, dominion, type); @@ -348,7 +389,8 @@ function regenerateEmblems() { function regenerateReligions() { Religions.generate(); - if (!layerIsOn("toggleReligions")) toggleReligions(); else drawReligions(); + if (!layerIsOn("toggleReligions")) toggleReligions(); + else drawReligions(); } function regenerateCultures() { @@ -356,7 +398,8 @@ function regenerateCultures() { Cultures.expand(); BurgsAndStates.updateCultures(); Religions.updateCultures(); - if (!layerIsOn("toggleCultures")) toggleCultures(); else drawCultures(); + if (!layerIsOn("toggleCultures")) toggleCultures(); + else drawCultures(); refreshAllEditors(); } @@ -373,15 +416,18 @@ function regenerateIce() { } function regenerateMarkers(event) { - if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfMarkers(v)); - else addNumberOfMarkers(gauss(1, .5, .3, 5, 2)); + if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default: 1, step: 0.01, min: 0, max: 100}, v => addNumberOfMarkers(v)); + else addNumberOfMarkers(gauss(1, 0.5, 0.3, 5, 2)); function addNumberOfMarkers(number) { // remove existing markers and assigned notes - markers.selectAll("use").each(function() { - const index = notes.findIndex(n => n.id === this.id); - if (index != -1) notes.splice(index, 1); - }).remove(); + markers + .selectAll("use") + .each(function () { + const index = notes.findIndex(n => n.id === this.id); + if (index != -1) notes.splice(index, 1); + }) + .remove(); addMarkers(number); if (!layerIsOn("toggleMarkers")) toggleMarkers(); @@ -389,8 +435,8 @@ function regenerateMarkers(event) { } function regenerateZones(event) { - if (isCtrlClick(event)) prompt("Please provide zones number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfZones(v)); - else addNumberOfZones(gauss(1, .5, .6, 5, 2)); + if (isCtrlClick(event)) prompt("Please provide zones number multiplier", {default: 1, step: 0.01, min: 0, max: 100}, v => addNumberOfZones(v)); + else addNumberOfZones(gauss(1, 0.5, 0.6, 5, 2)); function addNumberOfZones(number) { zones.selectAll("g").remove(); // remove existing zones @@ -408,10 +454,13 @@ function unpressClickToAddButton() { function toggleAddLabel() { const pressed = document.getElementById("addLabel").classList.contains("pressed"); - if (pressed) {unpressClickToAddButton(); return;} + if (pressed) { + unpressClickToAddButton(); + return; + } addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed")); - addLabel.classList.add('pressed'); + addLabel.classList.add("pressed"); closeDialogs(".stable"); viewbox.style("cursor", "crosshair").on("click", addLabelOnClick); tip("Click on map to place label. Hold Shift to add multiple", true); @@ -428,10 +477,7 @@ function addLabelOnClick() { const id = getNextId("label"); let group = labels.select("#addedLabels"); - if (!group.size()) group = labels.append("g").attr("id", "addedLabels") - .attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a") - .attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC") - .attr("font-size", 18).attr("data-size", 18).attr("filter", null); + if (!group.size()) group = labels.append("g").attr("id", "addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null); const example = group.append("text").attr("x", 0).attr("x", 0).text(name); const width = example.node().getBBox().width; @@ -439,13 +485,22 @@ function addLabelOnClick() { example.remove(); group.classed("hidden", false); - group.append("text").attr("id", id) - .append("textPath").attr("xlink:href", "#textPath_"+id).attr("startOffset", "50%").attr("font-size", "100%") - .append("tspan").attr("x", x).text(name); + group + .append("text") + .attr("id", id) + .append("textPath") + .attr("xlink:href", "#textPath_" + id) + .attr("startOffset", "50%") + .attr("font-size", "100%") + .append("tspan") + .attr("x", x) + .text(name); - defs.select("#textPaths") - .append("path").attr("id", "textPath_"+id) - .attr("d", `M${point[0]-width},${point[1]} h${width*2}`); + defs + .select("#textPaths") + .append("path") + .attr("id", "textPath_" + id) + .attr("d", `M${point[0] - width},${point[1]} h${width * 2}`); if (d3.event.shiftKey === false) unpressClickToAddButton(); } @@ -466,7 +521,7 @@ function toggleAddRiver() { } addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed")); - addRiver.classList.add('pressed'); + addRiver.classList.add("pressed"); document.getElementById("addNewRiver").classList.add("pressed"); closeDialogs(".stable"); viewbox.style("cursor", "crosshair").on("click", addRiverOnClick); @@ -486,22 +541,27 @@ function addRiverOnClick() { // height with added t value to make map less depressed const h = Array.from(cells.h) - .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100) - .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000); - Rivers.resolveDepressions(h); + .map((h, i) => (h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100)) + .map((h, i) => (h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000)); + Rivers.resolveDepressions(h, 200); while (i) { cells.r[i] = river; - const x = cells.p[i][0], y = cells.p[i][1]; - dataRiver.push({x, y, cell:i}); + const x = cells.p[i][0], + y = cells.p[i][1]; + dataRiver.push({x, y, cell: i}); const min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell - if (h[i] <= h[min]) {tip(`Cell ${i} is depressed, river cannot flow further`, false, "error"); return;} - const tx = cells.p[min][0], ty = cells.p[min][1]; + if (h[i] <= h[min]) { + tip(`Cell ${i} is depressed, river cannot flow further`, false, "error"); + return; + } + const tx = cells.p[min][0], + ty = cells.p[min][1]; if (h[min] < 20) { // pour to water body - dataRiver.push({x: tx, y: ty, cell:i}); + dataRiver.push({x: tx, y: ty, cell: i}); break; } @@ -526,19 +586,22 @@ function addRiverOnClick() { } // extend old river - rivers.select("#river"+r).remove(); - cells.i.filter(i => cells.r[i] === river).forEach(i => cells.r[i] = r); - riverCells.forEach(i => cells.r[i] = 0); + rivers.select("#river" + r).remove(); + cells.i.filter(i => cells.r[i] === river).forEach(i => (cells.r[i] = r)); + riverCells.forEach(i => (cells.r[i] = 0)); river = r; cells.fl[min] = cells.fl[i] + grid.cells.prec[cells.g[min]]; i = min; } - const points = Rivers.addMeandering(dataRiver, 1, .5); - const widthFactor = rn(.8 + Math.random() * .4, 1); // river width modifier [.8, 1.2] - const sourceWidth = .1; + const points = Rivers.addMeandering(dataRiver, 1, 0.5); + const widthFactor = rn(0.8 + Math.random() * 0.4, 1); // river width modifier [.8, 1.2] + const sourceWidth = 0.1; const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth); - rivers.append("path").attr("d", path).attr("id", "river"+river); + rivers + .append("path") + .attr("d", path) + .attr("id", "river" + river); // add new river to data or change extended river attributes const r = pack.rivers.find(r => r.i === river); @@ -555,10 +618,10 @@ function addRiverOnClick() { const source = dataRiver[0].cell; const width = rn(offset ** 2, 2); // mounth width in km const name = Rivers.getName(mouth); - const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)]; - const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River"; + const smallLength = pack.rivers.map(r => r.length || 0).sort((a, b) => a - b)[Math.ceil(pack.rivers.length * 0.15)]; + const type = length < smallLength ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : "River"; - pack.rivers.push({i:river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type}); + pack.rivers.push({i: river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type}); } if (d3.event.shiftKey === false) { @@ -570,10 +633,13 @@ function addRiverOnClick() { function toggleAddRoute() { const pressed = document.getElementById("addRoute").classList.contains("pressed"); - if (pressed) {unpressClickToAddButton(); return;} + if (pressed) { + unpressClickToAddButton(); + return; + } addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed")); - addRoute.classList.add('pressed'); + addRoute.classList.add("pressed"); closeDialogs(".stable"); viewbox.style("cursor", "crosshair").on("click", addRouteOnClick); tip("Click on map to add a first control point", true); @@ -590,10 +656,13 @@ function addRouteOnClick() { function toggleAddMarker() { const pressed = document.getElementById("addMarker").classList.contains("pressed"); - if (pressed) {unpressClickToAddButton(); return;} + if (pressed) { + unpressClickToAddButton(); + return; + } addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed")); - addMarker.classList.add('pressed'); + addMarker.classList.add("pressed"); closeDialogs(".stable"); viewbox.style("cursor", "crosshair").on("click", addMarkerOnClick); tip("Click on map to add a marker. Hold Shift to add multiple", true); @@ -602,27 +671,44 @@ function toggleAddMarker() { function addMarkerOnClick() { const point = d3.mouse(this); - const x = rn(point[0], 2), y = rn(point[1], 2); + const x = rn(point[0], 2), + y = rn(point[1], 2); const id = getNextId("markerElement"); const selected = markerSelectGroup.value; - const valid = selected && d3.select("#defs-markers").select("#"+selected).size(); - const symbol = valid ? "#"+selected : "#marker0"; + const valid = + selected && + d3 + .select("#defs-markers") + .select("#" + selected) + .size(); + const symbol = valid ? "#" + selected : "#marker0"; const added = markers.select("[data-id='" + symbol + "']").size(); let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr("data-size") : 1; if (isNaN(desired)) desired = 1; const size = desired * 5 + 25 / scale; - markers.append("use").attr("id", id).attr("xlink:href", symbol).attr("data-id", symbol) - .attr("data-x", x).attr("data-y", y).attr("x", x - size / 2).attr("y", y - size) - .attr("data-size", desired).attr("width", size).attr("height", size); + markers + .append("use") + .attr("id", id) + .attr("xlink:href", symbol) + .attr("data-id", symbol) + .attr("data-x", x) + .attr("data-y", y) + .attr("x", x - size / 2) + .attr("y", y - size) + .attr("data-size", desired) + .attr("width", size) + .attr("height", size); if (d3.event.shiftKey === false) unpressClickToAddButton(); } function viewCellDetails() { $("#cellInfo").dialog({ - resizable: false, width: "22em", title: "Cell Details", + resizable: false, + width: "22em", + title: "Cell Details", position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); } From 1cd9069b1549927e1454f98da0b7040c4575dbcc Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 6 Jun 2021 01:30:29 +0300 Subject: [PATCH 04/34] get back to using vertices --- modules/lakes.js | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/modules/lakes.js b/modules/lakes.js index f4bc8c6f..0c91ca67 100644 --- a/modules/lakes.js +++ b/modules/lakes.js @@ -32,20 +32,9 @@ // get array of land cells aroound lake const getShoreline = function (lake) { - const queue = [lake.firstCell]; - const used = [queue[0]]; - const landCellsAround = []; - while (queue.length) { - const q = queue.pop(); - for (const c of pack.cells.c[q]) { - if (used[c]) continue; - used[c] = true; - if (pack.cells.f[c] === lake.i) queue.push(c); - if (pack.cells.h[c] >= 20) landCellsAround.push(c); - } - } - - lake.shoreline = landCellsAround; + const uniqueCells = new Set(); + lake.vertices.forEach(v => pack.vertices.c[v].forEach(c => pack.cells.h[c] >= 20 && uniqueCells.add(c))); + lake.shoreline = [...uniqueCells]; }; const cleanupLakeData = function () { From 6cca0b7f3856d9564d8ae108a47eceec372f75e5 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 6 Jun 2021 17:27:24 +0300 Subject: [PATCH 05/34] start depression fill with lowest cells --- main.js | 1 + modules/river-generator.js | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/main.js b/main.js index 4cba5ac2..b7a6f37a 100644 --- a/main.js +++ b/main.js @@ -599,6 +599,7 @@ function generate() { markFeatures(); markupGridOcean(); openNearSeaLakes(); + OceanLayers(); defineMapSize(); calculateMapCoordinates(); diff --git a/modules/river-generator.js b/modules/river-generator.js index 28e29dd9..8dcda0f6 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -43,6 +43,10 @@ const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]); const lakeOutCells = Lakes.setClimateData(h); + // const flow = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); + // flow[i] = min; + // debug.append("path").attr("class", "arrow").attr("d", `M${cells.p[i][0]},${cells.p[i][1]}L${cells.p[min][0]},${cells.p[min][1]}`); + land.forEach(function (i) { cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation const [x, y] = p[i]; @@ -194,12 +198,13 @@ const lakes = features.filter(f => f.type === "lake"); const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells - land.sort((a, b) => h[b] - h[a]); // highest cells go first + land.sort((a, b) => h[a] - h[b]); // lowest cells go first const progress = []; let depressions = Infinity; let prevDepressions = null; - for (let iteration = 0; depressions && iteration < maxIterations; iteration++) { + let iteration = 0; + for (; depressions && iteration < maxIterations; iteration++) { if (progress.length > 5 && d3.sum(progress) > 0) { // bad progress, abort and set heights back h = alterHeights(); @@ -239,11 +244,10 @@ prevDepressions = depressions; } + console.log(iteration); + if (!depressions) return; WARN && console.warn(`Unresolved depressions: ${depressions}. Edit heightmap to fix`); - //const flow = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); - //flow[i] = min; - //debug.append("path").attr("class", "arrow").attr("d", `M${cells.p[i][0]},${cells.p[i][1]}L${cells.p[min][0]},${cells.p[min][1]}`); }; // add more river points on 1/3 and 2/3 of length From c8c1c2490910267d2857afdc8c37b6521d0bc251 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 6 Jun 2021 20:25:36 +0300 Subject: [PATCH 06/34] addLakesInDeepDepressions --- index.html | 6 ++-- main.js | 65 ++++++++++++++++++++++++++++++++-- modules/ocean-layers.js | 50 +++++++++++++++----------- modules/river-generator.js | 4 +-- modules/ui/heightmap-editor.js | 27 ++++++++------ 5 files changed, 113 insertions(+), 39 deletions(-) diff --git a/index.html b/index.html index eb363635..907d690a 100644 --- a/index.html +++ b/index.html @@ -1358,9 +1358,9 @@ -
- - +
+ +
diff --git a/main.js b/main.js index b7a6f37a..347670c4 100644 --- a/main.js +++ b/main.js @@ -598,6 +598,7 @@ function generate() { HeightmapGenerator.generate(); markFeatures(); markupGridOcean(); + addLakesInDeepDepressions(); openNearSeaLakes(); OceanLayers(); @@ -770,11 +771,71 @@ function markup(cells, start, increment, limit) { } } +function addLakesInDeepDepressions() { + console.time("addLakesInDeepDepressions"); + const {cells, features, points} = grid; + const {c, h, b} = cells; + const ELEVATION_LIMIT = 10; + + for (const i of cells.i) { + if (b[i] || h[i] < 20) continue; + + const minHeight = d3.min(c[i].map(c => h[c])); + if (h[i] > minHeight) continue; + + let deep = true; + const treshold = h[i] + ELEVATION_LIMIT; + const queue = [i]; + const checked = []; + checked[i] = true; + + // check if elevated cell can potentially pour to water + while (deep && queue.length) { + const q = queue.pop(); + + for (const n of c[q]) { + if (checked[n]) continue; + if (h[n] < 20) { + deep = false; + break; + } + if (h[n] >= treshold) continue; + + checked[n] = true; + queue.push(n); + } + } + + // if not, add a lake + if (deep) { + debug.append("circle").attr("cx", points[i][0]).attr("cy", points[i][1]).attr("r", 1).attr("fill", "red"); + const lakeCells = [i].concat(c[i].filter(n => h[n] === h[i])); + addLake(lakeCells); + } + } + + function addLake(lakeCells) { + const f = features.length; + + lakeCells.forEach(i => { + cells.h[i] = 19; + cells.t[i] = -1; + cells.f[i] = f; + c[i].forEach(n => !lakeCells.includes(n) && (cells.t[c] = 1)); + }); + + features.push({i: f, land: false, border: false, type: "lake"}); + } + + console.timeEnd("addLakesInDeepDepressions"); +} + // near sea lakes usually get a lot of water inflow, most of them should brake treshold and flow out to sea (see Ancylus Lake) function openNearSeaLakes() { if (templateInput.value === "Atoll") return; // no need for Atolls - const cells = grid.cells, - features = grid.features; + + const cells = grid.cells; + const features = grid.features; if (!features.find(f => f.type === "lake")) return; // no lakes TIME && console.time("openLakes"); const LIMIT = 22; // max height that can be breached by water diff --git a/modules/ocean-layers.js b/modules/ocean-layers.js index 2194f7b0..16a8ce88 100644 --- a/modules/ocean-layers.js +++ b/modules/ocean-layers.js @@ -1,8 +1,7 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.OceanLayers = factory()); -}(this, (function () { 'use strict'; + typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.OceanLayers = factory()); +})(this, function () { + "use strict"; let cells, vertices, pointsN, used; @@ -12,11 +11,11 @@ TIME && console.time("drawOceanLayers"); lineGen.curve(d3.curveBasisClosed); - cells = grid.cells, pointsN = grid.cells.i.length, vertices = grid.vertices; + (cells = grid.cells), (pointsN = grid.cells.i.length), (vertices = grid.vertices); const limits = outline === "random" ? randomizeOutline() : outline.split(",").map(s => +s); const chains = []; - const opacity = rn(.4 / limits.length, 2); + const opacity = rn(0.4 / limits.length, 2); used = new Uint8Array(pointsN); // to detect already passed cells for (const i of cells.i) { @@ -29,10 +28,13 @@ const chain = connectVertices(start, t); // vertices chain to form a path if (chain.length < 4) continue; const relax = 1 + t * -2; // select only n-th point - const relaxed = chain.filter((v, i) => !(i%relax) || vertices.c[v].some(c => c >= pointsN)); + const relaxed = chain.filter((v, i) => !(i % relax) || vertices.c[v].some(c => c >= pointsN)); if (relaxed.length < 4) continue; - const points = clipPoly(relaxed.map(v => vertices.p[v]), 1); - chains.push([t, points]); + const points = clipPoly( + relaxed.map(v => vertices.p[v]), + 1 + ); + chains.push([t, points]); } for (const t of limits) { @@ -48,14 +50,18 @@ } TIME && console.timeEnd("drawOceanLayers"); - } + }; function randomizeOutline() { const limits = []; - let odd = .2 + let odd = 0.2; for (let l = -9; l < 0; l++) { - if (P(odd)) {odd = .2; limits.push(l);} - else {odd *= 2;} + if (P(odd)) { + odd = 0.2; + limits.push(l); + } else { + odd *= 2; + } } return limits; } @@ -63,24 +69,26 @@ // 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 < 10000; i++) { + for (let i = 0, current = start; i === 0 || (current !== start && i < 10000); 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.t[c] === t).forEach(c => used[c] = 1); + c.filter(c => cells.t[c] === t).forEach(c => (used[c] = 1)); const v = vertices.v[current]; // neighboring vertices - const c0 = !cells.t[c[0]] || cells.t[c[0]] === t-1; - const c1 = !cells.t[c[1]] || cells.t[c[1]] === t-1; - const c2 = !cells.t[c[2]] || cells.t[c[2]] === t-1; + const c0 = !cells.t[c[0]] || cells.t[c[0]] === t - 1; + const c1 = !cells.t[c[1]] || cells.t[c[1]] === t - 1; + const c2 = !cells.t[c[2]] || cells.t[c[2]] === t - 1; if (v[0] !== undefined && v[0] !== prev && c0 !== c1) current = v[0]; else if (v[1] !== undefined && v[1] !== prev && c1 !== c2) current = v[1]; else if (v[2] !== undefined && 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(chain[0]); // push first vertex as the last one return chain; } return OceanLayers; - -}))); \ No newline at end of file +}); diff --git a/modules/river-generator.js b/modules/river-generator.js index 8dcda0f6..72dfac1e 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -3,7 +3,7 @@ })(this, function () { "use strict"; - const generate = function (changeHeights = true) { + const generate = function (allowErosion = true) { TIME && console.time("generateRivers"); Math.random = aleaPRNG(seed); const cells = pack.cells, @@ -23,7 +23,7 @@ defineRivers(); Lakes.cleanupLakeData(); - if (changeHeights) cells.h = Uint8Array.from(h); // apply changed heights as basic one + if (allowErosion) cells.h = Uint8Array.from(h); // apply changed heights as basic one TIME && console.timeEnd("generateRivers"); diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 0b7ef617..935660ca 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -77,7 +77,7 @@ function editHeightmap() { convertImage.style.display = type === "erase" ? "inline-block" : "none"; // hide erosion checkbox if mode is Keep - changeHeightsBox.style.display = type === "keep" ? "none" : "inline-block"; + allowErosionBox.style.display = type === "keep" ? "none" : "inline-block"; // show finalize button if (!sessionStorage.getItem("noExitButtonAnimation")) { @@ -182,19 +182,22 @@ function editHeightmap() { INFO && console.group("Edit Heightmap"); TIME && console.time("regenerateErasedData"); - const change = changeHeights.checked; + const erosionAllowed = allowErosion.checked; markFeatures(); markupGridOcean(); - if (change) openNearSeaLakes(); + if (erosionAllowed) { + addLakesInDeepDepressions(); + openNearSeaLakes(); + } OceanLayers(); calculateTemperatures(); generatePrecipitation(); reGraph(); drawCoastline(); - Rivers.generate(change); + Rivers.generate(erosionAllowed); - if (!change) { + if (!erosionAllowed) { for (const i of pack.cells.i) { const g = pack.cells.g[i]; if (pack.cells.h[i] !== grid.cells.h[g] && pack.cells.h[i] >= 20 === grid.cells.h[g] >= 20) pack.cells.h[i] = grid.cells.h[g]; @@ -236,6 +239,7 @@ function editHeightmap() { function restoreRiskedData() { INFO && console.group("Edit Heightmap"); TIME && console.time("restoreRiskedData"); + const erosionAllowed = allowErosion.checked; // assign pack data to grid cells const l = grid.cells.i.length; @@ -250,7 +254,7 @@ function editHeightmap() { const culture = new Uint16Array(l); const religion = new Uint16Array(l); - // rivers data, stored only if changeHeights is unchecked + // rivers data, stored only if allowErosion is unchecked const fl = new Uint16Array(l); const r = new Uint16Array(l); const conf = new Uint8Array(l); @@ -268,7 +272,7 @@ function editHeightmap() { burg[g] = pack.cells.burg[i]; religion[g] = pack.cells.religion[i]; - if (!changeHeights.checked) { + if (!erosionAllowed) { fl[g] = pack.cells.fl[i]; r[g] = pack.cells.r[i]; conf[g] = pack.cells.conf[i]; @@ -301,13 +305,14 @@ function editHeightmap() { markFeatures(); markupGridOcean(); + if (erosionAllowed) addLakesInDeepDepressions(); OceanLayers(); calculateTemperatures(); generatePrecipitation(); reGraph(); drawCoastline(); - if (changeHeights.checked) Rivers.generate(changeHeights.checked); + if (erosionAllowed) Rivers.generate(true); // assign saved pack data from grid back to pack const n = pack.cells.i.length; @@ -322,7 +327,7 @@ function editHeightmap() { pack.cells.religion = new Uint16Array(n); pack.cells.biome = new Uint8Array(n); - if (!changeHeights.checked) { + if (!erosionAllowed) { pack.cells.r = new Uint16Array(n); pack.cells.conf = new Uint8Array(n); pack.cells.fl = new Uint16Array(n); @@ -336,7 +341,7 @@ function editHeightmap() { pack.cells.biome[i] = land && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], pack.cells.h[i]); // rivers data - if (!changeHeights.checked) { + if (!erosionAllowed) { pack.cells.r[i] = r[g]; pack.cells.conf[i] = conf[g]; pack.cells.fl[i] = fl[g]; @@ -400,7 +405,7 @@ function editHeightmap() { drawStates(); drawBorders(); - if (changeHeights.checked) { + if (erosion) { Rivers.specify(); Lakes.generateName(); } From 05e6cc388e0de83e92fbd50ce26905945885c7ed Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 6 Jun 2021 20:50:00 +0300 Subject: [PATCH 07/34] change lakes depth calculation --- modules/ui/general.js | 504 +++++++++++++++++++++++++++--------------- 1 file changed, 327 insertions(+), 177 deletions(-) diff --git a/modules/ui/general.js b/modules/ui/general.js index 04d64459..083643a1 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -2,7 +2,7 @@ "use strict"; // fit full-screen map if window is resized -$(window).resize(function(e) { +$(window).resize(function (e) { if (localStorage.getItem("mapWidth") && localStorage.getItem("mapHeight")) return; mapWidthInput.value = window.innerWidth; mapHeightInput.value = window.innerHeight; @@ -28,12 +28,12 @@ document.getElementById("exitCustomization").addEventListener("mousemove", showD function tip(tip = "Tip is undefined", main, type, time) { tooltip.innerHTML = tip; tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)"; - if (type === "error") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)"; else - if (type === "warn") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)"; else - if (type === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)"; + if (type === "error") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)"; + else if (type === "warn") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)"; + else if (type === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)"; if (main) tooltip.dataset.main = tip; // set main tip - if (time) setTimeout(() => tooltip.dataset.main = "", time); // clear main in some time + if (time) setTimeout(() => (tooltip.dataset.main = ""), time); // clear main in some time } function showMainTip() { @@ -62,7 +62,8 @@ function mouseMove() { if (i === undefined) return; showNotes(d3.event, i); const g = findGridCell(point[0], point[1]); // grid cell id - if (tooltip.dataset.main) showMainTip(); else showMapTooltip(point, d3.event, i, g); + if (tooltip.dataset.main) showMainTip(); + else showMapTooltip(point, d3.event, i, g); if (cellInfo.offsetParent) updateCellInfo(point, i, g); } @@ -70,8 +71,8 @@ function mouseMove() { function showNotes(e, i) { if (notesEditor.offsetParent) return; let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id; - if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; else - if (e.target.parentNode.parentNode.id === "burgIcons") id = "burg" + e.target.dataset.id; + if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; + else if (e.target.parentNode.parentNode.id === "burgIcons") id = "burg" + e.target.dataset.id; const note = notes.find(note => note.id === id); if (note !== undefined && note.legend !== "") { @@ -102,9 +103,7 @@ function showMapTooltip(point, e, i, g) { if (group === "emblems" && e.target.tagName === "use") { const parent = e.target.parentNode; - const [g, type] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : - parent.id === "provinceEmblems" ? [pack.provinces, "province"] : - [pack.states, "state"]; + const [g, type] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"]; const i = +e.target.dataset.i; if (event.shiftKey) highlightEmblemElement(type, g[i]); @@ -124,8 +123,14 @@ function showMapTooltip(point, e, i, g) { if (riversOverview.offsetParent) highlightEditorLine(riversOverview, river, 5000); return; } - if (group === "routes") {tip("Click to edit the Route"); return;} - if (group === "terrain") {tip("Click to edit the Relief Icon"); return;} + if (group === "routes") { + tip("Click to edit the Route"); + return; + } + if (group === "terrain") { + tip("Click to edit the Relief Icon"); + return; + } if (subgroup === "burgLabels" || subgroup === "burgIcons") { const burg = +path[path.length - 10].dataset.id; const b = pack.burgs[burg]; @@ -134,52 +139,87 @@ function showMapTooltip(point, e, i, g) { if (burgsOverview.offsetParent) highlightEditorLine(burgsOverview, burg, 5000); return; } - if (group === "labels") {tip("Click to edit the Label"); return;} - if (group === "markers") {tip("Click to edit the Marker"); return;} + if (group === "labels") { + tip("Click to edit the Label"); + return; + } + if (group === "markers") { + tip("Click to edit the Marker"); + return; + } if (group === "ruler") { const tag = e.target.tagName; const className = e.target.getAttribute("class"); - if (tag === "circle" && className === "edge") {tip("Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point"); return;} - if (tag === "circle" && className === "control") {tip("Drag to adjust. Hold Shifta and drag to keep axial direction. Click to remove the point"); return;} - if (tag === "circle") {tip("Drag to adjust the measurer"); return;} - if (tag === "polyline") {tip("Click on drag to add a control point"); return;} - if (tag === "path") {tip("Drag to move the measurer"); return;} - if (tag === "text") {tip("Drag to move, click to remove the measurer"); return;} + if (tag === "circle" && className === "edge") { + tip("Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point"); + return; + } + if (tag === "circle" && className === "control") { + tip("Drag to adjust. Hold Shifta and drag to keep axial direction. Click to remove the point"); + return; + } + if (tag === "circle") { + tip("Drag to adjust the measurer"); + return; + } + if (tag === "polyline") { + tip("Click on drag to add a control point"); + return; + } + if (tag === "path") { + tip("Drag to move the measurer"); + return; + } + if (tag === "text") { + tip("Drag to move, click to remove the measurer"); + return; + } + } + if (subgroup === "burgIcons") { + tip("Click to edit the Burg"); + return; + } + if (subgroup === "burgLabels") { + tip("Click to edit the Burg"); + return; } - if (subgroup === "burgIcons") {tip("Click to edit the Burg"); return;} - if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;} if (group === "lakes" && !land) { const lakeId = +e.target.dataset.f; const name = pack.features[lakeId]?.name; const fullName = subgroup === "freshwater" ? name : name + " " + subgroup; - tip(`${fullName} lake. Click to edit`); return; + tip(`${fullName} lake. Click to edit`); + return; + } + if (group === "coastline") { + tip("Click to edit the coastline"); + return; } - if (group === "coastline") {tip("Click to edit the coastline"); return;} if (group === "zones") { - const zone = path[path.length-8]; + const zone = path[path.length - 8]; tip(zone.dataset.description); if (zonesEditor.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000); return; } - if (group === "ice") {tip("Click to edit the Ice"); return;} + if (group === "ice") { + tip("Click to edit the Ice"); + return; + } // covering elements - if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else - if (layerIsOn("togglePopulation")) tip(getPopulationTip(i)); else - if (layerIsOn("toggleTemp")) tip("Temperature: " + convertTemperature(grid.cells.temp[g])); else - if (layerIsOn("toggleBiomes") && pack.cells.biome[i]) { - const biome = pack.cells.biome[i] + if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: " + getFriendlyPrecipitation(i)); + else if (layerIsOn("togglePopulation")) tip(getPopulationTip(i)); + else if (layerIsOn("toggleTemp")) tip("Temperature: " + convertTemperature(grid.cells.temp[g])); + else if (layerIsOn("toggleBiomes") && pack.cells.biome[i]) { + const biome = pack.cells.biome[i]; tip("Biome: " + biomesData.name[biome]); if (biomesEditor.offsetParent) highlightEditorLine(biomesEditor, biome); - } else - if (layerIsOn("toggleReligions") && pack.cells.religion[i]) { + } else if (layerIsOn("toggleReligions") && pack.cells.religion[i]) { const religion = pack.cells.religion[i]; const r = pack.religions[religion]; const type = r.type === "Cult" || r.type == "Heresy" ? r.type : r.type + " religion"; tip(type + ": " + r.name); if (religionsEditor.offsetParent) highlightEditorLine(religionsEditor, religion); - } else - if (pack.cells.state[i] && (layerIsOn("toggleProvinces") || layerIsOn("toggleStates"))) { + } else if (pack.cells.state[i] && (layerIsOn("toggleProvinces") || layerIsOn("toggleStates"))) { const state = pack.cells.state[i]; const stateName = pack.states[state].fullName; const province = pack.cells.province[i]; @@ -189,27 +229,28 @@ function showMapTooltip(point, e, i, g) { if (diplomacyEditor.offsetParent) highlightEditorLine(diplomacyEditor, state); if (militaryOverview.offsetParent) highlightEditorLine(militaryOverview, state); if (provincesEditor.offsetParent) highlightEditorLine(provincesEditor, province); - } else - if (layerIsOn("toggleCultures") && pack.cells.culture[i]) { + } else if (layerIsOn("toggleCultures") && pack.cells.culture[i]) { const culture = pack.cells.culture[i]; tip("Culture: " + pack.cultures[culture].name); if (culturesEditor.offsetParent) highlightEditorLine(culturesEditor, culture); - } else - if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point)); + } else if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point)); } function highlightEditorLine(editor, id, timeout = 15000) { Array.from(editor.getElementsByClassName("states hovered")).forEach(el => el.classList.remove("hovered")); // clear all hovered const hovered = Array.from(editor.querySelectorAll("div")).find(el => el.dataset.id == id); if (hovered) hovered.classList.add("hovered"); // add hovered class - if (timeout) setTimeout(() => {hovered && hovered.classList.remove("hovered")}, timeout); + if (timeout) + setTimeout(() => { + hovered && hovered.classList.remove("hovered"); + }, timeout); } // get cell info on mouse move function updateCellInfo(point, i, g) { const cells = pack.cells; - const x = infoX.innerHTML = rn(point[0]); - const y = infoY.innerHTML = rn(point[1]); + const x = (infoX.innerHTML = rn(point[0])); + const y = (infoY.innerHTML = rn(point[1])); const f = cells.f[i]; infoLat.innerHTML = toDMS(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, "lat"); infoLon.innerHTML = toDMS(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, "lon"); @@ -222,7 +263,7 @@ function updateCellInfo(point, i, g) { infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]); infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a"; infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no"; - infoState.innerHTML = cells.h[i] >= 20 ? cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : "neutral lands (0)" : "no"; + infoState.innerHTML = cells.h[i] >= 20 ? (cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : "neutral lands (0)") : "no"; infoProvince.innerHTML = cells.province[i] ? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})` : "no"; infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : "no"; infoReligion.innerHTML = cells.religion[i] ? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})` : "no"; @@ -238,7 +279,7 @@ function toDMS(coord, c) { const minutesNotTruncated = (Math.abs(coord) - degrees) * 60; const minutes = Math.floor(minutesNotTruncated); const seconds = Math.floor((minutesNotTruncated - minutes) * 60); - const cardinal = c === "lat" ? coord >= 0 ? "N" : "S" : coord >= 0 ? "E" : "W"; + const cardinal = c === "lat" ? (coord >= 0 ? "N" : "S") : coord >= 0 ? "E" : "W"; return degrees + "° " + minutes + "′ " + seconds + "″ " + cardinal; } @@ -252,9 +293,15 @@ function getElevation(f, h) { // get water depth function getDepth(f, h, p) { if (f.land) return "0 " + heightUnit.value; // land: 0 - if (!f.border) return getHeight(h, "abs"); // lake: pack abs height + + // lake: difference between surface and bottom const gridH = grid.cells.h[findGridCell(p[0], p[1])]; - return getHeight(gridH, "abs"); // ocean: grig height + if (f.type === "lake") { + const depth = gridH === 19 ? f.height / 2 : gridH; + return getHeight(depth, "abs"); + } + + return getHeight(gridH, "abs"); // ocean: grid height } // get user-friendly (real-world) height value from map data @@ -268,12 +315,13 @@ function getFriendlyHeight(p) { function getHeight(h, abs) { const unit = heightUnit.value; let unitRatio = 3.281; // default calculations are in feet - if (unit === "m") unitRatio = 1; // if meter + if (unit === "m") unitRatio = 1; + // if meter else if (unit === "f") unitRatio = 0.5468; // if fathom let height = -990; if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value); - else if (h < 20 && h > 0) height = (h - 20) / h * 50; + else if (h < 20 && h > 0) height = ((h - 20) / h) * 50; if (abs) height = Math.abs(height); return rn(height * unitRatio) + " " + unit; @@ -299,49 +347,67 @@ function getCellPopulation(i) { // get user-friendly (real-world) population value from map data function getFriendlyPopulation(i) { const [rural, urban] = getCellPopulation(i); - return `${si(rural+urban)} (${si(rural)} rural, urban ${si(urban)})`; + return `${si(rural + urban)} (${si(rural)} rural, urban ${si(urban)})`; } function getPopulationTip(i) { const [rural, urban] = getCellPopulation(i); - return `Cell population: ${si(rural+urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`; + return `Cell population: ${si(rural + urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`; } function highlightEmblemElement(type, el) { - const i = el.i, cells = pack.cells; - const animation = d3.transition().duration(1000).ease(d3.easeSinIn); + const i = el.i, + cells = pack.cells; + const animation = d3.transition().duration(1000).ease(d3.easeSinIn); - if (type === "burg") { - const {x, y} = el; - debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0) - .attr("fill", "none").attr("stroke", "#d0240f").attr("stroke-width", 1).attr("opacity", 1) - .transition(animation).attr("r", 20).attr("opacity", .1).attr("stroke-width", 0).remove(); - return; - } + if (type === "burg") { + const {x, y} = el; + debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0).attr("fill", "none").attr("stroke", "#d0240f").attr("stroke-width", 1).attr("opacity", 1).transition(animation).attr("r", 20).attr("opacity", 0.1).attr("stroke-width", 0).remove(); + return; + } - const [x, y] = el.pole || pack.cells.p[el.center]; - const obj = type === "state" ? cells.state : cells.province; - const borderCells = cells.i.filter(id => obj[id] === i && cells.c[id].some(n => obj[n] !== i)); - const data = Array.from(borderCells).filter((c, i) => !(i%2)).map(i => cells.p[i]).map(i => [i[0], i[1], Math.hypot(i[0]-x, i[1]-y)]); + const [x, y] = el.pole || pack.cells.p[el.center]; + const obj = type === "state" ? cells.state : cells.province; + const borderCells = cells.i.filter(id => obj[id] === i && cells.c[id].some(n => obj[n] !== i)); + const data = Array.from(borderCells) + .filter((c, i) => !(i % 2)) + .map(i => cells.p[i]) + .map(i => [i[0], i[1], Math.hypot(i[0] - x, i[1] - y)]); - debug.selectAll("line").data(data).enter().append("line") - .attr("x1", x).attr("y1", y).attr("x2", d => d[0]).attr("y2", d => d[1]) - .attr("stroke", "#d0240f").attr("stroke-width", .5).attr("opacity", .2) - .attr("stroke-dashoffset", d => d[2]).attr("stroke-dasharray", d => d[2]) - .transition(animation).attr("stroke-dashoffset", 0).attr("opacity", 1) - .transition(animation).delay(1000).attr("stroke-dashoffset", d => d[2]).attr("opacity", 0).remove(); + debug + .selectAll("line") + .data(data) + .enter() + .append("line") + .attr("x1", x) + .attr("y1", y) + .attr("x2", d => d[0]) + .attr("y2", d => d[1]) + .attr("stroke", "#d0240f") + .attr("stroke-width", 0.5) + .attr("opacity", 0.2) + .attr("stroke-dashoffset", d => d[2]) + .attr("stroke-dasharray", d => d[2]) + .transition(animation) + .attr("stroke-dashoffset", 0) + .attr("opacity", 1) + .transition(animation) + .delay(1000) + .attr("stroke-dashoffset", d => d[2]) + .attr("opacity", 0) + .remove(); } // assign lock behavior -document.querySelectorAll("[data-locked]").forEach(function(e) { - e.addEventListener("mouseover", function(event) { +document.querySelectorAll("[data-locked]").forEach(function (e) { + e.addEventListener("mouseover", function (event) { if (this.className === "icon-lock") tip("Click to unlock the option and allow it to be randomized on new map generation"); else tip("Click to lock the option and always use the current value on new map generation"); event.stopPropagation(); }); - e.addEventListener("click", function() { - const id = (this.id).slice(5); + e.addEventListener("click", function () { + const id = this.id.slice(5); if (this.className === "icon-lock") unlock(id); else lock(id); }); @@ -349,10 +415,10 @@ document.querySelectorAll("[data-locked]").forEach(function(e) { // lock option function lock(id) { - const input = document.querySelector("[data-stored='"+id+"']"); + const input = document.querySelector("[data-stored='" + id + "']"); if (input) localStorage.setItem(id, input.value); const el = document.getElementById("lock_" + id); - if(!el) return; + if (!el) return; el.dataset.locked = 1; el.className = "icon-lock"; } @@ -361,7 +427,7 @@ function lock(id) { function unlock(id) { localStorage.removeItem(id); const el = document.getElementById("lock_" + id); - if(!el) return; + if (!el) return; el.dataset.locked = 0; el.className = "icon-lock-open"; } @@ -403,7 +469,7 @@ function applyOption(select, id, name = id) { // show info about the generator in a popup function showInfo() { const Discord = link("https://discordapp.com/invite/X7E84HU", "Discord"); - const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit") + const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit"); const Patreon = link("https://www.patreon.com/azgaar", "Patreon"); const Trello = link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Trello"); const Armoria = link("https://azgaar.github.io/Armoria", "Armoria"); @@ -433,8 +499,15 @@ function showInfo() {
  • ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys", "Hotkeys")}
  • `; - $("#alert").dialog({resizable: false, title: document.title, width: "28em", - buttons: {OK: function() {$(this).dialog("close");}}, + $("#alert").dialog({ + resizable: false, + title: document.title, + width: "28em", + buttons: { + OK: function () { + $(this).dialog("close"); + } + }, position: {my: "center", at: "center", of: "svg"} }); } @@ -460,93 +533,170 @@ document.addEventListener("keyup", event => { const shift = event.shiftKey || key === 16; const alt = event.altKey || key === 18; - if (key === 112) showInfo(); // "F1" to show info - else if (key === 113) regeneratePrompt(); // "F2" for new map - else if (key === 113) regeneratePrompt(); // "F2" for a new map - else if (key === 117) quickSave(); // "F6" for quick save - else if (key === 120) quickLoad(); // "F9" for quick load - else if (key === 9) toggleOptions(event); // Tab to toggle options - else if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs - else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element - else if (key === 79 && canvas3d) toggle3dOptions(); // "O" to toggle 3d options - - else if (ctrl && key === 81) toggleSaveReminder(); // Ctrl + "Q" to toggle save reminder - else if (ctrl && key === 83) saveMap(); // Ctrl + "S" to save .map file - else if (undo.offsetParent && ctrl && key === 90) undo.click(); // Ctrl + "Z" to undo - else if (redo.offsetParent && ctrl && key === 89) redo.click(); // Ctrl + "Y" to redo - - else if (shift && key === 72) editHeightmap(); // Shift + "H" to edit Heightmap - else if (shift && key === 66) editBiomes(); // Shift + "B" to edit Biomes - else if (shift && key === 83) editStates(); // Shift + "S" to edit States - else if (shift && key === 80) editProvinces(); // Shift + "P" to edit Provinces - else if (shift && key === 68) editDiplomacy(); // Shift + "D" to edit Diplomacy - else if (shift && key === 67) editCultures(); // Shift + "C" to edit Cultures - else if (shift && key === 78) editNamesbase(); // Shift + "N" to edit Namesbase - else if (shift && key === 90) editZones(); // Shift + "Z" to edit Zones - else if (shift && key === 82) editReligions(); // Shift + "R" to edit Religions - else if (shift && key === 89) openEmblemEditor(); // Shift + "Y" to edit Emblems - else if (shift && key === 81) editUnits(); // Shift + "Q" to edit Units - else if (shift && key === 79) editNotes(); // Shift + "O" to edit Notes - else if (shift && key === 84) overviewBurgs(); // Shift + "T" to open Burgs overview - else if (shift && key === 86) overviewRivers(); // Shift + "V" to open Rivers overview - else if (shift && key === 77) overviewMilitary(); // Shift + "M" to open Military overview - else if (shift && key === 69) viewCellDetails(); // Shift + "E" to open Cell Details - - else if (shift && key === 49) toggleAddBurg(); // Shift + "1" to click to add Burg - else if (shift && key === 50) toggleAddLabel(); // Shift + "2" to click to add Label - else if (shift && key === 51) toggleAddRiver(); // Shift + "3" to click to add River - else if (shift && key === 52) toggleAddRoute(); // Shift + "4" to click to add Route - else if (shift && key === 53) toggleAddMarker(); // Shift + "5" to click to add Marker - - else if (alt && key === 66) console.table(pack.burgs); // Alt + "B" to log burgs data - else if (alt && key === 83) console.table(pack.states); // Alt + "S" to log states data - else if (alt && key === 67) console.table(pack.cultures); // Alt + "C" to log cultures data - else if (alt && key === 82) console.table(pack.religions); // Alt + "R" to log religions data - else if (alt && key === 70) console.table(pack.features); // Alt + "F" to log features data - - else if (key === 88) toggleTexture(); // "X" to toggle Texture layer - else if (key === 72) toggleHeight(); // "H" to toggle Heightmap layer - else if (key === 66) toggleBiomes(); // "B" to toggle Biomes layer - else if (key === 69) toggleCells(); // "E" to toggle Cells layer - else if (key === 71) toggleGrid(); // "G" to toggle Grid layer - else if (key === 79) toggleCoordinates(); // "O" to toggle Coordinates layer - else if (key === 87) toggleCompass(); // "W" to toggle Compass Rose layer - else if (key === 86) toggleRivers(); // "V" to toggle Rivers layer - else if (key === 70) toggleRelief(); // "F" to toggle Relief icons layer - else if (key === 67) toggleCultures(); // "C" to toggle Cultures layer - else if (key === 83) toggleStates(); // "S" to toggle States layer - else if (key === 80) toggleProvinces(); // "P" to toggle Provinces layer - else if (key === 90) toggleZones(); // "Z" to toggle Zones - else if (key === 68) toggleBorders(); // "D" to toggle Borders layer - else if (key === 82) toggleReligions(); // "R" to toggle Religions layer - else if (key === 85) toggleRoutes(); // "U" to toggle Routes layer - else if (key === 84) toggleTemp(); // "T" to toggle Temperature layer - else if (key === 78) togglePopulation(); // "N" to toggle Population layer - else if (key === 74) toggleIce(); // "J" to toggle Ice layer - else if (key === 65) togglePrec(); // "A" to toggle Precipitation layer - else if (key === 89) toggleEmblems(); // "Y" to toggle Emblems layer - else if (key === 76) toggleLabels(); // "L" to toggle Labels layer - else if (key === 73) toggleIcons(); // "I" to toggle Icons layer - else if (key === 77) toggleMilitary(); // "M" to toggle Military layer - else if (key === 75) toggleMarkers(); // "K" to toggle Markers layer - else if (key === 187) toggleRulers(); // Equal (=) to toggle Rulers - else if (key === 189) toggleScaleBar(); // Minus (-) to toggle Scale bar - - else if (key === 37) zoom.translateBy(svg, 10, 0); // Left to scroll map left - else if (key === 39) zoom.translateBy(svg, -10, 0); // Right to scroll map right - else if (key === 38) zoom.translateBy(svg, 0, 10); // Up to scroll map up - else if (key === 40) zoom.translateBy(svg, 0, -10); // Up to scroll map up - else if (key === 107 || key === 109) pressNumpadSign(key); // Numpad Plus/Minus to zoom map or change brush size - else if (key === 48 || key === 96) resetZoom(1000); // 0 to reset zoom - else if (key === 49 || key === 97) zoom.scaleTo(svg, 1); // 1 to zoom to 1 - else if (key === 50 || key === 98) zoom.scaleTo(svg, 2); // 2 to zoom to 2 - else if (key === 51 || key === 99) zoom.scaleTo(svg, 3); // 3 to zoom to 3 - else if (key === 52 || key === 100) zoom.scaleTo(svg, 4); // 4 to zoom to 4 - else if (key === 53 || key === 101) zoom.scaleTo(svg, 5); // 5 to zoom to 5 - else if (key === 54 || key === 102) zoom.scaleTo(svg, 6); // 6 to zoom to 6 - else if (key === 55 || key === 103) zoom.scaleTo(svg, 7); // 7 to zoom to 7 - else if (key === 56 || key === 104) zoom.scaleTo(svg, 8); // 8 to zoom to 8 - else if (key === 57 || key === 105) zoom.scaleTo(svg, 9); // 9 to zoom to 9 + if (key === 112) showInfo(); + // "F1" to show info + else if (key === 113) regeneratePrompt(); + // "F2" for new map + else if (key === 113) regeneratePrompt(); + // "F2" for a new map + else if (key === 117) quickSave(); + // "F6" for quick save + else if (key === 120) quickLoad(); + // "F9" for quick load + else if (key === 9) toggleOptions(event); + // Tab to toggle options + else if (key === 27) { + closeDialogs(); + hideOptions(); + } // Escape to close all dialogs + else if (key === 46) removeElementOnKey(); + // "Delete" to remove the selected element + else if (key === 79 && canvas3d) toggle3dOptions(); + // "O" to toggle 3d options + else if (ctrl && key === 81) toggleSaveReminder(); + // Ctrl + "Q" to toggle save reminder + else if (ctrl && key === 83) saveMap(); + // Ctrl + "S" to save .map file + else if (undo.offsetParent && ctrl && key === 90) undo.click(); + // Ctrl + "Z" to undo + else if (redo.offsetParent && ctrl && key === 89) redo.click(); + // Ctrl + "Y" to redo + else if (shift && key === 72) editHeightmap(); + // Shift + "H" to edit Heightmap + else if (shift && key === 66) editBiomes(); + // Shift + "B" to edit Biomes + else if (shift && key === 83) editStates(); + // Shift + "S" to edit States + else if (shift && key === 80) editProvinces(); + // Shift + "P" to edit Provinces + else if (shift && key === 68) editDiplomacy(); + // Shift + "D" to edit Diplomacy + else if (shift && key === 67) editCultures(); + // Shift + "C" to edit Cultures + else if (shift && key === 78) editNamesbase(); + // Shift + "N" to edit Namesbase + else if (shift && key === 90) editZones(); + // Shift + "Z" to edit Zones + else if (shift && key === 82) editReligions(); + // Shift + "R" to edit Religions + else if (shift && key === 89) openEmblemEditor(); + // Shift + "Y" to edit Emblems + else if (shift && key === 81) editUnits(); + // Shift + "Q" to edit Units + else if (shift && key === 79) editNotes(); + // Shift + "O" to edit Notes + else if (shift && key === 84) overviewBurgs(); + // Shift + "T" to open Burgs overview + else if (shift && key === 86) overviewRivers(); + // Shift + "V" to open Rivers overview + else if (shift && key === 77) overviewMilitary(); + // Shift + "M" to open Military overview + else if (shift && key === 69) viewCellDetails(); + // Shift + "E" to open Cell Details + else if (shift && key === 49) toggleAddBurg(); + // Shift + "1" to click to add Burg + else if (shift && key === 50) toggleAddLabel(); + // Shift + "2" to click to add Label + else if (shift && key === 51) toggleAddRiver(); + // Shift + "3" to click to add River + else if (shift && key === 52) toggleAddRoute(); + // Shift + "4" to click to add Route + else if (shift && key === 53) toggleAddMarker(); + // Shift + "5" to click to add Marker + else if (alt && key === 66) console.table(pack.burgs); + // Alt + "B" to log burgs data + else if (alt && key === 83) console.table(pack.states); + // Alt + "S" to log states data + else if (alt && key === 67) console.table(pack.cultures); + // Alt + "C" to log cultures data + else if (alt && key === 82) console.table(pack.religions); + // Alt + "R" to log religions data + else if (alt && key === 70) console.table(pack.features); + // Alt + "F" to log features data + else if (key === 88) toggleTexture(); + // "X" to toggle Texture layer + else if (key === 72) toggleHeight(); + // "H" to toggle Heightmap layer + else if (key === 66) toggleBiomes(); + // "B" to toggle Biomes layer + else if (key === 69) toggleCells(); + // "E" to toggle Cells layer + else if (key === 71) toggleGrid(); + // "G" to toggle Grid layer + else if (key === 79) toggleCoordinates(); + // "O" to toggle Coordinates layer + else if (key === 87) toggleCompass(); + // "W" to toggle Compass Rose layer + else if (key === 86) toggleRivers(); + // "V" to toggle Rivers layer + else if (key === 70) toggleRelief(); + // "F" to toggle Relief icons layer + else if (key === 67) toggleCultures(); + // "C" to toggle Cultures layer + else if (key === 83) toggleStates(); + // "S" to toggle States layer + else if (key === 80) toggleProvinces(); + // "P" to toggle Provinces layer + else if (key === 90) toggleZones(); + // "Z" to toggle Zones + else if (key === 68) toggleBorders(); + // "D" to toggle Borders layer + else if (key === 82) toggleReligions(); + // "R" to toggle Religions layer + else if (key === 85) toggleRoutes(); + // "U" to toggle Routes layer + else if (key === 84) toggleTemp(); + // "T" to toggle Temperature layer + else if (key === 78) togglePopulation(); + // "N" to toggle Population layer + else if (key === 74) toggleIce(); + // "J" to toggle Ice layer + else if (key === 65) togglePrec(); + // "A" to toggle Precipitation layer + else if (key === 89) toggleEmblems(); + // "Y" to toggle Emblems layer + else if (key === 76) toggleLabels(); + // "L" to toggle Labels layer + else if (key === 73) toggleIcons(); + // "I" to toggle Icons layer + else if (key === 77) toggleMilitary(); + // "M" to toggle Military layer + else if (key === 75) toggleMarkers(); + // "K" to toggle Markers layer + else if (key === 187) toggleRulers(); + // Equal (=) to toggle Rulers + else if (key === 189) toggleScaleBar(); + // Minus (-) to toggle Scale bar + else if (key === 37) zoom.translateBy(svg, 10, 0); + // Left to scroll map left + else if (key === 39) zoom.translateBy(svg, -10, 0); + // Right to scroll map right + else if (key === 38) zoom.translateBy(svg, 0, 10); + // Up to scroll map up + else if (key === 40) zoom.translateBy(svg, 0, -10); + // Up to scroll map up + else if (key === 107 || key === 109) pressNumpadSign(key); + // Numpad Plus/Minus to zoom map or change brush size + else if (key === 48 || key === 96) resetZoom(1000); + // 0 to reset zoom + else if (key === 49 || key === 97) zoom.scaleTo(svg, 1); + // 1 to zoom to 1 + else if (key === 50 || key === 98) zoom.scaleTo(svg, 2); + // 2 to zoom to 2 + else if (key === 51 || key === 99) zoom.scaleTo(svg, 3); + // 3 to zoom to 3 + else if (key === 52 || key === 100) zoom.scaleTo(svg, 4); + // 4 to zoom to 4 + else if (key === 53 || key === 101) zoom.scaleTo(svg, 5); + // 5 to zoom to 5 + else if (key === 54 || key === 102) zoom.scaleTo(svg, 6); + // 6 to zoom to 6 + else if (key === 55 || key === 103) zoom.scaleTo(svg, 7); + // 7 to zoom to 7 + else if (key === 56 || key === 104) zoom.scaleTo(svg, 8); + // 8 to zoom to 8 + else if (key === 57 || key === 105) zoom.scaleTo(svg, 9); + // 9 to zoom to 9 else if (ctrl) pressControl(); // Control to toggle mode }); @@ -555,21 +705,21 @@ function pressNumpadSign(key) { let brush = null; const d = key === 107 ? 1 : -1; - if (brushRadius.offsetParent) brush = document.getElementById("brushRadius"); else - if (biomesManuallyBrush.offsetParent) brush = document.getElementById("biomesManuallyBrush"); else - if (statesManuallyBrush.offsetParent) brush = document.getElementById("statesManuallyBrush"); else - if (provincesManuallyBrush.offsetParent) brush = document.getElementById("provincesManuallyBrush"); else - if (culturesManuallyBrush.offsetParent) brush = document.getElementById("culturesManuallyBrush"); else - if (zonesBrush.offsetParent) brush = document.getElementById("zonesBrush"); else - if (religionsManuallyBrush.offsetParent) brush = document.getElementById("religionsManuallyBrush"); + if (brushRadius.offsetParent) brush = document.getElementById("brushRadius"); + else if (biomesManuallyBrush.offsetParent) brush = document.getElementById("biomesManuallyBrush"); + else if (statesManuallyBrush.offsetParent) brush = document.getElementById("statesManuallyBrush"); + else if (provincesManuallyBrush.offsetParent) brush = document.getElementById("provincesManuallyBrush"); + else if (culturesManuallyBrush.offsetParent) brush = document.getElementById("culturesManuallyBrush"); + else if (zonesBrush.offsetParent) brush = document.getElementById("zonesBrush"); + else if (religionsManuallyBrush.offsetParent) brush = document.getElementById("religionsManuallyBrush"); if (brush) { const value = Math.max(Math.min(+brush.value + d, +brush.max), +brush.min); - brush.value = document.getElementById(brush.id+"Number").value = value; + brush.value = document.getElementById(brush.id + "Number").value = value; return; } - const scaleBy = key === 107 ? 1.2 : .8; + const scaleBy = key === 107 ? 1.2 : 0.8; zoom.scaleBy(svg, scaleBy); // if no, zoom map } @@ -583,4 +733,4 @@ function pressControl() { function removeElementOnKey() { $(".dialog:visible .fastDelete").click(); $("button:visible:contains('Remove')").click(); -} \ No newline at end of file +} From e7cf6e4f5a251257d0d6e8bdd485a3a56993fd23 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 8 Jun 2021 23:32:46 +0300 Subject: [PATCH 08/34] detect depressions on river flow and return --- modules/river-generator.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/river-generator.js b/modules/river-generator.js index 72dfac1e..91566f52 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -6,9 +6,8 @@ const generate = function (allowErosion = true) { TIME && console.time("generateRivers"); Math.random = aleaPRNG(seed); - const cells = pack.cells, - p = cells.p, - features = pack.features; + const {cells, features} = pack; + const p = cells.p; const riversData = []; // rivers data cells.fl = new Uint16Array(cells.i.length); // water flux array @@ -93,7 +92,11 @@ } // downhill cell (make sure it's not in the source lake) - const min = lakeOutCells[i] ? cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c])).sort((a, b) => h[a] - h[b])[0] : cells.c[i].sort((a, b) => h[a] - h[b])[0]; + const filtered = lakeOutCells[i] ? cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c])) : cells.c[i]; + const min = filtered.sort((a, b) => h[a] - h[b])[0]; + + // cells is depressed + if (h[i] <= h[min]) return; if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) { if (h[min] >= 20) cells.fl[min] += cells.fl[i]; @@ -244,10 +247,7 @@ prevDepressions = depressions; } - console.log(iteration); - - if (!depressions) return; - WARN && console.warn(`Unresolved depressions: ${depressions}. Edit heightmap to fix`); + depressions && WARN && console.warn(`Unresolved depressions: ${depressions}. Edit heightmap to fix`); }; // add more river points on 1/3 and 2/3 of length From 234814ee56a1c24883acb5eda855a55ef28e1ea3 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 9 Jun 2021 00:24:57 +0300 Subject: [PATCH 09/34] check lakes before depressiong fill --- main.js | 3 +-- modules/lakes.js | 2 +- modules/river-generator.js | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/main.js b/main.js index 347670c4..1b64d1a2 100644 --- a/main.js +++ b/main.js @@ -795,11 +795,11 @@ function addLakesInDeepDepressions() { for (const n of c[q]) { if (checked[n]) continue; + if (h[n] >= treshold) continue; if (h[n] < 20) { deep = false; break; } - if (h[n] >= treshold) continue; checked[n] = true; queue.push(n); @@ -808,7 +808,6 @@ function addLakesInDeepDepressions() { // if not, add a lake if (deep) { - debug.append("circle").attr("cx", points[i][0]).attr("cy", points[i][1]).attr("r", 1).attr("fill", "red"); const lakeCells = [i].concat(c[i].filter(n => h[n] === h[i])); addLake(lakeCells); } diff --git a/modules/lakes.js b/modules/lakes.js index 0c91ca67..cf73b5dc 100644 --- a/modules/lakes.js +++ b/modules/lakes.js @@ -45,7 +45,7 @@ delete feature.shoreline; delete feature.outCell; delete feature.closed; - feature.height = rn(feature.height); + feature.height = rn(feature.height, 3); const inlets = feature.inlets?.filter(r => pack.rivers.find(river => river.i === r)); if (!inlets || !inlets.length) delete feature.inlets; diff --git a/modules/river-generator.js b/modules/river-generator.js index 91566f52..b6152544 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -27,13 +27,50 @@ TIME && console.timeEnd("generateRivers"); function prepareLakeData() { + const ELEVATION_LIMIT = 10; + features.forEach(f => { if (f.type !== "lake") return; delete f.flux; delete f.inlets; delete f.outlet; delete f.height; + delete f.closed; !f.shoreline && Lakes.getShoreline(f); + + // lake surface height is as lowest land cells around + const min = f.shoreline.sort((a, b) => h[a] - h[b])[0]; + f.height = h[min] - 0.1; + + // check if lake can be open (not in deep depression) + let deep = true; + const treshold = f.height + ELEVATION_LIMIT; + const queue = [min]; + const checked = []; + checked[min] = true; + + // check if elevated lake can potentially pour to another water body + while (deep && queue.length) { + const q = queue.pop(); + + for (const n of cells.c[q]) { + if (checked[n]) continue; + if (h[n] >= treshold) continue; + + if (h[n] < 20) { + const nFeature = features[cells.f[n]]; + if (nFeature.type === "ocean" || f.height > nFeature.height) { + deep = false; + break; + } + } + + checked[n] = true; + queue.push(n); + } + } + + f.closed = deep; }); } From 820cb22463b454882877f26a3b8a2a05eaac1aa5 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 9 Jun 2021 00:39:20 +0300 Subject: [PATCH 10/34] version pump --- index.html | 2 +- main.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 907d690a..4a6cc879 100644 --- a/index.html +++ b/index.html @@ -234,7 +234,7 @@
    Azgaar's
    Fantasy Map Generator
    -
    v. 1.61
    +
    v. 1.62

    LOADING...

    diff --git a/main.js b/main.js index 1b64d1a2..23617a39 100644 --- a/main.js +++ b/main.js @@ -2,7 +2,7 @@ // https://github.com/Azgaar/Fantasy-Map-Generator "use strict"; -const version = "1.61"; // generator version +const version = "1.62"; // generator version document.title += " v" + version; // Switches to disable/enable logging features @@ -13,7 +13,7 @@ const WARN = 1; const ERROR = 1; // if map version is not stored, clear localStorage and show a message -if (rn(localStorage.getItem("version"), 2) !== rn(version, 2)) { +if (rn(localStorage.getItem("version"), 1) !== rn(version, 1)) { localStorage.clear(); setTimeout(showWelcomeMessage, 8000); } @@ -390,6 +390,7 @@ function showWelcomeMessage() {
  • Lakes now have names
  • Rulers rework (v1.61)
  • New ocean pattern by Kiwiroo (v1.61)
  • +
  • Water erosion rework (v1.62)
  • Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.

    From 2162c043c934719e01536fe450d34bda8ce5f6bb Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 10 Jun 2021 20:23:45 +0300 Subject: [PATCH 11/34] fix add river on click --- modules/lakes.js | 51 +++++++++++++++++++++++++++++++++- modules/river-generator.js | 56 +++----------------------------------- modules/ui/tools.js | 23 ++++++---------- 3 files changed, 63 insertions(+), 67 deletions(-) diff --git a/modules/lakes.js b/modules/lakes.js index cf73b5dc..9cae7196 100644 --- a/modules/lakes.js +++ b/modules/lakes.js @@ -37,6 +37,55 @@ lake.shoreline = [...uniqueCells]; }; + const prepareLakeData = h => { + const cells = pack.cells; + const ELEVATION_LIMIT = 10; + + pack.features.forEach(f => { + if (f.type !== "lake") return; + delete f.flux; + delete f.inlets; + delete f.outlet; + delete f.height; + delete f.closed; + !f.shoreline && Lakes.getShoreline(f); + + // lake surface height is as lowest land cells around + const min = f.shoreline.sort((a, b) => h[a] - h[b])[0]; + f.height = h[min] - 0.1; + + // check if lake can be open (not in deep depression) + let deep = true; + const treshold = f.height + ELEVATION_LIMIT; + const queue = [min]; + const checked = []; + checked[min] = true; + + // check if elevated lake can potentially pour to another water body + while (deep && queue.length) { + const q = queue.pop(); + + for (const n of cells.c[q]) { + if (checked[n]) continue; + if (h[n] >= treshold) continue; + + if (h[n] < 20) { + const nFeature = pack.features[cells.f[n]]; + if (nFeature.type === "ocean" || f.height > nFeature.height) { + deep = false; + break; + } + } + + checked[n] = true; + queue.push(n); + } + } + + f.closed = deep; + }); + }; + const cleanupLakeData = function () { for (const feature of pack.features) { if (feature.type !== "lake") continue; @@ -95,5 +144,5 @@ return "freshwater"; } - return {setClimateData, cleanupLakeData, defineGroup, generateName, getName, getShoreline}; + return {setClimateData, cleanupLakeData, prepareLakeData, defineGroup, generateName, getName, getShoreline}; }); diff --git a/modules/river-generator.js b/modules/river-generator.js index b6152544..81f3457f 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -16,7 +16,7 @@ let riverNext = 1; // first river id is 1 const h = alterHeights(); - prepareLakeData(); + Lakes.prepareLakeData(h); resolveDepressions(h, 200); drainWater(); defineRivers(); @@ -26,54 +26,6 @@ TIME && console.timeEnd("generateRivers"); - function prepareLakeData() { - const ELEVATION_LIMIT = 10; - - features.forEach(f => { - if (f.type !== "lake") return; - delete f.flux; - delete f.inlets; - delete f.outlet; - delete f.height; - delete f.closed; - !f.shoreline && Lakes.getShoreline(f); - - // lake surface height is as lowest land cells around - const min = f.shoreline.sort((a, b) => h[a] - h[b])[0]; - f.height = h[min] - 0.1; - - // check if lake can be open (not in deep depression) - let deep = true; - const treshold = f.height + ELEVATION_LIMIT; - const queue = [min]; - const checked = []; - checked[min] = true; - - // check if elevated lake can potentially pour to another water body - while (deep && queue.length) { - const q = queue.pop(); - - for (const n of cells.c[q]) { - if (checked[n]) continue; - if (h[n] >= treshold) continue; - - if (h[n] < 20) { - const nFeature = features[cells.f[n]]; - if (nFeature.type === "ocean" || f.height > nFeature.height) { - deep = false; - break; - } - } - - checked[n] = true; - queue.push(n); - } - } - - f.closed = deep; - }); - } - function drainWater() { const MIN_FLUX_TO_FORM_RIVER = 30; const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]); @@ -223,13 +175,13 @@ }; // add distance to water value to land cells to make map less depressed - function alterHeights() { + const alterHeights = () => { const cells = pack.cells; return Array.from(cells.h).map((h, i) => { if (h < 20 || cells.t[i] < 1) return h; return h + cells.t[i] / 100 + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000; }); - } + }; // depression filling algorithm (for a correct water flux modeling) const resolveDepressions = function (h, maxIterations) { @@ -426,5 +378,5 @@ return getBasin(parent); }; - return {generate, resolveDepressions, addMeandering, getPath, specify, getName, getBasin, remove}; + return {generate, alterHeights, resolveDepressions, addMeandering, getPath, specify, getName, getBasin, remove}; }); diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 16b8fa87..1a5dd1a1 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -539,25 +539,19 @@ function addRiverOnClick() { let river = +getNextId("river").slice(5); // river id cells.fl[i] = grid.cells.prec[cells.g[i]]; // initial flux - // height with added t value to make map less depressed - const h = Array.from(cells.h) - .map((h, i) => (h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100)) - .map((h, i) => (h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000)); + const h = Rivers.alterHeights(); + Lakes.prepareLakeData(h); Rivers.resolveDepressions(h, 200); while (i) { cells.r[i] = river; - const x = cells.p[i][0], - y = cells.p[i][1]; + const [x, y] = cells.p[i]; dataRiver.push({x, y, cell: i}); - const min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell - if (h[i] <= h[min]) { - tip(`Cell ${i} is depressed, river cannot flow further`, false, "error"); - return; - } - const tx = cells.p[min][0], - ty = cells.p[min][1]; + const min = cells.c[i].sort((a, b) => h[a] - h[b])[0]; // downhill cell + if (h[i] <= h[min]) return tip(`Cell ${i} is depressed, river cannot flow further`, false, "error"); + + const [tx, ty] = cells.p[min]; if (h[min] < 20) { // pour to water body @@ -572,7 +566,7 @@ function addRiverOnClick() { continue; } - // hadnle case when lowest cell already has a river + // handle case when lowest cell already has a river const r = cells.r[min]; const riverCells = cells.i.filter(i => cells.r[i] === r); const riverCellsUpper = riverCells.filter(i => h[i] > h[min]); @@ -625,6 +619,7 @@ function addRiverOnClick() { } if (d3.event.shiftKey === false) { + Lakes.cleanupLakeData(); unpressClickToAddButton(); document.getElementById("addNewRiver").classList.remove("pressed"); if (addNewRiver.offsetParent) riversOverviewRefresh.click(); From a77c6ed9c5bdb1a864191a81479f3a8201b9ff6d Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 12 Jun 2021 11:50:10 +0300 Subject: [PATCH 12/34] clear note style --- index.html | 1 + modules/ui/notes-editor.js | 62 ++++++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/index.html b/index.html index 4a6cc879..09f5a2c6 100644 --- a/index.html +++ b/index.html @@ -2838,6 +2838,7 @@ + diff --git a/modules/ui/notes-editor.js b/modules/ui/notes-editor.js index 04facf08..5a343966 100644 --- a/modules/ui/notes-editor.js +++ b/modules/ui/notes-editor.js @@ -3,7 +3,9 @@ function editNotes(id, name) { // update list of objects const select = document.getElementById("notesSelect"); select.options.length = 0; - for (const note of notes) {select.options.add(new Option(note.id, note.id));} + for (const note of notes) { + select.options.add(new Option(note.id, note.id)); + } // initiate pell (html editor) const editor = Pell.init({ @@ -38,9 +40,11 @@ function editNotes(id, name) { // open a dialog $("#notesEditor").dialog({ - title: "Notes Editor", minWidth: "40em", width: "50vw", + title: "Notes Editor", + minWidth: "40em", + width: "50vw", position: {my: "center", at: "center", of: "svg"}, - close: () => notesText.innerHTML = "" + close: () => (notesText.innerHTML = "") }); if (modules.editNotes) return; @@ -49,12 +53,15 @@ function editNotes(id, name) { // add listeners document.getElementById("notesSelect").addEventListener("change", changeObject); document.getElementById("notesName").addEventListener("input", changeName); - document.getElementById("notesPin").addEventListener("click", () => options.pinNotes = !options.pinNotes); + document.getElementById("notesPin").addEventListener("click", () => (options.pinNotes = !options.pinNotes)); document.getElementById("notesSpeak").addEventListener("click", () => speak(editor.content.innerHTML)); document.getElementById("notesFocus").addEventListener("click", validateHighlightElement); document.getElementById("notesDownload").addEventListener("click", downloadLegends); document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click()); - document.getElementById("legendsToLoad").addEventListener("change", function() {uploadFile(this, uploadLegends)}); + document.getElementById("legendsToLoad").addEventListener("change", function () { + uploadFile(this, uploadLegends); + }); + document.getElementById("notesClearStyle").addEventListener("click", clearStyle); document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove); function showNote(note) { @@ -85,15 +92,22 @@ function editNotes(id, name) { // if element is not found if (element === null) { alertMessage.innerHTML = "Related element is not found. Would you like to remove the note?"; - $("#alert").dialog({resizable: false, title: "Element not found", + $("#alert").dialog({ + resizable: false, + title: "Element not found", buttons: { - Remove: function() {$(this).dialog("close"); removeLegend();}, - Keep: function() {$(this).dialog("close");} + Remove: function () { + $(this).dialog("close"); + removeLegend(); + }, + Keep: function () { + $(this).dialog("close"); + } } }); return; } - + highlightElement(element); // if element is found } @@ -104,18 +118,32 @@ function editNotes(id, name) { } function uploadLegends(dataLoaded) { - if (!dataLoaded) {tip("Cannot load the file. Please check the data format", false, "error"); return;} + if (!dataLoaded) { + tip("Cannot load the file. Please check the data format", false, "error"); + return; + } notes = JSON.parse(dataLoaded); document.getElementById("notesSelect").options.length = 0; editNotes(notes[0].id, notes[0].name); } + function clearStyle() { + editor.content.innerHTML = editor.content.textContent; + } + function triggerNotesRemove() { alertMessage.innerHTML = "Are you sure you want to remove the selected note?"; - $("#alert").dialog({resizable: false, title: "Remove note", + $("#alert").dialog({ + resizable: false, + title: "Remove note", buttons: { - Remove: function() {$(this).dialog("close"); removeLegend();}, - Keep: function() {$(this).dialog("close");} + Remove: function () { + $(this).dialog("close"); + removeLegend(); + }, + Keep: function () { + $(this).dialog("close"); + } } }); } @@ -125,9 +153,11 @@ function editNotes(id, name) { const index = notes.findIndex(n => n.id === select.value); notes.splice(index, 1); select.options.length = 0; - if (!notes.length) {$("#notesEditor").dialog("close"); return;} + if (!notes.length) { + $("#notesEditor").dialog("close"); + return; + } notesText.innerHTML = ""; editNotes(notes[0].id, notes[0].name); } - -} \ No newline at end of file +} From ae60b2c47ce635fb9290c79b4ab2005021a67d7c Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 12 Jun 2021 14:49:36 +0300 Subject: [PATCH 13/34] store hideLabel state in .map file --- modules/save-and-load.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/save-and-load.js b/modules/save-and-load.js index e30da762..d3353525 100644 --- a/modules/save-and-load.js +++ b/modules/save-and-load.js @@ -340,7 +340,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, barSize.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].join("|"); + const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value, barSize.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 coords = JSON.stringify(mapCoordinates); const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|"); const notesData = JSON.stringify(notes); @@ -700,6 +700,7 @@ function parseLoadedData(data) { if (settings[18]) precInput.value = precOutput.value = settings[18]; if (settings[19]) options = JSON.parse(settings[19]); if (settings[20]) mapName.value = settings[20]; + if (settings[21]) hideLabels.checked = +settings[21]; })(); void (function parseConfiguration() { From 47dd3c05aaa6d516fc8874c28e7b28bec21a11ae Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 12 Jun 2021 14:54:28 +0300 Subject: [PATCH 14/34] fix ocean pattenr for Gloom style --- modules/ui/style.js | 600 +++++++++++++++++++++++++------------------- 1 file changed, 341 insertions(+), 259 deletions(-) diff --git a/modules/ui/style.js b/modules/ui/style.js index 59e7f272..2a298f27 100644 --- a/modules/ui/style.js +++ b/modules/ui/style.js @@ -2,7 +2,7 @@ "use strict"; // store some style inputs as options -styleElements.addEventListener("change", function(ev) { +styleElements.addEventListener("change", function (ev) { if (ev.target.dataset.stored) lock(ev.target.dataset.stored); }); @@ -26,21 +26,24 @@ function editStyle(element, group) { styleElementSelect.addEventListener("change", selectStyleElement); function selectStyleElement() { const sel = styleElementSelect.value; - let el = d3.select("#"+sel); + let el = d3.select("#" + sel); - styleElements.querySelectorAll("tbody").forEach(e => e.style.display = "none"); // hide all sections + styleElements.querySelectorAll("tbody").forEach(e => (e.style.display = "none")); // hide all sections const off = sel !== "ocean" && (el.style("display") === "none" || !el.selectAll("*").size()); // check if layer is off if (off) { styleIsOff.style.display = "block"; - setTimeout(() => styleIsOff.style.display = "none", 1500); + setTimeout(() => (styleIsOff.style.display = "none"), 1500); } // active group element const group = styleGroupSelect.value; if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(sel)) { - el = d3.select("#"+sel).select("g#"+group).size() - ? d3.select("#"+sel).select("g#"+group) - : d3.select("#"+sel).select("g"); + el = d3 + .select("#" + sel) + .select("g#" + group) + .size() + ? d3.select("#" + sel).select("g#" + group) + : d3.select("#" + sel).select("g"); } if (sel !== "landmass" && sel !== "legend") { @@ -84,7 +87,7 @@ function selectStyleElement() { // show specific sections if (sel === "texture") styleTexture.style.display = "block"; - if (sel === "routes", "labels" || sel == "anchors" || sel == "burgIcons", "coastline", "lakes", "borders") styleGroup.style.display = "block"; + if ((sel === "routes", "labels" || sel == "anchors" || sel == "burgIcons", "coastline", "lakes", "borders")) styleGroup.style.display = "block"; if (sel === "terrs") { styleHeightmap.style.display = "block"; @@ -175,7 +178,7 @@ function selectStyleElement() { styleRadius.style.display = "block"; styleFillInput.value = styleFillOutput.value = el.attr("fill") || "#ffffff"; styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke") || "#3e3e4b"; - styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || .24; + styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || 0.24; styleStrokeDasharrayInput.value = el.attr("stroke-dasharray") || ""; styleStrokeLinecapInput.value = el.attr("stroke-linecap") || "inherit"; styleRadiusInput.value = el.attr("size") || 1; @@ -188,7 +191,7 @@ function selectStyleElement() { styleIconSize.style.display = "block"; styleFillInput.value = styleFillOutput.value = el.attr("fill") || "#ffffff"; styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke") || "#3e3e4b"; - styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || .24; + styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || 0.24; styleIconSizeInput.value = el.attr("size") || 2; } @@ -205,7 +208,7 @@ function selectStyleElement() { styleLegendOpacityOutput.value = styleLegendOpacity.value = el.select("#legendBox").attr("fill-opacity"); styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke") || "#111111"; - styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || .5; + styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || 0.5; styleSelectFont.value = fonts.indexOf(el.attr("data-font")); styleInputFont.style.display = "none"; styleInputFont.value = ""; @@ -224,9 +227,9 @@ function selectStyleElement() { styleStrokeWidth.style.display = "block"; styleTemperature.style.display = "block"; styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || ""; - styleTemperatureFillOpacityInput.value = styleTemperatureFillOpacityOutput.value = el.attr("fill-opacity") || .1; + styleTemperatureFillOpacityInput.value = styleTemperatureFillOpacityOutput.value = el.attr("fill-opacity") || 0.1; styleTemperatureFillInput.value = styleTemperatureFillOutput.value = el.attr("fill") || "#000"; - styleTemperatureFontSizeInput.value = styleTemperatureFontSizeOutput.value = el.attr("font-size") || "8px";; + styleTemperatureFontSizeInput.value = styleTemperatureFontSizeOutput.value = el.attr("font-size") || "8px"; } if (sel === "coordinates") { @@ -249,11 +252,14 @@ function selectStyleElement() { // update group options styleGroupSelect.options.length = 0; // remove all options if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(sel)) { - document.getElementById(sel).querySelectorAll("g").forEach(el => { - if (el.id === "burgLabels") return; - const count = el.childElementCount; - styleGroupSelect.options.add(new Option(`${el.id} (${count})`, el.id, false, false)); - }); + document + .getElementById(sel) + .querySelectorAll("g") + .forEach(el => { + if (el.id === "burgLabels") return; + const count = el.childElementCount; + styleGroupSelect.options.add(new Option(`${el.id} (${count})`, el.id, false, false)); + }); styleGroupSelect.value = el.attr("id"); } else { styleGroupSelect.options.add(new Option(sel, sel, false, true)); @@ -261,85 +267,92 @@ function selectStyleElement() { if (sel === "coastline" && styleGroupSelect.value === "sea_island") { styleCoastline.style.display = "block"; - const auto = styleCoastlineAuto.checked = coastline.select("#sea_island").attr("auto-filter"); + const auto = (styleCoastlineAuto.checked = coastline.select("#sea_island").attr("auto-filter")); if (auto) styleFilter.style.display = "none"; } - } // Handle style inputs change styleGroupSelect.addEventListener("change", selectStyleElement); function getEl() { - const el = styleElementSelect.value, g = styleGroupSelect.value; - if (g === el) return svg.select("#"+el); else return svg.select("#"+el).select("#"+g); + const el = styleElementSelect.value, + g = styleGroupSelect.value; + if (g === el) return svg.select("#" + el); + else return svg.select("#" + el).select("#" + g); } -styleFillInput.addEventListener("input", function() { +styleFillInput.addEventListener("input", function () { styleFillOutput.value = this.value; - getEl().attr('fill', this.value); + getEl().attr("fill", this.value); }); -styleStrokeInput.addEventListener("input", function() { +styleStrokeInput.addEventListener("input", function () { styleStrokeOutput.value = this.value; - getEl().attr('stroke', this.value); + getEl().attr("stroke", this.value); if (styleElementSelect.value === "gridOverlay" && layerIsOn("toggleGrid")) drawGrid(); }); -styleStrokeWidthInput.addEventListener("input", function() { +styleStrokeWidthInput.addEventListener("input", function () { styleStrokeWidthOutput.value = this.value; - getEl().attr('stroke-width', +this.value); + getEl().attr("stroke-width", +this.value); if (styleElementSelect.value === "gridOverlay" && layerIsOn("toggleGrid")) drawGrid(); }); -styleStrokeDasharrayInput.addEventListener("input", function() { - getEl().attr('stroke-dasharray', this.value); +styleStrokeDasharrayInput.addEventListener("input", function () { + getEl().attr("stroke-dasharray", this.value); if (styleElementSelect.value === "gridOverlay" && layerIsOn("toggleGrid")) drawGrid(); }); -styleStrokeLinecapInput.addEventListener("change", function() { - getEl().attr('stroke-linecap', this.value); +styleStrokeLinecapInput.addEventListener("change", function () { + getEl().attr("stroke-linecap", this.value); if (styleElementSelect.value === "gridOverlay" && layerIsOn("toggleGrid")) drawGrid(); }); -styleOpacityInput.addEventListener("input", function() { +styleOpacityInput.addEventListener("input", function () { styleOpacityOutput.value = this.value; - getEl().attr('opacity', this.value); + getEl().attr("opacity", this.value); }); -styleFilterInput.addEventListener("change", function() { +styleFilterInput.addEventListener("change", function () { if (styleGroupSelect.value === "ocean") { - oceanLayers.attr('filter', this.value); + oceanLayers.attr("filter", this.value); return; } - getEl().attr('filter', this.value); + getEl().attr("filter", this.value); }); -styleTextureInput.addEventListener("change", function() { +styleTextureInput.addEventListener("change", function () { if (this.value === "none") texture.select("image").attr("xlink:href", ""); if (this.value === "default") texture.select("image").attr("xlink:href", getDefaultTexture()); else getBase64(this.value, base64 => texture.select("image").attr("xlink:href", base64)); }); -styleTextureShiftX.addEventListener("input", function() { - texture.select("image").attr("x", this.value).attr("width", graphWidth - this.valueAsNumber); +styleTextureShiftX.addEventListener("input", function () { + texture + .select("image") + .attr("x", this.value) + .attr("width", graphWidth - this.valueAsNumber); }); -styleTextureShiftY.addEventListener("input", function() { - texture.select("image").attr("y", this.value).attr("height", graphHeight - this.valueAsNumber); +styleTextureShiftY.addEventListener("input", function () { + texture + .select("image") + .attr("y", this.value) + .attr("height", graphHeight - this.valueAsNumber); }); -styleClippingInput.addEventListener("change", function() { - getEl().attr('mask', this.value); +styleClippingInput.addEventListener("change", function () { + getEl().attr("mask", this.value); }); -styleGridType.addEventListener("change", function() { +styleGridType.addEventListener("change", function () { getEl().attr("type", this.value); if (layerIsOn("toggleGrid")) drawGrid(); calculateFriendlyGridSize(); }); -styleGridScale.addEventListener("input", function() { +styleGridScale.addEventListener("input", function () { getEl().attr("scale", this.value); if (layerIsOn("toggleGrid")) drawGrid(); calculateFriendlyGridSize(); @@ -351,12 +364,12 @@ function calculateFriendlyGridSize() { styleGridSizeFriendly.value = friendly; } -styleGridShiftX.addEventListener("input", function() { +styleGridShiftX.addEventListener("input", function () { getEl().attr("dx", this.value); if (layerIsOn("toggleGrid")) drawGrid(); }); -styleGridShiftY.addEventListener("input", function() { +styleGridShiftY.addEventListener("input", function () { getEl().attr("dy", this.value); if (layerIsOn("toggleGrid")) drawGrid(); }); @@ -370,77 +383,77 @@ function shiftElement() { getEl().attr("transform", `translate(${x},${y})`); } -styleRescaleMarkers.addEventListener("change", function() { +styleRescaleMarkers.addEventListener("change", function () { markers.attr("rescale", +this.checked); invokeActiveZooming(); }); -styleCoastlineAuto.addEventListener("change", function() { +styleCoastlineAuto.addEventListener("change", function () { coastline.select("#sea_island").attr("auto-filter", +this.checked); styleFilter.style.display = this.checked ? "none" : "block"; invokeActiveZooming(); }); -styleOceanFill.addEventListener("input", function() { +styleOceanFill.addEventListener("input", function () { oceanLayers.select("rect").attr("fill", this.value); styleOceanFillOutput.value = this.value; }); -styleOceanPattern.addEventListener("change", function() { +styleOceanPattern.addEventListener("change", function () { document.getElementById("oceanicPattern")?.setAttribute("href", this.value); }); -styleOceanPatternOpacity.addEventListener("input", function() { +styleOceanPatternOpacity.addEventListener("input", function () { document.getElementById("oceanPattern").setAttribute("opacity", this.value); styleOceanPatternOpacityOutput.value = this.value; }); -outlineLayers.addEventListener("change", function() { +outlineLayers.addEventListener("change", function () { oceanLayers.selectAll("path").remove(); oceanLayers.attr("layers", this.value); OceanLayers(); }); -styleHeightmapScheme.addEventListener("change", function() { +styleHeightmapScheme.addEventListener("change", function () { terrs.attr("scheme", this.value); drawHeightmap(); }); -styleHeightmapTerracing.addEventListener("input", function() { +styleHeightmapTerracing.addEventListener("input", function () { styleHeightmapTerracingOutput.value = this.value; terrs.attr("terracing", this.value); drawHeightmap(); }); -styleHeightmapSkip.addEventListener("input", function() { +styleHeightmapSkip.addEventListener("input", function () { styleHeightmapSkipOutput.value = this.value; terrs.attr("skip", this.value); drawHeightmap(); }); -styleHeightmapSimplification.addEventListener("input", function() { +styleHeightmapSimplification.addEventListener("input", function () { styleHeightmapSimplificationOutput.value = this.value; terrs.attr("relax", this.value); drawHeightmap(); }); -styleHeightmapCurve.addEventListener("change", function() { +styleHeightmapCurve.addEventListener("change", function () { terrs.attr("curve", this.value); drawHeightmap(); }); -styleReliefSet.addEventListener("change", function() { +styleReliefSet.addEventListener("change", function () { terrain.attr("set", this.value); ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief(); }); -styleReliefSizeInput.addEventListener("change", function() { +styleReliefSizeInput.addEventListener("change", function () { styleReliefSizeOutput.value = this.value; const size = +this.value; terrain.attr("size", size); - terrain.selectAll("use").each(function(d) { + terrain.selectAll("use").each(function (d) { const newSize = this.getAttribute("data-size") * size; const shift = (newSize - +this.getAttribute("width")) / 2; this.setAttribute("width", newSize); @@ -452,39 +465,39 @@ styleReliefSizeInput.addEventListener("change", function() { }); }); -styleReliefDensityInput.addEventListener("input", function() { +styleReliefDensityInput.addEventListener("input", function () { terrain.attr("density", this.value); styleReliefDensityOutput.value = this.value; ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief(); }); -styleTemperatureFillOpacityInput.addEventListener("input", function() { +styleTemperatureFillOpacityInput.addEventListener("input", function () { temperature.attr("fill-opacity", this.value); styleTemperatureFillOpacityOutput.value = this.value; }); -styleTemperatureFontSizeInput.addEventListener("input", function() { +styleTemperatureFontSizeInput.addEventListener("input", function () { temperature.attr("font-size", this.value + "px"); styleTemperatureFontSizeOutput.value = this.value + "px"; }); -styleTemperatureFillInput.addEventListener("input", function() { +styleTemperatureFillInput.addEventListener("input", function () { temperature.attr("fill", this.value); styleTemperatureFillOutput.value = this.value; }); -stylePopulationRuralStrokeInput.addEventListener("input", function() { +stylePopulationRuralStrokeInput.addEventListener("input", function () { population.select("#rural").attr("stroke", this.value); stylePopulationRuralStrokeOutput.value = this.value; }); -stylePopulationUrbanStrokeInput.addEventListener("input", function() { +stylePopulationUrbanStrokeInput.addEventListener("input", function () { population.select("#urban").attr("stroke", this.value); stylePopulationUrbanStrokeOutput.value = this.value; }); -styleCompassSizeInput.addEventListener("input", function() { +styleCompassSizeInput.addEventListener("input", function () { styleCompassSizeOutput.value = this.value; shiftCompass(); }); @@ -497,18 +510,18 @@ function shiftCompass() { compass.select("use").attr("transform", tr); } -styleLegendColItems.addEventListener("input", function() { +styleLegendColItems.addEventListener("input", function () { styleLegendColItemsOutput.value = this.value; legend.select("#legendBox").attr("data-columns", this.value); redrawLegend(); }); -styleLegendBack.addEventListener("input", function() { +styleLegendBack.addEventListener("input", function () { styleLegendBackOutput.value = this.value; legend.select("#legendBox").attr("fill", this.value); }); -styleLegendOpacity.addEventListener("input", function() { +styleLegendOpacity.addEventListener("input", function () { styleLegendOpacityOutput.value = this.value; legend.select("#legendBox").attr("fill-opacity", this.value); }); @@ -516,12 +529,12 @@ styleLegendOpacity.addEventListener("input", function() { styleSelectFont.addEventListener("change", changeFont); function changeFont() { const value = styleSelectFont.value; - const font = fonts[value].split(':')[0].replace(/\+/g, " "); + const font = fonts[value].split(":")[0].replace(/\+/g, " "); getEl().attr("font-family", font).attr("data-font", fonts[value]); if (styleElementSelect.value === "legend") redrawLegend(); } -styleFontAdd.addEventListener("click", function() { +styleFontAdd.addEventListener("click", function () { if (styleInputFont.style.display === "none") { styleInputFont.style.display = "inline-block"; styleInputFont.focus(); @@ -532,29 +545,32 @@ styleFontAdd.addEventListener("click", function() { } }); -styleInputFont.addEventListener("change", function() { - if (!this.value) {tip("Please provide a valid Google font name or link to a @font-face declaration"); return;} +styleInputFont.addEventListener("change", function () { + if (!this.value) { + tip("Please provide a valid Google font name or link to a @font-face declaration"); + return; + } fetchFonts(this.value).then(fetched => { if (!fetched) return; styleFontAdd.click(); styleInputFont.value = ""; if (fetched !== 1) return; - styleSelectFont.value = fonts.length-1; + styleSelectFont.value = fonts.length - 1; changeFont(); // auto-change font if 1 font is fetched }); }); -styleFontSize.addEventListener("change", function() { +styleFontSize.addEventListener("change", function () { changeFontSize(+this.value); }); -styleFontPlus.addEventListener("click", function() { +styleFontPlus.addEventListener("click", function () { const size = Math.max(rn(getEl().attr("data-size") * 1.1, 2), 1); changeFontSize(size); }); -styleFontMinus.addEventListener("click", function() { - const size = Math.max(rn(getEl().attr("data-size") * .9, 2), 1); +styleFontMinus.addEventListener("click", function () { + const size = Math.max(rn(getEl().attr("data-size") * 0.9, 2), 1); changeFontSize(size); }); @@ -562,83 +578,90 @@ function changeFontSize(size) { const legend = styleElementSelect.value === "legend"; const coords = styleElementSelect.value === "coordinates"; - const desSize = legend ? size : coords ? rn(size / scale ** .8, 2) : rn(size + (size / scale)); + const desSize = legend ? size : coords ? rn(size / scale ** 0.8, 2) : rn(size + size / scale); getEl().attr("data-size", size).attr("font-size", desSize); styleFontSize.value = size; if (legend) redrawLegend(); } -styleRadiusInput.addEventListener("change", function() { +styleRadiusInput.addEventListener("change", function () { changeRadius(+this.value); }); -styleRadiusPlus.addEventListener("click", function() { - const size = Math.max(rn(getEl().attr("size") * 1.1, 2), .2); +styleRadiusPlus.addEventListener("click", function () { + const size = Math.max(rn(getEl().attr("size") * 1.1, 2), 0.2); changeRadius(size); }); -styleRadiusMinus.addEventListener("click", function() { - const size = Math.max(rn(getEl().attr("size") * .9, 2), .2); +styleRadiusMinus.addEventListener("click", function () { + const size = Math.max(rn(getEl().attr("size") * 0.9, 2), 0.2); changeRadius(size); }); function changeRadius(size, group) { - const el = group ? burgIcons.select("#"+group) : getEl(); + const el = group ? burgIcons.select("#" + group) : getEl(); const g = el.attr("id"); - el.attr("size", size) - el.selectAll("circle").each(function() {this.setAttribute("r", size)}); + el.attr("size", size); + el.selectAll("circle").each(function () { + this.setAttribute("r", size); + }); styleRadiusInput.value = size; - burgLabels.select("g#"+g).selectAll("text").each(function() {this.setAttribute("dy", `${size * -1.5}px`)}); + burgLabels + .select("g#" + g) + .selectAll("text") + .each(function () { + this.setAttribute("dy", `${size * -1.5}px`); + }); changeIconSize(size * 2, g); // change also anchor icons } -styleIconSizeInput.addEventListener("change", function() { +styleIconSizeInput.addEventListener("change", function () { changeIconSize(+this.value); }); -styleIconSizePlus.addEventListener("click", function() { - const size = Math.max(rn(getEl().attr("size") * 1.1, 2), .2); +styleIconSizePlus.addEventListener("click", function () { + const size = Math.max(rn(getEl().attr("size") * 1.1, 2), 0.2); changeIconSize(size); }); -styleIconSizeMinus.addEventListener("click", function() { - const size = Math.max(rn(getEl().attr("size") * .9, 2), .2); +styleIconSizeMinus.addEventListener("click", function () { + const size = Math.max(rn(getEl().attr("size") * 0.9, 2), 0.2); changeIconSize(size); }); function changeIconSize(size, group) { - const el = group ? anchors.select("#"+group) : getEl(); + const el = group ? anchors.select("#" + group) : getEl(); const oldSize = +el.attr("size"); const shift = (size - oldSize) / 2; el.attr("size", size); - el.selectAll("use").each(function() { + el.selectAll("use").each(function () { const x = +this.getAttribute("x"); const y = +this.getAttribute("y"); this.setAttribute("x", x - shift); this.setAttribute("y", y - shift); this.setAttribute("width", size); this.setAttribute("height", size); - });; + }); styleIconSizeInput.value = size; } -styleStatesHaloWidth.addEventListener("input", function() { +styleStatesHaloWidth.addEventListener("input", function () { styleStatesHaloWidthOutput.value = this.value; statesHalo.attr("data-width", this.value).attr("stroke-width", this.value); }); -styleStatesHaloOpacity.addEventListener("input", function() { +styleStatesHaloOpacity.addEventListener("input", function () { styleStatesHaloOpacityOutput.value = this.value; statesHalo.attr("opacity", this.value); }); -styleArmiesFillOpacity.addEventListener("input", function() { +styleArmiesFillOpacity.addEventListener("input", function () { armies.attr("fill-opacity", this.value); styleArmiesFillOpacityOutput.value = this.value; }); -styleArmiesSize.addEventListener("input", function() { - armies.attr("box-size", this.value).attr("font-size", this.value*2); +styleArmiesSize.addEventListener("input", function () { + armies.attr("box-size", this.value).attr("font-size", this.value * 2); styleArmiesSizeOutput.value = this.value; armies.selectAll("g").remove(); // clear armies layer pack.states.forEach(s => { @@ -656,11 +679,17 @@ function textureProvideURL() { alertMessage.innerHTML = `Provide an image URL to be used as a texture: `; - $("#alert").dialog({resizable: false, title: "Load custom texture", width: "26em", + $("#alert").dialog({ + resizable: false, + title: "Load custom texture", + width: "26em", buttons: { - Apply: function() { + Apply: function () { const name = textureURL.value.split("/").pop(); - if (!name || name === "") {tip("Please provide a valid URL", false, "error"); return;} + if (!name || name === "") { + tip("Please provide a valid URL", false, "error"); + return; + } const opt = document.createElement("option"); opt.value = textureURL.value; opt.text = name.slice(0, 20); @@ -670,7 +699,9 @@ function textureProvideURL() { zoom.scaleBy(svg, 1.00001); // enforce browser re-draw $(this).dialog("close"); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } @@ -689,10 +720,10 @@ function fetchTextureURL(url) { const defaultStyles = { styleAncient: `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#emblems":{"opacity":0.6,"stroke-width":0.8,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":"./images/pattern1.png"},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`, - styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":"./images/pattern3.jpg"},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, + styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":"./images/pattern3.png"},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, styleClean: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#emblems":{"opacity":1,"stroke-width":1,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":""},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`, styleMonochrome: `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#emblems": {"opacity": 0.5,"stroke-width": 0.5,"filter": null},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":0},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":""},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}` -} +}; // apply default or custom style settings on load function applyStyleOnLoad() { @@ -715,88 +746,85 @@ function applyStyleOnLoad() { // set default style function applyDefaultStyle() { - armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", .3); + armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", 0.3); biomes.attr("opacity", null).attr("filter", null).attr("mask", null); - ice.attr("opacity", .9).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)"); - stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null); - provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .5).attr("stroke-dasharray", "0 2").attr("stroke-linecap", "round").attr("filter", null); - cells.attr("opacity", null).attr("stroke", "#808080").attr("stroke-width", .1).attr("filter", null).attr("mask", null); + ice.attr("opacity", 0.9).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)"); + stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null); + provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "0 2").attr("stroke-linecap", "round").attr("filter", null); + cells.attr("opacity", null).attr("stroke", "#808080").attr("stroke-width", 0.1).attr("filter", null).attr("mask", null); - gridOverlay.attr("opacity", .8).attr("type", "pointyHex").attr("scale", 1).attr("dx", 0).attr("dy", 0).attr("stroke", "#777777").attr("stroke-width", .5).attr("stroke-dasharray", null).attr("filter", null).attr("mask", null); + gridOverlay.attr("opacity", 0.8).attr("type", "pointyHex").attr("scale", 1).attr("dx", 0).attr("dy", 0).attr("stroke", "#777777").attr("stroke-width", 0.5).attr("stroke-dasharray", null).attr("filter", null).attr("mask", null); coordinates.attr("opacity", 1).attr("data-size", 12).attr("font-size", 12).attr("stroke", "#d4d4d4").attr("stroke-width", 1).attr("stroke-dasharray", 5).attr("filter", null).attr("mask", null); - compass.attr("opacity", .8).attr("transform", null).attr("filter", null).attr("mask", "url(#water)").attr("shape-rendering", "optimizespeed"); + compass.attr("opacity", 0.8).attr("transform", null).attr("filter", null).attr("mask", "url(#water)").attr("shape-rendering", "optimizespeed"); if (!d3.select("#initial").size()) d3.select("#rose").attr("transform", "translate(80 80) scale(.25)"); - relig.attr("opacity", .7).attr("stroke", "#777777").attr("stroke-width", 0).attr("filter", null); - cults.attr("opacity", .6).attr("stroke", "#777777").attr("stroke-width", .5).attr("filter", null); + relig.attr("opacity", 0.7).attr("stroke", "#777777").attr("stroke-width", 0).attr("filter", null); + cults.attr("opacity", 0.6).attr("stroke", "#777777").attr("stroke-width", 0.5).attr("filter", null); landmass.attr("opacity", 1).attr("fill", "#eef6fb").attr("filter", null); markers.attr("opacity", null).attr("rescale", 1).attr("filter", "url(#dropShadow01)"); - prec.attr("opacity", null).attr("stroke", "#000000").attr("stroke-width", .1).attr("fill", "#003dff").attr("filter", null); + prec.attr("opacity", null).attr("stroke", "#000000").attr("stroke-width", 0.1).attr("fill", "#003dff").attr("filter", null); population.attr("opacity", null).attr("stroke-width", 1.6).attr("stroke-dasharray", null).attr("stroke-linecap", "butt").attr("filter", null); population.select("#rural").attr("stroke", "#0000ff"); population.select("#urban").attr("stroke", "#ff0000"); - lakes.select("#freshwater").attr("opacity", .5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", .7).attr("filter", null); - lakes.select("#salt").attr("opacity", .5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", .7).attr("filter", null); - lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", .7).attr("filter", null); - lakes.select("#frozen").attr("opacity", .95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null); - lakes.select("#lava").attr("opacity", .7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)"); - lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", .7).attr("filter", null); + lakes.select("#freshwater").attr("opacity", 0.5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", 0.7).attr("filter", null); + lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null); + lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", 0.7).attr("filter", null); + lakes.select("#frozen").attr("opacity", 0.95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null); + lakes.select("#lava").attr("opacity", 0.7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)"); + lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null); - coastline.select("#sea_island").attr("opacity", .5).attr("stroke", "#1f3846").attr("stroke-width", .7).attr("auto-filter", 1).attr("filter", "url(#dropShadow)"); - coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", .35).attr("filter", null); + coastline.select("#sea_island").attr("opacity", 0.5).attr("stroke", "#1f3846").attr("stroke-width", 0.7).attr("auto-filter", 1).attr("filter", "url(#dropShadow)"); + coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null); - terrain.attr("opacity", null).attr("set", "simple").attr("size", 1).attr("density", .4).attr("filter", null).attr("mask", null); + terrain.attr("opacity", null).attr("set", "simple").attr("size", 1).attr("density", 0.4).attr("filter", null).attr("mask", null); rivers.attr("opacity", null).attr("fill", "#5d97bb").attr("filter", null); ruler.attr("opacity", null).attr("filter", null); - roads.attr("opacity", .9).attr("stroke", "#d06324").attr("stroke-width", .7).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null); - trails.attr("opacity", .9).attr("stroke", "#d06324").attr("stroke-width", .25).attr("stroke-dasharray", ".8 1.6").attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null); - searoutes.attr("opacity", .8).attr("stroke", "#ffffff").attr("stroke-width", .45).attr("stroke-dasharray", "1 2").attr("stroke-linecap", "round").attr("filter", null).attr("mask", null); + roads.attr("opacity", 0.9).attr("stroke", "#d06324").attr("stroke-width", 0.7).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null); + trails.attr("opacity", 0.9).attr("stroke", "#d06324").attr("stroke-width", 0.25).attr("stroke-dasharray", ".8 1.6").attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null); + searoutes.attr("opacity", 0.8).attr("stroke", "#ffffff").attr("stroke-width", 0.45).attr("stroke-dasharray", "1 2").attr("stroke-linecap", "round").attr("filter", null).attr("mask", null); - regions.attr("opacity", .4).attr("filter", null); + regions.attr("opacity", 0.4).attr("filter", null); statesHalo.attr("data-width", 10).attr("stroke-width", 10).attr("opacity", 1); - provs.attr("opacity", .7).attr("fill", "#000000").attr("font-family", "Georgia").attr("data-font", "Georgia").attr("data-size", 10).attr("font-size", 10).attr("filter", null); + provs.attr("opacity", 0.7).attr("fill", "#000000").attr("font-family", "Georgia").attr("data-font", "Georgia").attr("data-size", 10).attr("font-size", 10).attr("filter", null); - temperature.attr("opacity", null).attr("fill", "#000000").attr("stroke-width", 1.8).attr("fill-opacity", .3).attr("font-size", "8px").attr("stroke-dasharray", null).attr("filter", null).attr("mask", null); + temperature.attr("opacity", null).attr("fill", "#000000").attr("stroke-width", 1.8).attr("fill-opacity", 0.3).attr("font-size", "8px").attr("stroke-dasharray", null).attr("filter", null).attr("mask", null); texture.attr("opacity", null).attr("filter", null).attr("mask", "url(#land)"); texture.select("#textureImage").attr("x", 0).attr("y", 0); - zones.attr("opacity", .6).attr("stroke", "#333333").attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null); + zones.attr("opacity", 0.6).attr("stroke", "#333333").attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null); // ocean and svg default style svg.attr("background-color", "#000000").attr("data-filter", null).attr("filter", null); oceanLayers.select("rect").attr("fill", "#466eab"); // old color #53679f oceanLayers.attr("filter", null).attr("layers", "-6,-3,-1"); - oceanPattern.attr("opacity", .2); + oceanPattern.attr("opacity", 0.2); svg.select("#oceanicPattern").attr("href", "./images/pattern1.png"); // heightmap style - terrs.attr("opacity", null).attr("filter", null).attr("mask", "url(#land)").attr("stroke", "none") - .attr("scheme", "bright").attr("terracing", 0).attr("skip", 5).attr("relax", 0).attr("curve", 0); + terrs.attr("opacity", null).attr("filter", null).attr("mask", "url(#land)").attr("stroke", "none").attr("scheme", "bright").attr("terracing", 0).attr("skip", 5).attr("relax", 0).attr("curve", 0); // legend - legend.attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 13).attr("data-size", 13) - .attr("data-x", 99).attr("data-y", 93).attr("data-columns", 8) - .attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round"); - legend.select("#legendBox").attr("fill", "#ffffff").attr("fill-opacity", .8); + legend.attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93).attr("data-columns", 8).attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round"); + legend.select("#legendBox").attr("fill", "#ffffff").attr("fill-opacity", 0.8); const citiesSize = Math.max(rn(8 - regionsInput.value / 20), 3); burgLabels.select("#cities").attr("fill", "#3e3e4b").attr("opacity", 1).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", citiesSize).attr("data-size", citiesSize); - burgIcons.select("#cities").attr("opacity", 1).attr("size", 1).attr("stroke-width", .24).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("fill-opacity", .7).attr("stroke-dasharray", "").attr("stroke-linecap", "butt"); + burgIcons.select("#cities").attr("opacity", 1).attr("size", 1).attr("stroke-width", 0.24).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("fill-opacity", 0.7).attr("stroke-dasharray", "").attr("stroke-linecap", "butt"); anchors.select("#cities").attr("opacity", 1).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("stroke-width", 1.2).attr("size", 2); burgLabels.select("#towns").attr("fill", "#3e3e4b").attr("opacity", 1).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 3).attr("data-size", 4); - burgIcons.select("#towns").attr("opacity", 1).attr("size", .5).attr("stroke-width", .12).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("fill-opacity", .7).attr("stroke-dasharray", "").attr("stroke-linecap", "butt"); + burgIcons.select("#towns").attr("opacity", 1).attr("size", 0.5).attr("stroke-width", 0.12).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("fill-opacity", 0.7).attr("stroke-dasharray", "").attr("stroke-linecap", "butt"); anchors.select("#towns").attr("opacity", 1).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("stroke-width", 1.2).attr("size", 1); const stateLabelSize = Math.max(rn(24 - regionsInput.value / 6), 6); labels.select("#states").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", stateLabelSize).attr("data-size", stateLabelSize).attr("filter", null); labels.select("#addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null); - fogging.attr("opacity", .98).attr("fill", "#30426f"); - emblems.attr("opacity", .9).attr("stroke-width", 1).attr("filter", null); + fogging.attr("opacity", 0.98).attr("fill", "#30426f"); + emblems.attr("opacity", 0.9).attr("stroke-width", 1).attr("filter", null); } // apply style settings in JSON @@ -814,11 +842,17 @@ function applyStyle(style) { // change current style preset to another saved one function changeStylePreset(preset) { - if (customization) {tip("Please exit the customization mode first", false, "error"); return;} + if (customization) { + tip("Please exit the customization mode first", false, "error"); + return; + } alertMessage.innerHTML = "Are you sure you want to change the style preset? All unsaved style changes will be lost"; - $("#alert").dialog({resizable: false, title: "Change style preset", width: "23em", + $("#alert").dialog({ + resizable: false, + title: "Change style preset", + width: "23em", buttons: { - Change: function() { + Change: function () { const customPreset = localStorage.getItem(preset); if (customPreset) { if (JSON.isValid(customPreset)) applyStyle(JSON.parse(customPreset)); @@ -837,7 +871,7 @@ function changeStylePreset(preset) { stylePreset.dataset.old = stylePreset.value; // save current value $(this).dialog("close"); }, - Cancel: function() { + Cancel: function () { stylePreset.value = stylePreset.dataset.old; $(this).dialog("close"); } @@ -847,23 +881,35 @@ function changeStylePreset(preset) { function updateElements() { // burgIcons to desired size - burgIcons.selectAll("g").each(function(d) { + burgIcons.selectAll("g").each(function (d) { const size = +this.getAttribute("size"); - d3.select(this).selectAll("circle").each(function() {this.setAttribute("r", size)}); - burgLabels.select("g#"+this.id).selectAll("text").each(function() {this.setAttribute("dy", `${size * -1.5}px`)}); + d3.select(this) + .selectAll("circle") + .each(function () { + this.setAttribute("r", size); + }); + burgLabels + .select("g#" + this.id) + .selectAll("text") + .each(function () { + this.setAttribute("dy", `${size * -1.5}px`); + }); }); // anchor icons to desired size - anchors.selectAll("g").each(function(d) { + anchors.selectAll("g").each(function (d) { const size = +this.getAttribute("size"); - d3.select(this).selectAll("use").each(function() { - const id = +this.dataset.id; - const x = pack.burgs[id].x, y = pack.burgs[id].y; - this.setAttribute("x", rn(x - size * .47, 2)); - this.setAttribute("y", rn(y- size * .47, 2)); - this.setAttribute("width", size); - this.setAttribute("height", size); - }); + d3.select(this) + .selectAll("use") + .each(function () { + const id = +this.dataset.id; + const x = pack.burgs[id].x, + y = pack.burgs[id].y; + this.setAttribute("x", rn(x - size * 0.47, 2)); + this.setAttribute("y", rn(y - size * 0.47, 2)); + this.setAttribute("width", size); + this.setAttribute("height", size); + }); }); // redraw elements @@ -876,7 +922,8 @@ function updateElements() { function addStylePreset() { $("#styleSaver").dialog({ - title: "Style Saver", width: "26em", + title: "Style Saver", + width: "26em", position: {my: "center", at: "center", of: "svg"} }); @@ -891,71 +938,74 @@ function addStylePreset() { document.getElementById("styleSaverSave").addEventListener("click", saveStyle); document.getElementById("styleSaverDownload").addEventListener("click", styleDownload); document.getElementById("styleSaverLoad").addEventListener("click", () => styleToLoad.click()); - document.getElementById("styleToLoad").addEventListener("change", function() {uploadFile(this, styleUpload)}); + document.getElementById("styleToLoad").addEventListener("change", function () { + uploadFile(this, styleUpload); + }); function getStyle() { - const style = {}, attributes = { - "#map":["background-color", "filter", "data-filter"], - "#armies":["font-size","box-size","stroke","stroke-width","fill-opacity","filter"], - "#biomes":["opacity", "filter", "mask"], - "#stateBorders":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], - "#provinceBorders":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], - "#cells":["opacity", "stroke", "stroke-width", "filter", "mask"], - "#gridOverlay":["opacity", "scale", "dx", "dy", "type", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "transform", "filter", "mask"], - "#coordinates":["opacity", "data-size", "font-size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], - "#compass":["opacity", "transform", "filter", "mask", "shape-rendering"], - "#rose":["transform"], - "#relig":["opacity", "stroke", "stroke-width", "filter"], - "#cults":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], - "#landmass":["opacity", "fill", "filter"], - "#markers":["opacity", "rescale", "filter"], - "#prec":["opacity", "stroke", "stroke-width", "fill", "filter"], - "#population":["opacity", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], - "#rural":["stroke"], - "#urban":["stroke"], - "#freshwater":["opacity", "fill", "stroke", "stroke-width", "filter"], - "#salt":["opacity", "fill", "stroke", "stroke-width", "filter"], - "#sinkhole":["opacity", "fill", "stroke", "stroke-width", "filter"], - "#frozen":["opacity", "fill", "stroke", "stroke-width", "filter"], - "#lava":["opacity", "fill", "stroke", "stroke-width", "filter"], - "#dry":["opacity", "fill", "stroke", "stroke-width", "filter"], - "#sea_island":["opacity", "stroke", "stroke-width", "filter", "auto-filter"], - "#lake_island":["opacity", "stroke", "stroke-width", "filter"], - "#terrain":["opacity", "set", "size", "density", "filter", "mask"], - "#rivers":["opacity", "filter", "fill"], - "#ruler":["opacity", "filter"], - "#roads":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], - "#trails":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], - "#searoutes":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], - "#regions":["opacity", "filter"], - "#statesHalo":["opacity", "data-width", "stroke-width"], - "#provs":["opacity", "fill", "font-size", "data-font", "font-family", "filter"], - "#temperature":["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], - "#ice":["opacity", "fill", "stroke", "stroke-width", "filter"], - "#emblems":["opacity", "stroke-width", "filter"], - "#texture":["opacity", "filter", "mask"], - "#textureImage":["x", "y"], - "#zones":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], - "#oceanLayers":["filter", "layers"], - "#oceanBase":["fill"], - "#oceanPattern":["opacity"], - "#oceanicPattern":["href"], - "#terrs":["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"], - "#legend":["data-size", "font-size", "data-font", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"], - "#legendBox":["fill", "fill-opacity"], - "#burgLabels > #cities":["opacity", "fill", "data-size", "font-size", "data-font", "font-family"], - "#burgIcons > #cities":["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"], - "#anchors > #cities":["opacity", "fill", "size", "stroke", "stroke-width"], - "#burgLabels > #towns":["opacity", "fill", "data-size", "font-size", "data-font", "font-family"], - "#burgIcons > #towns":["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"], - "#anchors > #towns":["opacity", "fill", "size", "stroke", "stroke-width"], - "#labels > #states":["opacity", "fill", "stroke", "stroke-width", "data-size", "font-size", "data-font", "font-family", "filter"], - "#labels > #addedLabels":["opacity", "fill", "stroke", "stroke-width", "data-size", "font-size", "data-font", "font-family", "filter"], - "#fogging":["opacity", "fill", "filter"] - }; + const style = {}, + attributes = { + "#map": ["background-color", "filter", "data-filter"], + "#armies": ["font-size", "box-size", "stroke", "stroke-width", "fill-opacity", "filter"], + "#biomes": ["opacity", "filter", "mask"], + "#stateBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], + "#provinceBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], + "#cells": ["opacity", "stroke", "stroke-width", "filter", "mask"], + "#gridOverlay": ["opacity", "scale", "dx", "dy", "type", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "transform", "filter", "mask"], + "#coordinates": ["opacity", "data-size", "font-size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], + "#compass": ["opacity", "transform", "filter", "mask", "shape-rendering"], + "#rose": ["transform"], + "#relig": ["opacity", "stroke", "stroke-width", "filter"], + "#cults": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], + "#landmass": ["opacity", "fill", "filter"], + "#markers": ["opacity", "rescale", "filter"], + "#prec": ["opacity", "stroke", "stroke-width", "fill", "filter"], + "#population": ["opacity", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], + "#rural": ["stroke"], + "#urban": ["stroke"], + "#freshwater": ["opacity", "fill", "stroke", "stroke-width", "filter"], + "#salt": ["opacity", "fill", "stroke", "stroke-width", "filter"], + "#sinkhole": ["opacity", "fill", "stroke", "stroke-width", "filter"], + "#frozen": ["opacity", "fill", "stroke", "stroke-width", "filter"], + "#lava": ["opacity", "fill", "stroke", "stroke-width", "filter"], + "#dry": ["opacity", "fill", "stroke", "stroke-width", "filter"], + "#sea_island": ["opacity", "stroke", "stroke-width", "filter", "auto-filter"], + "#lake_island": ["opacity", "stroke", "stroke-width", "filter"], + "#terrain": ["opacity", "set", "size", "density", "filter", "mask"], + "#rivers": ["opacity", "filter", "fill"], + "#ruler": ["opacity", "filter"], + "#roads": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], + "#trails": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], + "#searoutes": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], + "#regions": ["opacity", "filter"], + "#statesHalo": ["opacity", "data-width", "stroke-width"], + "#provs": ["opacity", "fill", "font-size", "data-font", "font-family", "filter"], + "#temperature": ["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], + "#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"], + "#emblems": ["opacity", "stroke-width", "filter"], + "#texture": ["opacity", "filter", "mask"], + "#textureImage": ["x", "y"], + "#zones": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], + "#oceanLayers": ["filter", "layers"], + "#oceanBase": ["fill"], + "#oceanPattern": ["opacity"], + "#oceanicPattern": ["href"], + "#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"], + "#legend": ["data-size", "font-size", "data-font", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"], + "#legendBox": ["fill", "fill-opacity"], + "#burgLabels > #cities": ["opacity", "fill", "data-size", "font-size", "data-font", "font-family"], + "#burgIcons > #cities": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"], + "#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"], + "#burgLabels > #towns": ["opacity", "fill", "data-size", "font-size", "data-font", "font-family"], + "#burgIcons > #towns": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"], + "#anchors > #towns": ["opacity", "fill", "size", "stroke", "stroke-width"], + "#labels > #states": ["opacity", "fill", "stroke", "stroke-width", "data-size", "font-size", "data-font", "font-family", "filter"], + "#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "data-size", "font-size", "data-font", "font-family", "filter"], + "#fogging": ["opacity", "fill", "filter"] + }; for (const selector in attributes) { - const s = style[selector] = {}; + const s = (style[selector] = {}); attributes[selector].forEach(attr => { const el = document.querySelector(selector); if (!el) return; @@ -971,13 +1021,13 @@ function addStylePreset() { if (!isNaN(+value)) return +value; return value; } - + return style; } function checkName() { let tip = ""; - const v = "style"+styleSaverName.value; + const v = "style" + styleSaverName.value; const listed = Array.from(stylePreset.options).some(o => o.value == v); const stored = localStorage.getItem(v); if (!stored && listed) tip = "default"; @@ -987,10 +1037,22 @@ function addStylePreset() { } function saveStyle() { - if (!styleSaverJSON.value) {tip("Please provide a style JSON", false, "error"); return}; - if (!JSON.isValid(styleSaverJSON.value)) {tip("JSON string is not valid, please check the format", false, "error"); return}; - if (!styleSaverName.value) {tip("Please provide a preset name", false, "error"); return}; - if (styleSaverTip.innerHTML === "default") {tip("You cannot overwrite default preset, please change the name", false, "error"); return}; + if (!styleSaverJSON.value) { + tip("Please provide a style JSON", false, "error"); + return; + } + if (!JSON.isValid(styleSaverJSON.value)) { + tip("JSON string is not valid, please check the format", false, "error"); + return; + } + if (!styleSaverName.value) { + tip("Please provide a preset name", false, "error"); + return; + } + if (styleSaverTip.innerHTML === "default") { + tip("You cannot overwrite default preset, please change the name", false, "error"); + return; + } const preset = "style" + styleSaverName.value; applyOption(stylePreset, preset, styleSaverName.value); // add option localStorage.setItem("presetStyle", preset); // mark preset as default @@ -1001,23 +1063,41 @@ function addStylePreset() { } function styleDownload() { - if (!styleSaverJSON.value) {tip("Please provide a style JSON", false, "error"); return}; - if (!JSON.isValid(styleSaverJSON.value)) {tip("JSON string is not valid, please check the format", false, "error"); return}; - if (!styleSaverName.value) {tip("Please provide a preset name", false, "error"); return}; + if (!styleSaverJSON.value) { + tip("Please provide a style JSON", false, "error"); + return; + } + if (!JSON.isValid(styleSaverJSON.value)) { + tip("JSON string is not valid, please check the format", false, "error"); + return; + } + if (!styleSaverName.value) { + tip("Please provide a preset name", false, "error"); + return; + } const data = styleSaverJSON.value; - if (!data) {tip("Please provide a style JSON", false, "error"); return}; + if (!data) { + tip("Please provide a style JSON", false, "error"); + return; + } downloadFile(data, "style" + styleSaverName.value + ".json", "application/json"); } function styleUpload(dataLoaded) { - if (!dataLoaded) {tip("Cannot load the file. Please check the data format", false, "error"); return;} + if (!dataLoaded) { + tip("Cannot load the file. Please check the data format", false, "error"); + return; + } const data = JSON.stringify(JSON.parse(dataLoaded), null, 2); styleSaverJSON.value = data; } } function removeStylePreset() { - if (stylePreset.selectedOptions[0].dataset.system) {tip("Cannot remove system preset", false, "error"); return;}; + if (stylePreset.selectedOptions[0].dataset.system) { + tip("Cannot remove system preset", false, "error"); + return; + } localStorage.removeItem("presetStyle"); localStorage.removeItem(stylePreset.value); stylePreset.selectedOptions[0].remove(); @@ -1030,7 +1110,10 @@ function applyMapFilter(event) { if (event.target.tagName !== "BUTTON") return; const button = event.target; svg.attr("data-filter", null).attr("filter", null); - if (button.classList.contains("pressed")) {button.classList.remove("pressed"); return;} + if (button.classList.contains("pressed")) { + button.classList.remove("pressed"); + return; + } mapFilters.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed")); button.classList.add("pressed"); svg.attr("data-filter", button.id).attr("filter", "url(#filter-" + button.id + ")"); @@ -1040,7 +1123,7 @@ function updateMapFilter() { const filter = svg.attr("data-filter"); mapFilters.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed")); if (!filter) return; - mapFilters.querySelector("#"+filter).classList.add("pressed"); + mapFilters.querySelector("#" + filter).classList.add("pressed"); } // FONTS @@ -1048,11 +1131,10 @@ function updateMapFilter() { function loadDefaultFonts() { if (!$('link[href="fonts.css"]').length) { $("head").append(''); - const fontsToAdd = ["Amatic+SC:700", "IM+Fell+English", "Great+Vibes", "MedievalSharp", "Metamorphous", - "Nova+Script", "Uncial+Antiqua", "Underdog", "Caesar+Dressing", "Bitter", "Yellowtail", "Montez", - "Shadows+Into+Light", "Fredericka+the+Great", "Orbitron", "Dancing+Script:700", - "Architects+Daughter", "Kaushan+Script", "Gloria+Hallelujah", "Satisfy", "Comfortaa:700", "Cinzel"]; - fontsToAdd.forEach(function(f) {if (fonts.indexOf(f) === -1) fonts.push(f);}); + const fontsToAdd = ["Amatic+SC:700", "IM+Fell+English", "Great+Vibes", "MedievalSharp", "Metamorphous", "Nova+Script", "Uncial+Antiqua", "Underdog", "Caesar+Dressing", "Bitter", "Yellowtail", "Montez", "Shadows+Into+Light", "Fredericka+the+Great", "Orbitron", "Dancing+Script:700", "Architects+Daughter", "Kaushan+Script", "Gloria+Hallelujah", "Satisfy", "Comfortaa:700", "Cinzel"]; + fontsToAdd.forEach(function (f) { + if (fonts.indexOf(f) === -1) fonts.push(f); + }); updateFontOptions(); } } @@ -1084,7 +1166,7 @@ function fetchFonts(url) { } resolve(fetched); }); - }) + }); } function addFonts(url) { @@ -1092,38 +1174,38 @@ function addFonts(url) { return fetch(url) .then(resp => resp.text()) .then(text => { - let s = document.createElement('style'); + let s = document.createElement("style"); s.innerHTML = text; document.head.appendChild(s); - let styleSheet = Array.prototype.filter.call( - document.styleSheets, - sS => sS.ownerNode === s)[0]; + let styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0]; let FontRule = rule => { - let family = rule.style.getPropertyValue('font-family'); - let font = family.replace(/['"]+/g, '').replace(/ /g, "+"); - let weight = rule.style.getPropertyValue('font-weight'); + let family = rule.style.getPropertyValue("font-family"); + let font = family.replace(/['"]+/g, "").replace(/ /g, "+"); + let weight = rule.style.getPropertyValue("font-weight"); if (weight && weight !== "400") font += ":" + weight; if (fonts.indexOf(font) == -1) { fonts.push(font); - fetched++ + fetched++; } }; let fetched = 0; - for (let r of styleSheet.cssRules) {FontRule(r);} + for (let r of styleSheet.cssRules) { + FontRule(r); + } document.head.removeChild(s); return fetched; }) - .catch(function() {}); + .catch(function () {}); } // Update font list for Label and Burg Editors function updateFontOptions() { styleSelectFont.innerHTML = ""; - for (let i=0; i < fonts.length; i++) { - const opt = document.createElement('option'); + for (let i = 0; i < fonts.length; i++) { + const opt = document.createElement("option"); opt.value = i; - const font = fonts[i].split(':')[0].replace(/\+/g, " "); + const font = fonts[i].split(":")[0].replace(/\+/g, " "); opt.style.fontFamily = opt.innerHTML = font; styleSelectFont.add(opt); } -} \ No newline at end of file +} From ea97c9c827ec14efe803b3e9682471c62ebac426 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 12 Jun 2021 17:12:25 +0300 Subject: [PATCH 15/34] reduce lava lake propability --- modules/lakes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/lakes.js b/modules/lakes.js index 9cae7196..261fd485 100644 --- a/modules/lakes.js +++ b/modules/lakes.js @@ -132,11 +132,11 @@ function getGroup(feature) { if (feature.temp < -3) return "frozen"; - if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 5 === 0) return "lava"; + if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 10 === 0) return "lava"; if (!feature.inlets && !feature.outlet) { if (feature.evaporation / 2 > feature.flux) return "dry"; - if (feature.cells < 3 && feature.firstCell % 5 === 0) return "sinkhole"; + if (feature.cells < 3 && feature.firstCell % 10 === 0) return "sinkhole"; } if (!feature.outlet && feature.evaporation > feature.flux) return "salt"; From caa0a781e0519b1aa4d3ea13f6a0535842b4546b Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 12 Jun 2021 19:45:02 +0300 Subject: [PATCH 16/34] fix oceanicPattern opacity --- index.html | 3 +- modules/{save-and-load.js => load.js} | 563 +------------------------- modules/save.js | 559 +++++++++++++++++++++++++ modules/ui/style.js | 18 +- 4 files changed, 570 insertions(+), 573 deletions(-) rename modules/{save-and-load.js => load.js} (60%) create mode 100644 modules/save.js diff --git a/index.html b/index.html index 09f5a2c6..db3487e7 100644 --- a/index.html +++ b/index.html @@ -4124,7 +4124,8 @@ - + + diff --git a/modules/save-and-load.js b/modules/load.js similarity index 60% rename from modules/save-and-load.js rename to modules/load.js index d3353525..500c390a 100644 --- a/modules/save-and-load.js +++ b/modules/load.js @@ -1,540 +1,6 @@ // Functions to save and load the map "use strict"; -// download map as SVG -async function saveSVG() { - TIME && console.time("saveSVG"); - const url = await getMapURL("svg"); - const link = document.createElement("a"); - link.download = getFileName() + ".svg"; - link.href = url; - link.click(); - - tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000); - TIME && console.timeEnd("saveSVG"); -} - -// download map as PNG -async function savePNG() { - TIME && console.time("savePNG"); - const url = await getMapURL("png"); - - const link = document.createElement("a"); - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - canvas.width = svgWidth * pngResolutionInput.value; - canvas.height = svgHeight * pngResolutionInput.value; - const img = new Image(); - img.src = url; - - img.onload = function () { - ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - link.download = getFileName() + ".png"; - canvas.toBlob(function (blob) { - link.href = window.URL.createObjectURL(blob); - link.click(); - window.setTimeout(function () { - canvas.remove(); - window.URL.revokeObjectURL(link.href); - tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000); - }, 1000); - }); - }; - - TIME && console.timeEnd("savePNG"); -} - -// download map as JPEG -async function saveJPEG() { - TIME && console.time("saveJPEG"); - const url = await getMapURL("png"); - - const canvas = document.createElement("canvas"); - canvas.width = svgWidth * pngResolutionInput.value; - canvas.height = svgHeight * pngResolutionInput.value; - const img = new Image(); - img.src = url; - - img.onload = async function () { - canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height); - const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92); - const URL = await canvas.toDataURL("image/jpeg", quality); - const link = document.createElement("a"); - link.download = getFileName() + ".jpeg"; - link.href = URL; - link.click(); - tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000); - window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000); - }; - - TIME && console.timeEnd("saveJPEG"); -} - -// parse map svg to object url -async function getMapURL(type, subtype) { - const cloneEl = document.getElementById("map").cloneNode(true); // clone svg - cloneEl.id = "fantasyMap"; - document.body.appendChild(cloneEl); - const clone = d3.select(cloneEl); - clone.select("#debug").remove(); - - const cloneDefs = cloneEl.getElementsByTagName("defs")[0]; - const svgDefs = document.getElementById("defElements"); - - const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; - if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); - if (subtype === "globe") clone.select("#scaleBar").remove(); - if (subtype === "noWater") { - clone.select("#oceanBase").attr("opacity", 0); - clone.select("#oceanPattern").attr("opacity", 0); - } - if (type !== "png") { - // reset transform to show the whole map - clone.attr("width", graphWidth).attr("height", graphHeight); - clone.select("#viewbox").attr("transform", null); - } - if (type === "svg") removeUnusedElements(clone); - if (customization && type === "mesh") updateMeshCells(clone); - inlineStyle(clone); - - // remove unused filters - const filters = cloneEl.querySelectorAll("filter"); - for (let i = 0; i < filters.length; i++) { - const id = filters[i].id; - if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue; - if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue; - filters[i].remove(); - } - - // remove unused patterns - const patterns = cloneEl.querySelectorAll("pattern"); - for (let i = 0; i < patterns.length; i++) { - const id = patterns[i].id; - if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue; - patterns[i].remove(); - } - - // remove unused symbols - const symbols = cloneEl.querySelectorAll("symbol"); - for (let i = 0; i < symbols.length; i++) { - const id = symbols[i].id; - if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue; - symbols[i].remove(); - } - - // add displayed emblems - if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) { - cloneEl - .getElementById("emblems") - ?.querySelectorAll("use") - .forEach(el => { - const href = el.getAttribute("href") || el.getAttribute("xlink:href"); - if (!href) return; - const emblem = document.getElementById(href.slice(1)); - if (emblem) cloneDefs.append(emblem.cloneNode(true)); - }); - } else { - cloneDefs.querySelector("#defs-emblems")?.remove(); - } - - // replace ocean pattern href to base64 - if (PRODUCTION && cloneEl.getElementById("oceanicPattern")) { - const el = cloneEl.getElementById("oceanicPattern"); - const url = el.getAttribute("href"); - await new Promise(resolve => { - getBase64(url, base64 => { - el.setAttribute("href", base64); - resolve(); - }); - }); - } - - // add relief icons - if (cloneEl.getElementById("terrain")) { - const uniqueElements = new Set(); - const terrainNodes = cloneEl.getElementById("terrain").childNodes; - for (let i = 0; i < terrainNodes.length; i++) { - const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href"); - uniqueElements.add(href); - } - - const defsRelief = svgDefs.getElementById("defs-relief"); - for (const terrain of [...uniqueElements]) { - const element = defsRelief.querySelector(terrain); - if (element) cloneDefs.appendChild(element.cloneNode(true)); - } - } - - // add wind rose - if (cloneEl.getElementById("compass")) { - const rose = svgDefs.getElementById("rose"); - if (rose) cloneDefs.appendChild(rose.cloneNode(true)); - } - - // add port icon - if (cloneEl.getElementById("anchors")) { - const anchor = svgDefs.getElementById("icon-anchor"); - if (anchor) cloneDefs.appendChild(anchor.cloneNode(true)); - } - - // add grid pattern - if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) { - const type = cloneEl.getElementById("gridOverlay").getAttribute("type"); - const pattern = svgDefs.getElementById("pattern_" + type); - if (pattern) cloneDefs.appendChild(pattern.cloneNode(true)); - } - - if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); //remove unused hatching group - if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog").remove(); //remove unused fog - if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths").remove(); // removed unused statePaths - if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths").remove(); // removed unused textPaths - - // add armies style - if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", ""); - - const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts - if (fontStyle) clone.select("defs").append("style").text(fontStyle.join("\n")); // add font to style - clone.remove(); - - const serialized = `` + new XMLSerializer().serializeToString(cloneEl); - const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"}); - const url = window.URL.createObjectURL(blob); - window.setTimeout(() => window.URL.revokeObjectURL(url), 5000); - return url; -} - -// remove hidden g elements and g elements without children to make downloaded svg smaller in size -function removeUnusedElements(clone) { - if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove(); - if (markers.style("display") === "none") clone.select("#defs-markers").remove(); - - for (let empty = 1; empty; ) { - empty = 0; - clone.selectAll("g").each(function () { - if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) { - empty++; - this.remove(); - } - if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display"); - }); - } -} - -function updateMeshCells(clone) { - const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20); - const scheme = getColorScheme(); - clone.select("#heights").attr("filter", "url(#blur1)"); - clone - .select("#heights") - .selectAll("polygon") - .data(data) - .join("polygon") - .attr("points", d => getGridPolygon(d)) - .attr("id", d => "cell" + d) - .attr("stroke", d => getColor(grid.cells.h[d], scheme)); -} - -// for each g element get inline style -function inlineStyle(clone) { - const emptyG = clone.append("g").node(); - const defaultStyles = window.getComputedStyle(emptyG); - - clone.selectAll("g, #ruler *, #scaleBar > text").each(function () { - const compStyle = window.getComputedStyle(this); - let style = ""; - - for (let i = 0; i < compStyle.length; i++) { - const key = compStyle[i]; - const value = compStyle.getPropertyValue(key); - - // Firefox mask hack - if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) { - style += "mask-image: url('#land');"; - continue; - } - - if (key === "cursor") continue; // cursor should be default - if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute - if (value === defaultStyles.getPropertyValue(key)) continue; - style += key + ":" + value + ";"; - } - - for (const key in compStyle) { - const value = compStyle.getPropertyValue(key); - - if (key === "cursor") continue; // cursor should be default - if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute - if (value === defaultStyles.getPropertyValue(key)) continue; - style += key + ":" + value + ";"; - } - - if (style != "") this.setAttribute("style", style); - }); - - emptyG.remove(); -} - -// get non-standard fonts used for labels to fetch them from web -function getFontsToLoad(clone) { - const webSafe = ["Georgia", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New", "Verdana", "Arial", "Impact"]; // fonts to not fetch - - const fontsInUse = new Set(); // to store fonts currently in use - clone.selectAll("#labels > g").each(function () { - if (!this.hasChildNodes()) return; - const font = this.dataset.font; - if (!font || webSafe.includes(font)) return; - fontsInUse.add(font); - }); - const legendFont = legend.attr("data-font"); - if (legend.node().hasChildNodes() && !webSafe.includes(legendFont)) fontsInUse.add(legendFont); - const fonts = [...fontsInUse]; - return fonts.length ? "https://fonts.googleapis.com/css?family=" + fonts.join("|") : null; -} - -// code from Kaiido's answer https://stackoverflow.com/questions/42402584/how-to-use-google-fonts-in-canvas-when-drawing-dom-objects-in-svg -function GFontToDataURI(url) { - if (!url) return Promise.resolve(); - return fetch(url) // first fecth the embed stylesheet page - .then(resp => resp.text()) // we only need the text of it - .then(text => { - let s = document.createElement("style"); - s.innerHTML = text; - document.head.appendChild(s); - const styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0]; - - const FontRule = rule => { - const src = rule.style.getPropertyValue("src"); - const url = src ? src.split("url(")[1].split(")")[0] : ""; - return {rule, src, url: url.substring(url.length - 1, 1)}; - }; - const fontProms = []; - - for (const r of styleSheet.cssRules) { - let fR = FontRule(r); - if (!fR.url) continue; - - fontProms.push( - fetch(fR.url) // fetch the actual font-file (.woff) - .then(resp => resp.blob()) - .then(blob => { - return new Promise(resolve => { - let f = new FileReader(); - f.onload = e => resolve(f.result); - f.readAsDataURL(blob); - }); - }) - .then(dataURL => fR.rule.cssText.replace(fR.url, dataURL)) - ); - } - document.head.removeChild(s); // clean up - return Promise.all(fontProms); // wait for all this has been done - }); -} - -// prepare map data for saving -function getMapData() { - TIME && console.time("createMapDataBlob"); - - return new Promise(resolve => { - const date = new Date(); - 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, barSize.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 coords = JSON.stringify(mapCoordinates); - const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|"); - const notesData = JSON.stringify(notes); - const rulersString = rulers.toString(); - - // clone svg - const cloneEl = document.getElementById("map").cloneNode(true); - - // set transform values to default - cloneEl.setAttribute("width", graphWidth); - cloneEl.setAttribute("height", graphHeight); - cloneEl.querySelector("#viewbox").removeAttribute("transform"); - - // always remove rulers - cloneEl.querySelector("#ruler").innerHTML = ""; - - const svg_xml = new XMLSerializer().serializeToString(cloneEl); - - const gridGeneral = JSON.stringify({spacing: grid.spacing, cellsX: grid.cellsX, cellsY: grid.cellsY, boundary: grid.boundary, points: grid.points, features: grid.features}); - const features = JSON.stringify(pack.features); - const cultures = JSON.stringify(pack.cultures); - const states = JSON.stringify(pack.states); - const burgs = JSON.stringify(pack.burgs); - const religions = JSON.stringify(pack.religions); - const provinces = JSON.stringify(pack.provinces); - const rivers = JSON.stringify(pack.rivers); - - // store name array only if it is not the same as default - const defaultNB = Names.getNameBases(); - const namesData = nameBases - .map((b, i) => { - const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b; - return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`; - }) - .join("/"); - - // round population to save resources - const pop = Array.from(pack.cells.pop).map(p => rn(p, 4)); - - // data format as below - const data = [params, settings, coords, biomes, notesData, svg_xml, gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, features, cultures, states, burgs, pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, namesData, rivers, rulersString].join("\r\n"); - const blob = new Blob([data], {type: "text/plain"}); - - TIME && console.timeEnd("createMapDataBlob"); - resolve(blob); - }); -} - -// Download .map file -async function saveMap() { - if (customization) { - tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); - return; - } - closeDialogs("#alert"); - - const blob = await getMapData(); - const URL = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - link.download = getFileName() + ".map"; - link.href = URL; - link.click(); - tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000); - window.URL.revokeObjectURL(URL); -} - -function saveGeoJSON_Cells() { - const json = {type: "FeatureCollection", features: []}; - const cells = pack.cells; - const getPopulation = i => { - const [r, u] = getCellPopulation(i); - return rn(r + u); - }; - const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]])); - - cells.i.forEach(i => { - const coordinates = getCellCoordinates(cells.v[i]); - const height = getHeight(i); - const biome = cells.biome[i]; - const type = pack.features[cells.f[i]].type; - const population = getPopulation(i); - const state = cells.state[i]; - const province = cells.province[i]; - const culture = cells.culture[i]; - const religion = cells.religion[i]; - const neighbors = cells.c[i]; - - const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors}; - const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties}; - json.features.push(feature); - }); - - const name = getFileName("Cells") + ".geojson"; - downloadFile(JSON.stringify(json), name, "application/json"); -} - -function saveGeoJSON_Routes() { - const json = {type: "FeatureCollection", features: []}; - - routes.selectAll("g > path").each(function () { - const coordinates = getRoutePoints(this); - const id = this.id; - const type = this.parentElement.id; - - const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}}; - json.features.push(feature); - }); - - const name = getFileName("Routes") + ".geojson"; - downloadFile(JSON.stringify(json), name, "application/json"); -} - -function saveGeoJSON_Rivers() { - const json = {type: "FeatureCollection", features: []}; - - rivers.selectAll("path").each(function () { - const coordinates = getRiverPoints(this); - const id = this.id; - const width = +this.dataset.increment; - const increment = +this.dataset.increment; - const river = pack.rivers.find(r => r.i === +id.slice(5)); - const name = river ? river.name : ""; - const type = river ? river.type : ""; - const i = river ? river.i : ""; - const basin = river ? river.basin : ""; - - const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}}; - json.features.push(feature); - }); - - const name = getFileName("Rivers") + ".geojson"; - downloadFile(JSON.stringify(json), name, "application/json"); -} - -function saveGeoJSON_Markers() { - const json = {type: "FeatureCollection", features: []}; - - markers.selectAll("use").each(function () { - const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y); - const id = this.id; - const type = this.dataset.id.substring(1); - const icon = document.getElementById(type).textContent; - const note = notes.length ? notes.find(note => note.id === this.id) : null; - const name = note ? note.name : ""; - const legend = note ? note.legend : ""; - - const feature = {type: "Feature", geometry: {type: "Point", coordinates}, properties: {id, type, icon, name, legend}}; - json.features.push(feature); - }); - - const name = getFileName("Markers") + ".geojson"; - downloadFile(JSON.stringify(json), name, "application/json"); -} - -function getCellCoordinates(vertices) { - const p = pack.vertices.p; - const coordinates = vertices.map(n => getQGIScoordinates(p[n][0], p[n][1])); - return [coordinates.concat([coordinates[0]])]; -} - -function getRoutePoints(node) { - let points = []; - const l = node.getTotalLength(); - const increment = l / Math.ceil(l / 2); - for (let i = 0; i <= l; i += increment) { - const p = node.getPointAtLength(i); - points.push(getQGIScoordinates(p.x, p.y)); - } - return points; -} - -function getRiverPoints(node) { - let points = []; - const l = node.getTotalLength() / 2; // half-length - const increment = 0.25; // defines density of points - for (let i = l, c = i; i >= 0; i -= increment, c += increment) { - const p1 = node.getPointAtLength(i); - const p2 = node.getPointAtLength(c); - const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); - points.push([x, y]); - } - return points; -} - -async function quickSave() { - if (customization) { - tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); - return; - } - const blob = await getMapData(); - if (blob) ldb.set("lastMap", blob); // auto-save map - tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000); -} - function quickLoad() { ldb.get("lastMap", blob => { if (blob) { @@ -580,32 +46,6 @@ function loadMapPrompt(blob) { } } -const saveReminder = function () { - if (localStorage.getItem("noReminder")) return; - const message = ["Please don't forget to save your work as a .map file", "Please remember to save work as a .map file", "Saving in .map format will ensure your data won't be lost in case of issues", "Safety is number one priority. Please save the map", "Don't forget to save your map on a regular basis!", "Just a gentle reminder for you to save the map", "Please don't forget to save your progress (saving as .map is the best option)", "Don't want to be reminded about need to save? Press CTRL+Q"]; - - saveReminder.reminder = setInterval(() => { - if (customization) return; - tip(ra(message), true, "warn", 2500); - }, 1e6); - saveReminder.status = 1; -}; - -saveReminder(); - -function toggleSaveReminder() { - if (saveReminder.status) { - tip("Save reminder is turned off. Press CTRL+Q again to re-initiate", true, "warn", 2000); - clearInterval(saveReminder.reminder); - localStorage.setItem("noReminder", true); - saveReminder.status = 0; - } else { - tip("Save reminder is turned on. Press CTRL+Q to turn off", true, "warn", 2000); - localStorage.removeItem("noReminder"); - saveReminder(); - } -} - function uploadMap(file, callback) { uploadMap.timeStart = performance.now(); @@ -1232,8 +672,7 @@ function parseLoadedData(data) { const pattern = document.getElementById("oceanic"); const filter = pattern.firstElementChild.getAttribute("filter"); const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : ""; - pattern.innerHTML = ``; - document.getElementById("oceanPattern").setAttribute("opacity", 0.2); + pattern.innerHTML = ``; } })(); diff --git a/modules/save.js b/modules/save.js new file mode 100644 index 00000000..82564489 --- /dev/null +++ b/modules/save.js @@ -0,0 +1,559 @@ +// Functions to save and load the map +"use strict"; + +// download map as SVG +async function saveSVG() { + TIME && console.time("saveSVG"); + const url = await getMapURL("svg"); + const link = document.createElement("a"); + link.download = getFileName() + ".svg"; + link.href = url; + link.click(); + + tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000); + TIME && console.timeEnd("saveSVG"); +} + +// download map as PNG +async function savePNG() { + TIME && console.time("savePNG"); + const url = await getMapURL("png"); + + const link = document.createElement("a"); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + canvas.width = svgWidth * pngResolutionInput.value; + canvas.height = svgHeight * pngResolutionInput.value; + const img = new Image(); + img.src = url; + + img.onload = function () { + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + link.download = getFileName() + ".png"; + canvas.toBlob(function (blob) { + link.href = window.URL.createObjectURL(blob); + link.click(); + window.setTimeout(function () { + canvas.remove(); + window.URL.revokeObjectURL(link.href); + tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000); + }, 1000); + }); + }; + + TIME && console.timeEnd("savePNG"); +} + +// download map as JPEG +async function saveJPEG() { + TIME && console.time("saveJPEG"); + const url = await getMapURL("png"); + + const canvas = document.createElement("canvas"); + canvas.width = svgWidth * pngResolutionInput.value; + canvas.height = svgHeight * pngResolutionInput.value; + const img = new Image(); + img.src = url; + + img.onload = async function () { + canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height); + const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92); + const URL = await canvas.toDataURL("image/jpeg", quality); + const link = document.createElement("a"); + link.download = getFileName() + ".jpeg"; + link.href = URL; + link.click(); + tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000); + window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000); + }; + + TIME && console.timeEnd("saveJPEG"); +} + +// parse map svg to object url +async function getMapURL(type, subtype) { + const cloneEl = document.getElementById("map").cloneNode(true); // clone svg + cloneEl.id = "fantasyMap"; + document.body.appendChild(cloneEl); + const clone = d3.select(cloneEl); + clone.select("#debug").remove(); + + const cloneDefs = cloneEl.getElementsByTagName("defs")[0]; + const svgDefs = document.getElementById("defElements"); + + const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; + if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); + if (subtype === "globe") clone.select("#scaleBar").remove(); + if (subtype === "noWater") { + clone.select("#oceanBase").attr("opacity", 0); + clone.select("#oceanPattern").attr("opacity", 0); + } + if (type !== "png") { + // reset transform to show the whole map + clone.attr("width", graphWidth).attr("height", graphHeight); + clone.select("#viewbox").attr("transform", null); + } + if (type === "svg") removeUnusedElements(clone); + if (customization && type === "mesh") updateMeshCells(clone); + inlineStyle(clone); + + // remove unused filters + const filters = cloneEl.querySelectorAll("filter"); + for (let i = 0; i < filters.length; i++) { + const id = filters[i].id; + if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue; + if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue; + filters[i].remove(); + } + + // remove unused patterns + const patterns = cloneEl.querySelectorAll("pattern"); + for (let i = 0; i < patterns.length; i++) { + const id = patterns[i].id; + if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue; + patterns[i].remove(); + } + + // remove unused symbols + const symbols = cloneEl.querySelectorAll("symbol"); + for (let i = 0; i < symbols.length; i++) { + const id = symbols[i].id; + if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue; + symbols[i].remove(); + } + + // add displayed emblems + if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) { + cloneEl + .getElementById("emblems") + ?.querySelectorAll("use") + .forEach(el => { + const href = el.getAttribute("href") || el.getAttribute("xlink:href"); + if (!href) return; + const emblem = document.getElementById(href.slice(1)); + if (emblem) cloneDefs.append(emblem.cloneNode(true)); + }); + } else { + cloneDefs.querySelector("#defs-emblems")?.remove(); + } + + // replace ocean pattern href to base64 + if (PRODUCTION && cloneEl.getElementById("oceanicPattern")) { + const el = cloneEl.getElementById("oceanicPattern"); + const url = el.getAttribute("href"); + await new Promise(resolve => { + getBase64(url, base64 => { + el.setAttribute("href", base64); + resolve(); + }); + }); + } + + // add relief icons + if (cloneEl.getElementById("terrain")) { + const uniqueElements = new Set(); + const terrainNodes = cloneEl.getElementById("terrain").childNodes; + for (let i = 0; i < terrainNodes.length; i++) { + const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href"); + uniqueElements.add(href); + } + + const defsRelief = svgDefs.getElementById("defs-relief"); + for (const terrain of [...uniqueElements]) { + const element = defsRelief.querySelector(terrain); + if (element) cloneDefs.appendChild(element.cloneNode(true)); + } + } + + // add wind rose + if (cloneEl.getElementById("compass")) { + const rose = svgDefs.getElementById("rose"); + if (rose) cloneDefs.appendChild(rose.cloneNode(true)); + } + + // add port icon + if (cloneEl.getElementById("anchors")) { + const anchor = svgDefs.getElementById("icon-anchor"); + if (anchor) cloneDefs.appendChild(anchor.cloneNode(true)); + } + + // add grid pattern + if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) { + const type = cloneEl.getElementById("gridOverlay").getAttribute("type"); + const pattern = svgDefs.getElementById("pattern_" + type); + if (pattern) cloneDefs.appendChild(pattern.cloneNode(true)); + } + + if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); //remove unused hatching group + if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog").remove(); //remove unused fog + if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths").remove(); // removed unused statePaths + if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths").remove(); // removed unused textPaths + + // add armies style + if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", ""); + + const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts + if (fontStyle) clone.select("defs").append("style").text(fontStyle.join("\n")); // add font to style + clone.remove(); + + const serialized = `` + new XMLSerializer().serializeToString(cloneEl); + const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"}); + const url = window.URL.createObjectURL(blob); + window.setTimeout(() => window.URL.revokeObjectURL(url), 5000); + return url; +} + +// remove hidden g elements and g elements without children to make downloaded svg smaller in size +function removeUnusedElements(clone) { + if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove(); + if (markers.style("display") === "none") clone.select("#defs-markers").remove(); + + for (let empty = 1; empty; ) { + empty = 0; + clone.selectAll("g").each(function () { + if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) { + empty++; + this.remove(); + } + if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display"); + }); + } +} + +function updateMeshCells(clone) { + const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20); + const scheme = getColorScheme(); + clone.select("#heights").attr("filter", "url(#blur1)"); + clone + .select("#heights") + .selectAll("polygon") + .data(data) + .join("polygon") + .attr("points", d => getGridPolygon(d)) + .attr("id", d => "cell" + d) + .attr("stroke", d => getColor(grid.cells.h[d], scheme)); +} + +// for each g element get inline style +function inlineStyle(clone) { + const emptyG = clone.append("g").node(); + const defaultStyles = window.getComputedStyle(emptyG); + + clone.selectAll("g, #ruler *, #scaleBar > text").each(function () { + const compStyle = window.getComputedStyle(this); + let style = ""; + + for (let i = 0; i < compStyle.length; i++) { + const key = compStyle[i]; + const value = compStyle.getPropertyValue(key); + + // Firefox mask hack + if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) { + style += "mask-image: url('#land');"; + continue; + } + + if (key === "cursor") continue; // cursor should be default + if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute + if (value === defaultStyles.getPropertyValue(key)) continue; + style += key + ":" + value + ";"; + } + + for (const key in compStyle) { + const value = compStyle.getPropertyValue(key); + + if (key === "cursor") continue; // cursor should be default + if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute + if (value === defaultStyles.getPropertyValue(key)) continue; + style += key + ":" + value + ";"; + } + + if (style != "") this.setAttribute("style", style); + }); + + emptyG.remove(); +} + +// get non-standard fonts used for labels to fetch them from web +function getFontsToLoad(clone) { + const webSafe = ["Georgia", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New", "Verdana", "Arial", "Impact"]; // fonts to not fetch + + const fontsInUse = new Set(); // to store fonts currently in use + clone.selectAll("#labels > g").each(function () { + if (!this.hasChildNodes()) return; + const font = this.dataset.font; + if (!font || webSafe.includes(font)) return; + fontsInUse.add(font); + }); + const legendFont = legend.attr("data-font"); + if (legend.node().hasChildNodes() && !webSafe.includes(legendFont)) fontsInUse.add(legendFont); + const fonts = [...fontsInUse]; + return fonts.length ? "https://fonts.googleapis.com/css?family=" + fonts.join("|") : null; +} + +// code from Kaiido's answer https://stackoverflow.com/questions/42402584/how-to-use-google-fonts-in-canvas-when-drawing-dom-objects-in-svg +function GFontToDataURI(url) { + if (!url) return Promise.resolve(); + return fetch(url) // first fecth the embed stylesheet page + .then(resp => resp.text()) // we only need the text of it + .then(text => { + let s = document.createElement("style"); + s.innerHTML = text; + document.head.appendChild(s); + const styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0]; + + const FontRule = rule => { + const src = rule.style.getPropertyValue("src"); + const url = src ? src.split("url(")[1].split(")")[0] : ""; + return {rule, src, url: url.substring(url.length - 1, 1)}; + }; + const fontProms = []; + + for (const r of styleSheet.cssRules) { + let fR = FontRule(r); + if (!fR.url) continue; + + fontProms.push( + fetch(fR.url) // fetch the actual font-file (.woff) + .then(resp => resp.blob()) + .then(blob => { + return new Promise(resolve => { + let f = new FileReader(); + f.onload = e => resolve(f.result); + f.readAsDataURL(blob); + }); + }) + .then(dataURL => fR.rule.cssText.replace(fR.url, dataURL)) + ); + } + document.head.removeChild(s); // clean up + return Promise.all(fontProms); // wait for all this has been done + }); +} + +// prepare map data for saving +function getMapData() { + TIME && console.time("createMapDataBlob"); + + return new Promise(resolve => { + const date = new Date(); + 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, barSize.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 coords = JSON.stringify(mapCoordinates); + const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|"); + const notesData = JSON.stringify(notes); + const rulersString = rulers.toString(); + + // clone svg + const cloneEl = document.getElementById("map").cloneNode(true); + + // set transform values to default + cloneEl.setAttribute("width", graphWidth); + cloneEl.setAttribute("height", graphHeight); + cloneEl.querySelector("#viewbox").removeAttribute("transform"); + + // always remove rulers + cloneEl.querySelector("#ruler").innerHTML = ""; + + const svg_xml = new XMLSerializer().serializeToString(cloneEl); + + const gridGeneral = JSON.stringify({spacing: grid.spacing, cellsX: grid.cellsX, cellsY: grid.cellsY, boundary: grid.boundary, points: grid.points, features: grid.features}); + const features = JSON.stringify(pack.features); + const cultures = JSON.stringify(pack.cultures); + const states = JSON.stringify(pack.states); + const burgs = JSON.stringify(pack.burgs); + const religions = JSON.stringify(pack.religions); + const provinces = JSON.stringify(pack.provinces); + const rivers = JSON.stringify(pack.rivers); + + // store name array only if it is not the same as default + const defaultNB = Names.getNameBases(); + const namesData = nameBases + .map((b, i) => { + const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b; + return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`; + }) + .join("/"); + + // round population to save resources + const pop = Array.from(pack.cells.pop).map(p => rn(p, 4)); + + // data format as below + const data = [params, settings, coords, biomes, notesData, svg_xml, gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, features, cultures, states, burgs, pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, namesData, rivers, rulersString].join("\r\n"); + const blob = new Blob([data], {type: "text/plain"}); + + TIME && console.timeEnd("createMapDataBlob"); + resolve(blob); + }); +} + +// Download .map file +async function saveMap() { + if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); + closeDialogs("#alert"); + + const blob = await getMapData(); + const URL = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.download = getFileName() + ".map"; + link.href = URL; + link.click(); + tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000); + window.URL.revokeObjectURL(URL); +} + +function saveGeoJSON_Cells() { + const json = {type: "FeatureCollection", features: []}; + const cells = pack.cells; + const getPopulation = i => { + const [r, u] = getCellPopulation(i); + return rn(r + u); + }; + const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]])); + + cells.i.forEach(i => { + const coordinates = getCellCoordinates(cells.v[i]); + const height = getHeight(i); + const biome = cells.biome[i]; + const type = pack.features[cells.f[i]].type; + const population = getPopulation(i); + const state = cells.state[i]; + const province = cells.province[i]; + const culture = cells.culture[i]; + const religion = cells.religion[i]; + const neighbors = cells.c[i]; + + const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors}; + const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties}; + json.features.push(feature); + }); + + const name = getFileName("Cells") + ".geojson"; + downloadFile(JSON.stringify(json), name, "application/json"); +} + +function saveGeoJSON_Routes() { + const json = {type: "FeatureCollection", features: []}; + + routes.selectAll("g > path").each(function () { + const coordinates = getRoutePoints(this); + const id = this.id; + const type = this.parentElement.id; + + const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}}; + json.features.push(feature); + }); + + const name = getFileName("Routes") + ".geojson"; + downloadFile(JSON.stringify(json), name, "application/json"); +} + +function saveGeoJSON_Rivers() { + const json = {type: "FeatureCollection", features: []}; + + rivers.selectAll("path").each(function () { + const coordinates = getRiverPoints(this); + const id = this.id; + const width = +this.dataset.increment; + const increment = +this.dataset.increment; + const river = pack.rivers.find(r => r.i === +id.slice(5)); + const name = river ? river.name : ""; + const type = river ? river.type : ""; + const i = river ? river.i : ""; + const basin = river ? river.basin : ""; + + const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}}; + json.features.push(feature); + }); + + const name = getFileName("Rivers") + ".geojson"; + downloadFile(JSON.stringify(json), name, "application/json"); +} + +function saveGeoJSON_Markers() { + const json = {type: "FeatureCollection", features: []}; + + markers.selectAll("use").each(function () { + const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y); + const id = this.id; + const type = this.dataset.id.substring(1); + const icon = document.getElementById(type).textContent; + const note = notes.length ? notes.find(note => note.id === this.id) : null; + const name = note ? note.name : ""; + const legend = note ? note.legend : ""; + + const feature = {type: "Feature", geometry: {type: "Point", coordinates}, properties: {id, type, icon, name, legend}}; + json.features.push(feature); + }); + + const name = getFileName("Markers") + ".geojson"; + downloadFile(JSON.stringify(json), name, "application/json"); +} + +function getCellCoordinates(vertices) { + const p = pack.vertices.p; + const coordinates = vertices.map(n => getQGIScoordinates(p[n][0], p[n][1])); + return [coordinates.concat([coordinates[0]])]; +} + +function getRoutePoints(node) { + let points = []; + const l = node.getTotalLength(); + const increment = l / Math.ceil(l / 2); + for (let i = 0; i <= l; i += increment) { + const p = node.getPointAtLength(i); + points.push(getQGIScoordinates(p.x, p.y)); + } + return points; +} + +function getRiverPoints(node) { + let points = []; + const l = node.getTotalLength() / 2; // half-length + const increment = 0.25; // defines density of points + for (let i = l, c = i; i >= 0; i -= increment, c += increment) { + const p1 = node.getPointAtLength(i); + const p2 = node.getPointAtLength(c); + const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); + points.push([x, y]); + } + return points; +} + +async function quickSave() { + if (customization) { + tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); + return; + } + const blob = await getMapData(); + if (blob) ldb.set("lastMap", blob); // auto-save map + tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000); +} + +const saveReminder = function () { + if (localStorage.getItem("noReminder")) return; + const message = ["Please don't forget to save your work as a .map file", "Please remember to save work as a .map file", "Saving in .map format will ensure your data won't be lost in case of issues", "Safety is number one priority. Please save the map", "Don't forget to save your map on a regular basis!", "Just a gentle reminder for you to save the map", "Please don't forget to save your progress (saving as .map is the best option)", "Don't want to be reminded about need to save? Press CTRL+Q"]; + + saveReminder.reminder = setInterval(() => { + if (customization) return; + tip(ra(message), true, "warn", 2500); + }, 1e6); + saveReminder.status = 1; +}; + +saveReminder(); + +function toggleSaveReminder() { + if (saveReminder.status) { + tip("Save reminder is turned off. Press CTRL+Q again to re-initiate", true, "warn", 2000); + clearInterval(saveReminder.reminder); + localStorage.setItem("noReminder", true); + saveReminder.status = 0; + } else { + tip("Save reminder is turned on. Press CTRL+Q to turn off", true, "warn", 2000); + localStorage.removeItem("noReminder"); + saveReminder(); + } +} diff --git a/modules/ui/style.js b/modules/ui/style.js index 2a298f27..2e8bb720 100644 --- a/modules/ui/style.js +++ b/modules/ui/style.js @@ -219,7 +219,7 @@ function selectStyleElement() { styleOcean.style.display = "block"; styleOceanFill.value = styleOceanFillOutput.value = oceanLayers.select("#oceanBase").attr("fill"); styleOceanPattern.value = document.getElementById("oceanicPattern")?.getAttribute("href"); - styleOceanPatternOpacity.value = styleOceanPatternOpacityOutput.value = document.getElementById("oceanPattern").getAttribute("opacity") || 1; + styleOceanPatternOpacity.value = styleOceanPatternOpacityOutput.value = document.getElementById("oceanicPattern").getAttribute("opacity") || 1; outlineLayers.value = oceanLayers.attr("layers"); } @@ -404,7 +404,7 @@ styleOceanPattern.addEventListener("change", function () { }); styleOceanPatternOpacity.addEventListener("input", function () { - document.getElementById("oceanPattern").setAttribute("opacity", this.value); + document.getElementById("oceanicPattern").setAttribute("opacity", this.value); styleOceanPatternOpacityOutput.value = this.value; }); @@ -719,10 +719,10 @@ function fetchTextureURL(url) { } const defaultStyles = { - styleAncient: `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#emblems":{"opacity":0.6,"stroke-width":0.8,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":"./images/pattern1.png"},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`, - styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":"./images/pattern3.png"},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, - styleClean: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#emblems":{"opacity":1,"stroke-width":1,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":""},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`, - styleMonochrome: `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#emblems": {"opacity": 0.5,"stroke-width": 0.5,"filter": null},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":0},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanPattern":{"opacity":0.2},"#oceanicPattern":{"href":""},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}` + styleAncient: `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#emblems":{"opacity":0.6,"stroke-width":0.8,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},,"#oceanicPattern":{"href":"./images/pattern1.png", "opacity":0.2},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`, + styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},,"#oceanicPattern":{"href":"./images/pattern3.png", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, + styleClean: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#emblems":{"opacity":1,"stroke-width":1,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},,"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`, + styleMonochrome: `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#emblems": {"opacity": 0.5,"stroke-width": 0.5,"filter": null},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},,"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}` }; // apply default or custom style settings on load @@ -800,8 +800,7 @@ function applyDefaultStyle() { svg.attr("background-color", "#000000").attr("data-filter", null).attr("filter", null); oceanLayers.select("rect").attr("fill", "#466eab"); // old color #53679f oceanLayers.attr("filter", null).attr("layers", "-6,-3,-1"); - oceanPattern.attr("opacity", 0.2); - svg.select("#oceanicPattern").attr("href", "./images/pattern1.png"); + svg.select("#oceanicPattern").attr("href", "./images/pattern1.png").attr("opacity", 0.2); // heightmap style terrs.attr("opacity", null).attr("filter", null).attr("mask", "url(#land)").attr("stroke", "none").attr("scheme", "bright").attr("terracing", 0).attr("skip", 5).attr("relax", 0).attr("curve", 0); @@ -988,8 +987,7 @@ function addStylePreset() { "#zones": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], "#oceanLayers": ["filter", "layers"], "#oceanBase": ["fill"], - "#oceanPattern": ["opacity"], - "#oceanicPattern": ["href"], + "#oceanicPattern": ["href", "opacity"], "#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"], "#legend": ["data-size", "font-size", "data-font", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"], "#legendBox": ["fill", "fill-opacity"], From 28a728ff782129c9276c938f597808385c7073fb Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 12 Jun 2021 19:52:55 +0300 Subject: [PATCH 17/34] fix styles --- modules/ui/style.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ui/style.js b/modules/ui/style.js index 2e8bb720..161636b6 100644 --- a/modules/ui/style.js +++ b/modules/ui/style.js @@ -719,10 +719,10 @@ function fetchTextureURL(url) { } const defaultStyles = { - styleAncient: `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#emblems":{"opacity":0.6,"stroke-width":0.8,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},,"#oceanicPattern":{"href":"./images/pattern1.png", "opacity":0.2},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`, - styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},,"#oceanicPattern":{"href":"./images/pattern3.png", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, - styleClean: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#emblems":{"opacity":1,"stroke-width":1,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},,"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`, - styleMonochrome: `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#emblems": {"opacity": 0.5,"stroke-width": 0.5,"filter": null},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},,"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}` + styleAncient: `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#emblems":{"opacity":0.6,"stroke-width":0.8,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanicPattern":{"href":"./images/pattern1.png", "opacity":0.2},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`, + styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanicPattern":{"href":"./images/pattern3.png", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, + styleClean: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#emblems":{"opacity":1,"stroke-width":1,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`, + styleMonochrome: `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#emblems": {"opacity": 0.5,"stroke-width": 0.5,"filter": null},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}` }; // apply default or custom style settings on load From a95f1f1187e301e7beb17c7a691bdbdc46e4803e Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 12 Jun 2021 20:04:12 +0300 Subject: [PATCH 18/34] erosion fix --- modules/ui/heightmap-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 935660ca..1b4e58bf 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -405,7 +405,7 @@ function editHeightmap() { drawStates(); drawBorders(); - if (erosion) { + if (erosionAllowed) { Rivers.specify(); Lakes.generateName(); } From 774aec283257b052395e4a7ebf3b2a7d47ac8d0e Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 13 Jun 2021 11:46:55 +0300 Subject: [PATCH 19/34] remove not hawaiian names --- modules/names-generator.js | 141 ++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/modules/names-generator.js b/modules/names-generator.js index 32367be1..d4a5e559 100644 --- a/modules/names-generator.js +++ b/modules/names-generator.js @@ -1,26 +1,26 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Names = factory()); -}(this, (function () { 'use strict'; - + typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Names = factory()); +})(this, function () { + "use strict"; let chains = []; // calculate Markov chain for a namesbase - const calculateChain = function(string) { - const chain = [], array = string.split(","); + const calculateChain = function (string) { + const chain = []; + const array = string.split(","); for (const n of array) { let name = n.trim().toLowerCase(); - const basic = !(/[^\u0000-\u007f]/.test(name)); // basic chars and English rules can be applied + const basic = !/[^\u0000-\u007f]/.test(name); // basic chars and English rules can be applied // split word into pseudo-syllables - for (let i=-1, syllable = ""; i < name.length; i += (syllable.length||1), syllable = "") { + for (let i = -1, syllable = ""; i < name.length; i += syllable.length || 1, syllable = "") { let prev = name[i] || ""; // pre-onset letter let v = 0; // 0 if no vowels in syllable - for (let c=i+1; name[c] && syllable.length < 5; c++) { - const that = name[c], next = name[c+1]; // next char + for (let c = i + 1; name[c] && syllable.length < 5; c++) { + const that = name[c], + next = name[c + 1]; // next char syllable += that; if (syllable === " " || syllable === "-") break; // syllable starts with space or hyphen if (!next || next === " " || next === "-") break; // no need to check @@ -29,7 +29,8 @@ // do not split some diphthongs if (that === "y" && next === "e") continue; // 'ye' - if (basic) { // English-like + if (basic) { + // English-like if (that === "o" && next === "o") continue; // 'oo' if (that === "e" && next === "e") continue; // 'ee' if (that === "a" && next === "e") continue; // 'ae' @@ -37,7 +38,7 @@ } if (vowel(that) === next) break; // two same vowels in a row - if (v && vowel(name[c+2])) break; // syllable has vowel and additional vowel is expected soon + if (v && vowel(name[c + 2])) break; // syllable has vowel and additional vowel is expected soon } if (chain[prev] === undefined) chain[prev] = []; @@ -46,17 +47,20 @@ } return chain; - } + }; // update chain for specific base - const updateChain = (i) => chains[i] = nameBases[i] || nameBases[i].b ? calculateChain(nameBases[i].b) : null; + const updateChain = i => (chains[i] = nameBases[i] || nameBases[i].b ? calculateChain(nameBases[i].b) : null); // update chains for all used bases - const clearChains = () => chains = []; + const clearChains = () => (chains = []); // generate name using Markov's chain - const getBase = function(base, min, max, dupl) { - if (base === undefined) {ERROR && console.error("Please define a base"); return;} + const getBase = function (base, min, max, dupl) { + if (base === undefined) { + ERROR && console.error("Please define a base"); + return; + } if (!chains[base]) updateChain(base); const data = chains[base]; @@ -70,12 +74,20 @@ if (!max) max = nameBases[base].max; if (dupl !== "") dupl = nameBases[base].d; - let v = data[""], cur = ra(v), w = ""; - for (let i=0; i < 20; i++) { - if (cur === "") { // end of word - if (w.length < min) {cur = ""; w = ""; v = data[""];} else break; + let v = data[""], + cur = ra(v), + w = ""; + for (let i = 0; i < 20; i++) { + if (cur === "") { + // end of word + if (w.length < min) { + cur = ""; + w = ""; + v = data[""]; + } else break; } else { - if (w.length + cur.length > max) { // word too long + if (w.length + cur.length > max) { + // word too long if (w.length < min) w += cur; break; } else v = data[last(cur)] || data[""]; @@ -87,23 +99,27 @@ // parse word to get a final name const l = last(w); // last letter - if (l === "'" || l === " " || l === "-") w = w.slice(0,-1); // not allow some characters at the end - const basic = !(/[^\u0000-\u007f]/.test(w)); // true if word has only basic characters + if (l === "'" || l === " " || l === "-") w = w.slice(0, -1); // not allow some characters at the end + const basic = !/[^\u0000-\u007f]/.test(w); // true if word has only basic characters - let name = [...w].reduce(function(r, c, i, d) { - if (c === d[i+1] && !dupl.includes(c)) return r; // duplication is not allowed + let name = [...w].reduce(function (r, c, i, d) { + if (c === d[i + 1] && !dupl.includes(c)) return r; // duplication is not allowed if (!r.length) return c.toUpperCase(); if (r.slice(-1) === "-" && c === " ") return r; // remove space after hyphen if (r.slice(-1) === " ") return r + c.toUpperCase(); // capitalize letter after space if (r.slice(-1) === "-") return r + c.toUpperCase(); // capitalize letter after hyphen - if (c === "a" && d[i+1] === "e") return r; // "ae" => "e" - if (basic && i+1 < d.length && !vowel(c) && !vowel(d[i-1]) && !vowel(d[i+1])) return r; // remove consonant between 2 consonants - if (i+2 < d.length && c === d[i+1] && c === d[i+2]) return r; // remove three same letters in a row + if (c === "a" && d[i + 1] === "e") return r; // "ae" => "e" + if (basic && i + 1 < d.length && !vowel(c) && !vowel(d[i - 1]) && !vowel(d[i + 1])) return r; // remove consonant between 2 consonants + if (i + 2 < d.length && c === d[i + 1] && c === d[i + 2]) return r; // remove three same letters in a row return r + c; }, ""); // join the word if any part has only 1 letter - if (name.split(" ").some(part => part.length < 2)) name = name.split(" ").map((p,i) => i ? p.toLowerCase() : p).join(""); + if (name.split(" ").some(part => part.length < 2)) + name = name + .split(" ") + .map((p, i) => (i ? p.toLowerCase() : p)) + .join(""); if (name.length < 2) { ERROR && console.error("Name is too short! Random name will be selected"); @@ -111,33 +127,40 @@ } return name; - } + }; // generate name for culture - const getCulture = function(culture, min, max, dupl) { - if (culture === undefined) {ERROR && console.error("Please define a culture"); return;} + const getCulture = function (culture, min, max, dupl) { + if (culture === undefined) { + ERROR && console.error("Please define a culture"); + return; + } const base = pack.cultures[culture].base; return getBase(base, min, max, dupl); - } + }; // generate short name for culture - const getCultureShort = function(culture) { - if (culture === undefined) {ERROR && console.error("Please define a culture"); return;} + const getCultureShort = function (culture) { + if (culture === undefined) { + ERROR && console.error("Please define a culture"); + return; + } return getBaseShort(pack.cultures[culture].base); - } + }; // generate short name for base - const getBaseShort = function(base) { + const getBaseShort = function (base) { if (nameBases[base] === undefined) { tip(`Namebase ${base} does not exist. Please upload custom namebases of change the base in Cultures Editor`, false, "error"); base = 1; } - const min = nameBases[base].min-1; - const max = Math.max(nameBases[base].max-2, min); + const min = nameBases[base].min - 1; + const max = Math.max(nameBases[base].max - 2, min); return getBase(base, min, max, "", 0); - } + }; // generate state name based on capital or random name and culture-specific suffix + // prettier-ignore const getState = function(name, culture, base) { if (name === undefined) {ERROR && console.error("Please define a base name"); return;} if (culture === undefined && base === undefined) {ERROR && console.error("Please define a culture"); return;} @@ -191,33 +214,37 @@ if (name.slice(-1 * suffix.length) === suffix) return name; // no suffix if name already ends with it const s1 = suffix.charAt(0); if (name.slice(-1) === s1) name = name.slice(0, -1); // remove name last letter if it's a suffix first letter - if (vowel(s1) === vowel(name.slice(-1)) && vowel(s1) === vowel(name.slice(-2,-1))) name = name.slice(0, -1); // remove name last char if 2 last chars are the same type as suffix's 1st + if (vowel(s1) === vowel(name.slice(-1)) && vowel(s1) === vowel(name.slice(-2, -1))) name = name.slice(0, -1); // remove name last char if 2 last chars are the same type as suffix's 1st if (name.slice(-1) === s1) name = name.slice(0, -1); // remove name last letter if it's a suffix first letter return name + suffix; } // generato name for the map - const getMapName = function(force) { + const getMapName = function (force) { if (!force && locked("mapName")) return; if (force && locked("mapName")) unlock("mapName"); - const base = P(.7) ? 2 : P(.5) ? rand(0, 6) : rand(0, 31); - if (!nameBases[base]) {tip("Namebase is not found", false, "error"); return ""}; - const min = nameBases[base].min-1; - const max = Math.max(nameBases[base].max-3, min); + const base = P(0.7) ? 2 : P(0.5) ? rand(0, 6) : rand(0, 31); + if (!nameBases[base]) { + tip("Namebase is not found", false, "error"); + return ""; + } + const min = nameBases[base].min - 1; + const max = Math.max(nameBases[base].max - 3, min); const baseName = getBase(base, min, max, "", 0); - const name = P(.7) ? addSuffix(baseName) : baseName; + const name = P(0.7) ? addSuffix(baseName) : baseName; mapName.value = name; - } + }; function addSuffix(name) { - const suffix = P(.8) ? "ia" : "land"; - if (suffix === "ia" && name.length > 6) name = name.slice(0, -(name.length-3)); else - if (suffix === "land" && name.length > 6) name = name.slice(0, -(name.length-5)); + const suffix = P(0.8) ? "ia" : "land"; + if (suffix === "ia" && name.length > 6) name = name.slice(0, -(name.length - 3)); + else if (suffix === "land" && name.length > 6) name = name.slice(0, -(name.length - 5)); return validateSuffix(name, suffix); } - const getNameBases = function() { + const getNameBases = function () { // name, min length, max length, letters to allow duplication, multi-word name rate [deprecated] + // prettier-ignore return [ // real-world bases by Azgaar: {name: "German", i: 0, min: 5, max: 12, d: "lt", m: 0, b: "Achern,Aichhalden,Aitern,Albbruck,Alpirsbach,Altensteig,Althengstett,Appenweier,Auggen,Wildbad,Badenen,Badenweiler,Baiersbronn,Ballrechten,Bellingen,Berghaupten,Bernau,Biberach,Biederbach,Binzen,Birkendorf,Birkenfeld,Bischweier,Blumberg,Bollen,Bollschweil,Bonndorf,Bosingen,Braunlingen,Breisach,Breisgau,Breitnau,Brigachtal,Buchenbach,Buggingen,Buhl,Buhlertal,Calw,Dachsberg,Dobel,Donaueschingen,Dornhan,Dornstetten,Dottingen,Dunningen,Durbach,Durrheim,Ebhausen,Ebringen,Efringen,Egenhausen,Ehrenkirchen,Ehrsberg,Eimeldingen,Eisenbach,Elzach,Elztal,Emmendingen,Endingen,Engelsbrand,Enz,Enzklosterle,Eschbronn,Ettenheim,Ettlingen,Feldberg,Fischerbach,Fischingen,Fluorn,Forbach,Freiamt,Freiburg,Freudenstadt,Friedenweiler,Friesenheim,Frohnd,Furtwangen,Gaggenau,Geisingen,Gengenbach,Gernsbach,Glatt,Glatten,Glottertal,Gorwihl,Gottenheim,Grafenhausen,Grenzach,Griesbach,Gutach,Gutenbach,Hag,Haiterbach,Hardt,Harmersbach,Hasel,Haslach,Hausach,Hausen,Hausern,Heitersheim,Herbolzheim,Herrenalb,Herrischried,Hinterzarten,Hochenschwand,Hofen,Hofstetten,Hohberg,Horb,Horben,Hornberg,Hufingen,Ibach,Ihringen,Inzlingen,Kandern,Kappel,Kappelrodeck,Karlsbad,Karlsruhe,Kehl,Keltern,Kippenheim,Kirchzarten,Konigsfeld,Krozingen,Kuppenheim,Kussaberg,Lahr,Lauchringen,Lauf,Laufenburg,Lautenbach,Lauterbach,Lenzkirch,Liebenzell,Loffenau,Loffingen,Lorrach,Lossburg,Mahlberg,Malsburg,Malsch,March,Marxzell,Marzell,Maulburg,Monchweiler,Muhlenbach,Mullheim,Munstertal,Murg,Nagold,Neubulach,Neuenburg,Neuhausen,Neuried,Neuweiler,Niedereschach,Nordrach,Oberharmersbach,Oberkirch,Oberndorf,Oberbach,Oberried,Oberwolfach,Offenburg,Ohlsbach,Oppenau,Ortenberg,otigheim,Ottenhofen,Ottersweier,Peterstal,Pfaffenweiler,Pfalzgrafenweiler,Pforzheim,Rastatt,Renchen,Rheinau,Rheinfelden,Rheinmunster,Rickenbach,Rippoldsau,Rohrdorf,Rottweil,Rummingen,Rust,Sackingen,Sasbach,Sasbachwalden,Schallbach,Schallstadt,Schapbach,Schenkenzell,Schiltach,Schliengen,Schluchsee,Schomberg,Schonach,Schonau,Schonenberg,Schonwald,Schopfheim,Schopfloch,Schramberg,Schuttertal,Schwenningen,Schworstadt,Seebach,Seelbach,Seewald,Sexau,Simmersfeld,Simonswald,Sinzheim,Solden,Staufen,Stegen,Steinach,Steinen,Steinmauern,Straubenhardt,Stuhlingen,Sulz,Sulzburg,Teinach,Tiefenbronn,Tiengen,Titisee,Todtmoos,Todtnau,Todtnauberg,Triberg,Tunau,Tuningen,uhlingen,Unterkirnach,Reichenbach,Utzenfeld,Villingen,Villingendorf,Vogtsburg,Vohrenbach,Waldachtal,Waldbronn,Waldkirch,Waldshut,Wehr,Weil,Weilheim,Weisenbach,Wembach,Wieden,Wiesental,Wildberg,Winzeln,Wittlingen,Wittnau,Wolfach,Wutach,Wutoschingen,Wyhlen,Zavelstein"}, @@ -245,7 +272,7 @@ {name: "Celtic", i: 22, min: 4, max: 12, d: "nld", m: 0, b: "Aberaman,Aberangell,Aberarth,Aberavon,Aberbanc,Aberbargoed,Aberbeeg,Abercanaid,Abercarn,Abercastle,Abercegir,Abercraf,Abercregan,Abercych,Abercynon,Aberdare,Aberdaron,Aberdaugleddau,Aberdeen,Aberdulais,Aberdyfi,Aberedw,Abereiddy,Abererch,Abereron,Aberfan,Aberffraw,Aberffrwd,Abergavenny,Abergele,Aberglasslyn,Abergorlech,Abergwaun,Abergwesyn,Abergwili,Abergwynfi,Abergwyngregyn,Abergynolwyn,Aberhafesp,Aberhonddu,Aberkenfig,Aberllefenni,Abermain,Abermaw,Abermorddu,Abermule,Abernant,Aberpennar,Aberporth,Aberriw,Abersoch,Abersychan,Abertawe,Aberteifi,Aberthin,Abertillery,Abertridwr,Aberystwyth,Achininver,Afonhafren,Alisaha,Antinbhearmor,Ardenna,Attacon,Beira,Bhrura,Boioduro,Bona,Boudobriga,Bravon,Brigant,Briganta,Briva,Cambodunum,Cambra,Caracta,Catumagos,Centobriga,Ceredigion,Chalain,Dinn,Diwa,Dubingen,Duro,Ebora,Ebruac,Eburodunum,Eccles,Eighe,Eireann,Ferkunos,Genua,Ghrainnse,Inbhear,Inbhir,Inbhirair,Innerleithen,Innerleven,Innerwick,Inver,Inveraldie,Inverallan,Inveralmond,Inveramsay,Inveran,Inveraray,Inverarnan,Inverbervie,Inverclyde,Inverell,Inveresk,Inverfarigaig,Invergarry,Invergordon,Invergowrie,Inverhaddon,Inverkeilor,Inverkeithing,Inverkeithney,Inverkip,Inverleigh,Inverleith,Inverloch,Inverlochlarig,Inverlochy,Invermay,Invermoriston,Inverness,Inveroran,Invershin,Inversnaid,Invertrossachs,Inverugie,Inveruglas,Inverurie,Kilninver,Kirkcaldy,Kirkintilloch,Krake,Latense,Leming,Lindomagos,Llanaber,Lochinver,Lugduno,Magoduro,Monmouthshire,Narann,Novioduno,Nowijonago,Octoduron,Penning,Pheofharain,Ricomago,Rossinver,Salodurum,Seguia,Sentica,Theorsa,Uige,Vitodurum,Windobona"}, {name: "Mesopotamian", i: 23, min: 4, max: 9, d: "srpl", m: .1, b: "Adab,Akkad,Akshak,Amnanum,Arbid,Arpachiyah,Arrapha,Assur,Babilim,Badtibira,Balawat,Barsip,Borsippa,Carchemish,Chagar Bazar,Chuera,Ctesiphon ,Der,Dilbat,Diniktum,Doura,Durkurigalzu,Ekallatum,Emar,Erbil,Eridu,Eshnunn,Fakhariya ,Gawra,Girsu,Hadatu,Hamoukar,Haradum,Harran,Hatra,Idu,Irisagrig,Isin,Jemdet,Kahat,Kartukulti,Khaiber,Kish ,Kisurra,Kuara,Kutha,Lagash,Larsa ,Leilan,Marad,Mardaman,Mari,Mashkan,Mumbaqat ,Nabada,Nagar,Nerebtum,Nimrud,Nineveh,Nippur,Nuzi,Qalatjarmo,Qatara,Rawda,Seleucia,Shaduppum,Shanidar,Sharrukin,Shemshara,Shibaniba,Shuruppak,Sippar,Tarbisu,Tellagrab,Tellessawwan,Tellessweyhat,Tellhassuna,Telltaya,Telul,Terqa,Thalathat,Tutub,Ubaid ,Umma,Ur,Urfa,Urkesh,Uruk,Urum,Zabalam,Zenobia"}, {name: "Iranian", i: 24, min: 5, max: 11, d: "", m: .1, b: "Abali,Abrisham,Absard,Abuzeydabad,Afus,Alavicheh,Alikosh,Amol,Anarak,Anbar,Andisheh,Anshan,Aran,Ardabil,Arderica,Ardestan,Arjomand,Asgaran,Asgharabad,Ashian,Awan,Babajan,Badrud,Bafran,Baghestan,Baghshad,Bahadoran,Baharan Shahr,Baharestan,Bakun,Bam,Baqershahr,Barzok,Bastam,Behistun,Bitistar,Bumahen,Bushehr,Chadegan,Chahardangeh,Chamgardan,Chermahin,Choghabonut,Chugan,Damaneh,Damavand,Darabgard,Daran,Dastgerd,Dehaq,Dehaqan,Dezful,Dizicheh,Dorcheh,Dowlatabad,Duruntash,Ecbatana,Eslamshahr,Estakhr,Ezhiyeh,Falavarjan,Farrokhi,Fasham,Ferdowsieh,Fereydunshahr,Ferunabad,Firuzkuh,Fuladshahr,Ganjdareh,Ganzak,Gaz,Geoy,Godin,Goldasht,Golestan,Golpayegan,Golshahr,Golshan,Gorgab,Guged,Habibabad,Hafshejan,Hajjifiruz,Hana,Harand,Hasanabad,Hasanlu,Hashtgerd,Hecatompylos,Hormirzad,Imanshahr,Isfahan,Jandaq,Javadabad,Jiroft,Jowsheqan ,Jowzdan,Kabnak,Kahriz Sang,Kahrizak,Kangavar,Karaj,Karkevand,Kashan,Kelishad,Kermanshah,Khaledabad,Khansar,Khorramabad,Khur,Khvorzuq,Kilan,Komeh,Komeshcheh,Konar,Kuhpayeh,Kul,Kushk,Lavasan,Laybid,Liyan,Lyan,Mahabad,Mahallat,Majlesi,Malard,Manzariyeh,Marlik,Meshkat,Meymeh,Miandasht,Mish,Mobarakeh,Nahavand,Nain,Najafabad,Naqshe,Narezzash,Nasimshahr,Nasirshahr,Nasrabad,Natanz,Neyasar,Nikabad,Nimvar,Nushabad,Pakdasht,Parand,Pardis,Parsa,Pasargadai,Patigrabana,Pir Bakran,Pishva,Qahderijan,Qahjaverestan,Qamsar,Qarchak,Qods,Rabat,Ray-shahr,Rezvanshahr,Rhages,Robat Karim,Rozveh,Rudehen,Sabashahr,Safadasht,Sagzi,Salehieh,Sandal,Sarvestan,Sedeh,Sefidshahr,Semirom,Semnan,Shadpurabad,Shah,Shahdad,Shahedshahr,Shahin,Shahpour,Shahr,Shahreza,Shahriar,Sharifabad,Shemshak,Shiraz,Shushan,Shushtar,Sialk,Sin,Sukhteh,Tabas,Tabriz,Takhte,Talkhuncheh,Talli,Tarq,Temukan,Tepe,Tiran,Tudeshk,Tureng,Urmia,Vahidieh,Vahrkana,Vanak,Varamin,Varnamkhast,Varzaneh,Vazvan,Yahya,Yarim,Yasuj,Zarrin Shahr,Zavareh,Zayandeh,Zazeran,Ziar,Zibashahr,Zranka"}, - {name: "Hawaiian", i: 25, min: 5, max: 10, d: "auo", m: 1, b: "Aapueo,Ahoa,Ahuakaio,Ahuakamalii,Ahuakeio,Ahupau,Aki,Alaakua,Alae,Alaeloa,Alaenui,Alamihi,Aleamai,Alena,Alio,Aupokopoko,Auwahi,Hahakea,Haiku,Halakaa,Halehaku,Halehana,Halemano,Haleu,Haliimaile,Hamakuapoko,Hamoa,Hanakaoo,Hanaulu,Hanawana,Hanehoi,Haneoo,Haou,Hikiaupea,Hoalua,Hokuula,Honohina,Honokahua,Honokala,Honokalani,Honokeana,Honokohau,Honokowai,Honolua,Honolulu,Honolulunui,Honomaele,Honomanu,Hononana,Honopou,Hoolawa,Hopenui,Hualele,Huelo,Hulaia,Ihuula,Ilikahi,Interisland,Kaalaea,Kaalelehinale,Kaapahu,Kaehoeho,Kaeleku,Kaeo,Kahakuloa,Kahalawe,Kahalawe,Kahalehili,Kahana,Kahilo,Kahuai,Kaiaula,Kailihiakoko,Kailua,Kainehe,Kakalahale,Kakanoni,Kakio,Kakiweka,Kalena,Kalenanui,Kaleoaihe,Kalepa,Kaliae,Kalialinui,Kalihi,Kalihi,Kalihi,Kalimaohe,Kaloi,Kamani,Kamaole,Kamehame,Kanahena,Kanaio,Kaniaula,Kaonoulu,Kaopa,Kapaloa,Kapaula,Kapewakua,Kapohue,Kapuaikini,Kapunakea,Kapuuomahuka,Kauau,Kauaula,Kaukuhalahala,Kaulalo,Kaulanamoa,Kauluohana,Kaumahalua,Kaumakani,Kaumanu,Kaunauhane,Kaunuahane,Kaupakulua,Kawaipapa,Kawaloa,Kawaloa,Kawalua,Kawela,Keaa,Keaalii,Keaaula,Keahua,Keahuapono,Keakuapauaela,Kealahou,Keanae,Keauhou,Kekuapawela,Kelawea,Keokea,Keopuka,Kepio,Kihapuhala,Kikoo,Kilolani,Kipapa,Koakupuna,Koali,Koananai,Koheo,Kolea,Kolokolo,Kooka,Kopili,Kou,Kualapa,Kuhiwa,Kuholilea,Kuhua,Kuia,Kuiaha,Kuikui,Kukoae,Kukohia,Kukuiaeo,Kukuioolu,Kukuipuka,Kukuiula,Kulahuhu,Kumunui,Lapakea,Lapalapaiki,Lapueo,Launiupoko,Loiloa,Lole,Lualailua,Maalo,Mahinahina,Mahulua,Maiana,Mailepai,Makaakini,Makaalae,Makaehu,Makaiwa,Makaliua,Makapipi,Makapuu,Makawao,Makila,Mala,Maluaka,Mamalu,Manawaiapiki,Manawainui,Maulili,Mehamenui,Miana,Mikimiki,Moalii,Moanui,Mohopili,Mohopilo,Mokae,Mokuia,Mokupapa,Mooiki,Mooloa,Moomuku,Muolea,Nahuakamalii,Nailiilipoko,Nakaaha,Nakalepo,Nakaohu,Nakapehu,Nakula,Napili,Niniau,Niumalu,Nuu,Ohia,Oloewa,Olowalu,Omaopio,Onau,Onouli,Opaeula,Opana,Opikoula,Paakea,Paeahu,Paehala,Paeohi,Pahoa,Paia,Pakakia,Pakala,Palauea,Palemo,Panaewa,Paniau,Papaaea,Papaanui,Papaauhau,Papahawahawa,Papaka,Papauluana,Pauku,Paunau,Pauwalu,Pauwela,Peahi,Piapia,Pohakanele,Pohoula,Polaiki,Polanui,Polapola,Polua,Poopoo,Popoiwi,Popoloa,Poponui,Poupouwela,Puaa,Puaaluu,Puahoowali,Puakea,Puako,Pualaea,Puehuehu,Puekahi,Pueokauiki,Pukaauhuhu,Pukalani,Pukuilua,Pulehu,Pulehuiki,Pulehunui,Punaluu,Puolua,Puou,Puuhaehae,Puuhaoa,Puuiki,Puuki,Puukohola,Puulani,Puumaneoneo,Puunau,Puunoa,Puuomaiai,Puuomaile,Uaoa,Uhao,Ukumehame,Ulaino,Ulumalu,Unknown,Various,Wahikuli,Waiahole,Waiakoa,Waianae,Waianu,Waiawa,Waiehu,Waieli,Waihee,Waikapu,Wailamoa,Wailaulau,Wailua,Wailuku,Wainee,Waiohole,Waiohonu,Waiohue,Waiohuli,Waiokama,Waiokila,Waiopai,Waiopua,Waipao,Waipio,Waipioiki,Waipionui,Waipouli,Wakiu,Wananalua"}, + {name: "Hawaiian", i: 25, min: 5, max: 10, d: "auo", m: 1, b: "Aapueo,Ahoa,Ahuakaio,Ahuakamalii,Ahuakeio,Ahupau,Aki,Alaakua,Alae,Alaeloa,Alaenui,Alamihi,Aleamai,Alena,Alio,Aupokopoko,Auwahi,Hahakea,Haiku,Halakaa,Halehaku,Halehana,Halemano,Haleu,Haliimaile,Hamakuapoko,Hamoa,Hanakaoo,Hanaulu,Hanawana,Hanehoi,Haneoo,Haou,Hikiaupea,Hoalua,Hokuula,Honohina,Honokahua,Honokala,Honokalani,Honokeana,Honokohau,Honokowai,Honolua,Honolulu,Honolulunui,Honomaele,Honomanu,Hononana,Honopou,Hoolawa,Hopenui,Hualele,Huelo,Hulaia,Ihuula,Ilikahi,Kaalaea,Kaalelehinale,Kaapahu,Kaehoeho,Kaeleku,Kaeo,Kahakuloa,Kahalawe,Kahalawe,Kahalehili,Kahana,Kahilo,Kahuai,Kaiaula,Kailihiakoko,Kailua,Kainehe,Kakalahale,Kakanoni,Kakio,Kakiweka,Kalena,Kalenanui,Kaleoaihe,Kalepa,Kaliae,Kalialinui,Kalihi,Kalihi,Kalihi,Kalimaohe,Kaloi,Kamani,Kamaole,Kamehame,Kanahena,Kanaio,Kaniaula,Kaonoulu,Kaopa,Kapaloa,Kapaula,Kapewakua,Kapohue,Kapuaikini,Kapunakea,Kapuuomahuka,Kauau,Kauaula,Kaukuhalahala,Kaulalo,Kaulanamoa,Kauluohana,Kaumahalua,Kaumakani,Kaumanu,Kaunauhane,Kaunuahane,Kaupakulua,Kawaipapa,Kawaloa,Kawaloa,Kawalua,Kawela,Keaa,Keaalii,Keaaula,Keahua,Keahuapono,Keakuapauaela,Kealahou,Keanae,Keauhou,Kekuapawela,Kelawea,Keokea,Keopuka,Kepio,Kihapuhala,Kikoo,Kilolani,Kipapa,Koakupuna,Koali,Koananai,Koheo,Kolea,Kolokolo,Kooka,Kopili,Kou,Kualapa,Kuhiwa,Kuholilea,Kuhua,Kuia,Kuiaha,Kuikui,Kukoae,Kukohia,Kukuiaeo,Kukuioolu,Kukuipuka,Kukuiula,Kulahuhu,Kumunui,Lapakea,Lapalapaiki,Lapueo,Launiupoko,Loiloa,Lole,Lualailua,Maalo,Mahinahina,Mahulua,Maiana,Mailepai,Makaakini,Makaalae,Makaehu,Makaiwa,Makaliua,Makapipi,Makapuu,Makawao,Makila,Mala,Maluaka,Mamalu,Manawaiapiki,Manawainui,Maulili,Mehamenui,Miana,Mikimiki,Moalii,Moanui,Mohopili,Mohopilo,Mokae,Mokuia,Mokupapa,Mooiki,Mooloa,Moomuku,Muolea,Nahuakamalii,Nailiilipoko,Nakaaha,Nakalepo,Nakaohu,Nakapehu,Nakula,Napili,Niniau,Niumalu,Nuu,Ohia,Oloewa,Olowalu,Omaopio,Onau,Onouli,Opaeula,Opana,Opikoula,Paakea,Paeahu,Paehala,Paeohi,Pahoa,Paia,Pakakia,Pakala,Palauea,Palemo,Panaewa,Paniau,Papaaea,Papaanui,Papaauhau,Papahawahawa,Papaka,Papauluana,Pauku,Paunau,Pauwalu,Pauwela,Peahi,Piapia,Pohakanele,Pohoula,Polaiki,Polanui,Polapola,Polua,Poopoo,Popoiwi,Popoloa,Poponui,Poupouwela,Puaa,Puaaluu,Puahoowali,Puakea,Puako,Pualaea,Puehuehu,Puekahi,Pueokauiki,Pukaauhuhu,Pukalani,Pukuilua,Pulehu,Pulehuiki,Pulehunui,Punaluu,Puolua,Puou,Puuhaehae,Puuhaoa,Puuiki,Puuki,Puukohola,Puulani,Puumaneoneo,Puunau,Puunoa,Puuomaiai,Puuomaile,Uaoa,Uhao,Ukumehame,Ulaino,Ulumalu,Wahikuli,Waiahole,Waiakoa,Waianae,Waianu,Waiawa,Waiehu,Waieli,Waihee,Waikapu,Wailamoa,Wailaulau,Wailua,Wailuku,Wainee,Waiohole,Waiohonu,Waiohue,Waiohuli,Waiokama,Waiokila,Waiopai,Waiopua,Waipao,Waipio,Waipioiki,Waipionui,Waipouli,Wakiu,Wananalua"}, {name: "Karnataka", i: 26, min: 5, max: 11, d: "tnl", m: 0, b: "Adityapatna,Adyar,Afzalpur,Aland,Alnavar,Alur,Ambikanagara,Anekal,Ankola,Annigeri,Arkalgud,Arsikere,Athni,Aurad,Badami,Bagalkot,Bagepalli,Bail,Bajpe,Bangalore,Bangarapet,Bankapura,Bannur,Bantval,Basavakalyan,Basavana,Belgaum,Beltangadi,Belur,Bhadravati,Bhalki,Bhatkal,Bhimarayanagudi,Bidar,Bijapur,Bilgi,Birur,Bommasandra,Byadgi,Challakere,Chamarajanagar,Channagiri,Channapatna,Channarayapatna,Chik,Chikmagalur,Chiknayakanhalli,Chikodi,Chincholi,Chintamani,Chitapur,Chitgoppa,Chitradurga,Dandeli,Dargajogihalli,Devadurga,Devanahalli,Dod,Donimalai,Gadag,Gajendragarh,Gangawati,Gauribidanur,Gokak,Gonikoppal,Gubbi,Gudibanda,Gulbarga,Guledgudda,Gundlupet,Gurmatkal,Haliyal,Hangal,Harapanahalli,Harihar,Hassan,Hatti,Haveri,Hebbagodi,Heggadadevankote,Hirekerur,Holalkere,Hole,Homnabad,Honavar,Honnali,Hoovina,Hosakote,Hosanagara,Hosdurga,Hospet,Hubli,Hukeri,Hungund,Hunsur,Ilkal,Indi,Jagalur,Jamkhandi,Jevargi,Jog,Kadigenahalli,Kadur,Kalghatgi,Kamalapuram,Kampli,Kanakapura,Karkal,Karwar,Khanapur,Kodiyal,Kolar,Kollegal,Konnur,Koppa,Koppal,Koratagere,Kotturu,Krishnarajanagara,Krishnarajasagara,Krishnarajpet,Kudchi,Kudligi,Kudremukh,Kumta,Kundapura,Kundgol,Kunigal,Kurgunta,Kushalnagar,Kushtagi,Lakshmeshwar,Lingsugur,Londa,Maddur,Madhugiri,Madikeri,Mahalingpur,Malavalli,Mallar,Malur,Mandya,Mangalore,Manvi,Molakalmuru,Mudalgi,Mudbidri,Muddebihal,Mudgal,Mudhol,Mudigere,Mulbagal,Mulgund,Mulki,Mulur,Mundargi,Mundgod,Munirabad,Mysore,Nagamangala,Nanjangud,Narasimharajapura,Naregal,Nargund,Navalgund,Nipani,Pandavapura,Pavagada,Piriyapatna,Pudu,Puttur,Rabkavi,Raichur,Ramanagaram,Ramdurg,Ranibennur,Raybag,Robertson,Ron,Sadalgi,Sagar,Sakleshpur,Saligram,Sandur,Sankeshwar,Saundatti,Savanur,Sedam,Shahabad,Shahpur,Shaktinagar,Shiggaon,Shikarpur,Shirhatti,Shorapur,Shrirangapattana,Siddapur,Sidlaghatta,Sindgi,Sindhnur,Sira,Siralkoppa,Sirsi,Siruguppa,Somvarpet,Sorab,Sringeri,Srinivaspur,Sulya,Talikota,Tarikere,Tekkalakote,Terdal,Thumbe,Tiptur,Tirthahalli,Tirumakudal,Tumkur,Turuvekere,Udupi,Vijayapura,Wadi,Yadgir,Yelandur,Yelbarga,Yellapur,Yenagudde"}, {name: "Quechua", i: 27, min: 6, max: 12, d: "l", m: 0, b: "Altomisayoq,Ancash,Andahuaylas,Apachekta,Apachita,Apu ,Apurimac,Arequipa,Atahuallpa,Atawalpa,Atico,Ayacucho,Ayllu,Cajamarca,Carhuac,Carhuacatac,Cashan,Caullaraju,Caxamalca,Cayesh,Chacchapunta,Chacraraju,Champara,Chanchan,Chekiacraju,Chinchey,Chontah,Chopicalqui,Chucuito,Chuito,Chullo,Chumpi,Chuncho,Chuquiapo,Churup,Cochapata,Cojup,Collota,Conococha,Copa,Corihuayrachina,Cusichaca,Despacho,Haika,Hanpiq,Hatun,Haywarisqa,Huaca,Hualcan,Huamanga,Huamashraju,Huancarhuas,Huandoy,Huantsan,Huarmihuanusca,Huascaran,Huaylas,Huayllabamba,Huichajanca,Huinayhuayna,Huinioch,Illiasca,Intipunku,Ishinca,Jahuacocha,Jirishanca,Juli,Jurau,Kakananpunta,Kamasqa,Karpay,Kausay,Khuya ,Kuelap,Llaca,Llactapata,Llanganuco,Llaqta,Llupachayoc,Machu,Mallku,Matarraju,Mikhuy,Milluacocha,Munay,Ocshapalca,Ollantaytambo,Pacamayo,Paccharaju,Pachacamac,Pachakamaq,Pachakuteq,Pachakuti,Pachamama  ,Paititi,Pajaten,Palcaraju,Pampa,Panaka,Paqarina,Paqo,Parap,Paria,Patallacta,Phuyupatamarca,Pisac,Pongos,Pucahirca,Pucaranra,Puscanturpa,Putaca,Qawaq ,Qayqa,Qochamoqo,Qollana,Qorihuayrachina,Qorimoqo,Quenuaracra,Queshque,Quillcayhuanca,Quillya,Quitaracsa,Quitaraju,Qusqu,Rajucolta,Rajutakanan,Rajutuna,Ranrahirca,Ranrapalca,Raria,Rasac,Rimarima,Riobamba,Runkuracay,Rurec,Sacsa,Saiwa,Sarapo,Sayacmarca,Sinakara,TamboColorado,Tamboccocha,Taripaypacha,Taulliraju,Tawantinsuyu,Taytanchis,Tiwanaku,Tocllaraju,Tsacra,Tuco,Tullparaju,Tumbes,Ulta,Uruashraju,Vallunaraju,Vilcabamba,Wacho ,Wankawillka,Wayra,Yachay,Yahuarraju,Yanamarey,Yanesha,Yerupaja"}, {name: "Swahili", i: 28, min: 4, max: 9, d: "", m: 0, b: "Abim,Adjumani,Alebtong,Amolatar,Amuria,Amuru,Apac,Arua,Arusha,Babati,Baragoi,Bombo,Budaka,Bugembe,Bugiri,Buikwe,Bukedea,Bukoba,Bukomansimbi,Bukungu,Buliisa,Bundibugyo,Bungoma,Busembatya,Bushenyi,Busia,Busia,Busolwe,Butaleja,Butambala,Butere,Buwenge,Buyende,Dadaab,Dodoma,Dokolo,Eldoret,Elegu,Emali,Embu,Entebbe,Garissa,Gede,Gulu,Handeni,Hima,Hoima,Hola,Ibanda,Iganga,Iringa,Isingiro,Isiolo,Jinja,Kaabong,Kabale,Kaberamaido,Kabuyanda,Kabwohe,Kagadi,Kahama,Kajiado,Kakamega,Kakinga,Kakira,Kakiri,Kakuma,Kalangala,Kaliro,Kalisizo,Kalongo,Kalungu,Kampala,Kamuli,Kamwenge,Kanoni,Kanungu,Kapchorwa,Kapenguria,Kasese,Kasulu,Katakwi,Kayunga,Kericho,Keroka,Kiambu,Kibaale,Kibaha,Kibingo,Kiboga,Kibwezi,Kigoma,Kihiihi,Kilifi,Kira,Kiruhura,Kiryandongo,Kisii,Kisoro,Kisumu,Kitale,Kitgum,Kitui,Koboko,Korogwe,Kotido,Kumi,Kyazanga,Kyegegwa,Kyenjojo,Kyotera,Lamu,Langata,Lindi,Lodwar,Lokichoggio,Londiani,Loyangalani,Lugazi,Lukaya,Luweero,Lwakhakha,Lwengo,Lyantonde,Machakos,Mafinga,Makambako,Makindu,Malaba,Malindi,Manafwa,Mandera,Maralal,Marsabit,Masaka,Masindi,MasindiPort,Masulita,Matugga,Mayuge,Mbale,Mbarara,Mbeya,Meru,Mitooma,Mityana,Mombasa,Morogoro,Moroto,Moshi,Moyale,Moyo,Mpanda,Mpigi,Mpondwe,Mtwara,Mubende,Mukono,Mumias,Muranga,Musoma,Mutomo,Mutukula,Mwanza,Nagongera,Nairobi,Naivasha,Nakapiripirit,Nakaseke,Nakasongola,Nakuru,Namanga,Namayingo,Namutumba,Nansana,Nanyuki,Narok,Naromoru,Nebbi,Ngora,Njeru,Njombe,Nkokonjeru,Ntungamo,Nyahururu,Nyeri,Oyam,Pader,Paidha,Pakwach,Pallisa,Rakai,Ruiru,Rukungiri,Rwimi,Sanga,Sembabule,Shimoni,Shinyanga,Singida,Sironko,Songea,Soroti,Ssabagabo,Sumbawanga,Tabora,Takaungu,Tanga,Thika,Tororo,Tunduma,Vihiga,Voi,Wajir,Wakiso,Watamu,Webuye,Wobulenzi,Wote,Wundanyi,Yumbe,Zanzibar"}, @@ -264,7 +291,7 @@ {name: "Arachnid", i: 40, min: 4, max: 10, d: "erlsk", m: 0, b: "Aaqok'ser,Acah,Aiced,Aisi,Aizachis,Allinqel,As'taq,Ashrash,Caaqtos,Caq'zux,Ceek'sax,Ceezuq,Cek'siereel,Cen'qi,Ceqru,Ceqzocer,Cezeed,Chachocaq,Charis,Chashar,Chashilieth,Checib,Chen'qal,Chernul,Cherzoq,Chezi,Chiazu,Chikoqal,Chishros,Chixhi,Chizhi,Chizoser,Chollash,Choq'sha,Chouk'rix,Cinchichail,Collul,Ecush'taid,Eenqachal,Ekiqe,El'zos,El'zur,Ellu,Eq'tur,Eqa,Eqas,Er'uria,Erikas,Ertu,Es'tase,Esrub,Evirrot,Exha,Haqsho,Heekath,Hiavheesh,Hitha,Hok'thi,Hossa,Iacid,Iciever,Ik'si,Illuq,Iri,Isicer,Isnir,Ivrid,Kaalzux,Keezut,Kheellavas,Kheizoh,Khellinqesh,Khiachod,Khika,Khinchi,Khirzur,Khivila,Khonrud,Khontid,Khosi,Khrakku,Khraqshis,Khrerrith,Khrethish'ti,Khriashus,Khrika,Khrirni,Khrocoqshesh,Klashirel,Klassa,Kleil'sha,Kliakis,Klishuth,Klith'osha,Krarnit,Kras'tex,Kreelzi,Krivas,Krotieqas,Laco,Lairta,Lais'tid,Laizuh,Lasnoth,Lekkol,Len'qeer,Leqanches,Lezad,Lhezsi,Lhilir,Lhivhath,Lhok'thu,Lialliesed,Liaraq,Liarisriq,Liceva,Lichorro,Lilla,Livorzish,Lokieqib,Nakar,Nakur,Naros,Natha,Necuk'saih,Neerhaca,Neet'er,Neezoh,Nenchiled,Nerhalneth,Nir'ih,Nizus,Noreeqo,Novalsher,On'qix,Qailloncho,Qak'sovo,Qalitho,Qartori,Qas'tor,Qasol,Qavrud,Qavud,Qazar,Qazieveq,Qazru,Qeik'thoth,Qekno,Qeqravee,Qes'tor,Qhaaviq,Qhaik'sal,Qhak'sish,Qhazsakais,Qhechorte,Qheliva,Qhenchaqes,Qherazal,Qhesoh,Qhiallud,Qhon'qos,Qhoshielleed,Qish'tur,Qisih,Qollal,Qorhoci,Qouxet,Qranchiq,Racith,Rak'zes,Ranchis,Rarhie,Rarzi,Rarzisiaq,Ras'tih,Ravosho,Recad,Rekid,Relshacash,Reqishee,Rernee,Rertachis,Rezhokketh,Reziel,Rhacish,Rhail'shel,Rhairhizse,Rhakivex,Rhaqeer,Rhartix,Rheciezsei,Rheevid,Rhel'shir,Rhetovraix,Rhevhie,Rhialzub,Rhiavekot,Rhikkos,Rhiqese,Rhiqi,Rhiqracar,Rhisned,Rhokno,Rhousnateb,Rhouvaqid,Riakeesnex,Rik'sid,Rintachal,Rir'ul,Rorrucis,Rosharhir,Rourk'u,Rouzakri,Sailiqei,Sanchiqed,Sanqad,Saqshu,Sat'ier,Sazi,Seiqas,Shieth'i,Shiqsheh,Shizha,Shrachuvo,Shranqo,Shravhos,Shravuth,Shreerhod,Shrethuh,Shriantieth,Shronqash,Shrovarhir,Shrozih,Siacaqoh,Siezosh,Silrul,Siq'sha,Sirro,Sornosi,Srachussi,Sreqrud,Srirnukaaq,Szaca,Szacih,Szaqova,Szasu,Szazhilos,Szeerrud,Szeezsad,Szeknur,Szesir,Szet'as,Szetirrar,Szezhirros,Szilshith,Szon'qol,Szornuq,Xaax'uq,Xeekke,Xosax,Yaconchi,Yacozses,Yazrer,Yeek'su,Yeeq'zox,Yeqil,Yeqroq,Yeveed,Yevied,Yicaveeh,Yirresh,Yisie,Yithik'thaih,Yorhaqshes,Zacheek'sa,Zakkasa,Zaqi,Zelraq,Zeqo,Zhaivoq,Zharuncho,Zhath'arhish,Zhavirrit,Zhazilraq,Zhazot,Zhazsachiel,Zhek'tha,Zhequ,Zhias'ted,Zhicat,Zhicur,Zhiese,Zhirhacil,Zhizri,Zhochizses,Zhorkir,Ziarih,Zirnib,Zis'teq,Zivezeh"}, {name: "Serpents", i: 41, min: 5, max: 11, d: "slrk", m: 0, b: "Aj'ha,Aj'i,Aj'tiss,Ajakess,Aksas,Aksiss,Al'en,An'jeshe,Apjige,Arkkess,Athaz,Atus,Azras,Caji,Cakrasar,Cal'arrun,Capji,Cathras,Cej'han,Ces,Cez'jenta,Cij'te,Cinash,Cizran,Coth'jus,Cothrash,Culzanek,Cunaless,Ej'tesh,Elzazash,Ergek,Eshjuk,Ethris,Gan'jas,Gapja,Gar'thituph,Gopjeguss,Gor'thesh,Gragishaph,Grar'theness,Grath'ji,Gressinas,Grolzesh,Grorjar,Grozrash,Guj'ika,Harji,Hej'hez,Herkush,Horgarrez,Illuph,Ipjar,Ithashin,Kaj'ess,Kar'kash,Kepjusha,Ki'kintus,Kissere,Koph,Kopjess,Kra'kasher,Krak,Krapjez,Krashjuless,Kraz'ji,Krirrigis,Krussin,Ma'lush,Mage,Maj'tak,Mal'a,Mapja,Mar'kash,Mar'kis,Marjin,Mas,Mathan,Men'jas,Meth'jaresh,Mij'hegak,Min'jash,Mith'jas,Monassu,Moss,Naj'hass,Najugash,Nak,Napjiph,Nar'ka,Nar'thuss,Narrusha,Nash,Nashjekez,Nataph,Nij'ass,Nij'tessiph,Nishjiss,Norkkuss,Nus,Olluruss,Or'thi,Or'thuss,Paj'a,Parkka,Pas,Pathujen,Paz'jaz,Pepjerras,Pirkkanar,Pituk,Porjunek,Pu'ke,Ragen,Ran'jess,Rargush,Razjuph,Rilzan,Riss,Rithruz,Rorgiss,Rossez,Rraj'asesh,Rraj'tass,Rrar'kess,Rrar'thuph,Rras,Rrazresh,Rrej'hish,Rrigelash,Rris,Rris,Rroksurrush,Rukrussush,Rurri,Russa,Ruth'jes,Sa'kitesh,Sar'thass,Sarjas,Sazjuzush,Ser'thez,Sezrass,Shajas,Shas,Shashja,Shass,Shetesh,Shijek,Shun'jaler,Shurjarri,Skaler,Skalla,Skallentas,Skaph,Skar'kerriz,Skath'jeruk,Sker'kalas,Skor,Skoz'ji,Sku'lu,Skuph,Skur'thur,Slalli,Slalt'har,Slelziress,Slil'ar,Sloz'jisa,Sojesh,Solle,Sorge,Sral'e,Sran'ji,Srapjess,Srar'thazur,Srash,Srath'jess,Srathrarre,Srerkkash,Srus,Sruss'tugeph,Sun,Suss'tir,Uzrash,Vargush,Vek,Vess'tu,Viph,Vult'ha,Vupjer,Vushjesash,Xagez,Xassa,Xulzessu,Zaj'tiss,Zan'jer,Zarriss,Zassegus,Zirres,Zsor,Zurjass"} ]; - } + }; return {getBase, getCulture, getCultureShort, getBaseShort, getState, updateChain, clearChains, getNameBases, getMapName, calculateChain}; -}))); +}); From 2be590ab24a74616326edae14f25d8b8e23ea4d5 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 13 Jun 2021 12:10:59 +0300 Subject: [PATCH 20/34] replace href with xlink:href in saved image --- modules/save.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/save.js b/modules/save.js index 82564489..1a2e910d 100644 --- a/modules/save.js +++ b/modules/save.js @@ -184,14 +184,23 @@ async function getMapURL(type, subtype) { if (pattern) cloneDefs.appendChild(pattern.cloneNode(true)); } - if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); //remove unused hatching group - if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog").remove(); //remove unused fog + if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); // remove unused hatching group + if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog").remove(); // remove unused fog if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths").remove(); // removed unused statePaths if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths").remove(); // removed unused textPaths // add armies style if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", ""); + // add xlink: for href to support svg1.1 + if (type === "svg") { + cloneEl.querySelectorAll("[href]").forEach(el => { + const href = el.getAttribute("href"); + el.removeAttribute("href"); + el.setAttribute("xlink:href", href); + }); + } + const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts if (fontStyle) clone.select("defs").append("style").text(fontStyle.join("\n")); // add font to style clone.remove(); From e3da664e5690d141218147870d68682f0d4dfcbf Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 13 Jun 2021 16:42:47 +0300 Subject: [PATCH 21/34] template editor: allow single value --- index.html | 4 +- modules/heightmap-generator.js | 368 ++++++++++++++++++++------------- modules/ui/heightmap-editor.js | 3 +- 3 files changed, 233 insertions(+), 142 deletions(-) diff --git a/index.html b/index.html index db3487e7..4d9f90cb 100644 --- a/index.html +++ b/index.html @@ -2322,8 +2322,8 @@
    Hill
    - y: - x: + y: + x: h: n: diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js index d9943819..8e0dc42a 100644 --- a/modules/heightmap-generator.js +++ b/modules/heightmap-generator.js @@ -1,49 +1,72 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.HeightmapGenerator = factory()); -}(this, (function () { 'use strict'; + typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.HeightmapGenerator = factory()); +})(this, function () { + "use strict"; let cells, p; - const generate = function() { - TIME && console.time('generateHeightmap'); - cells = grid.cells, p = grid.points; + const generate = function () { + TIME && console.time("generateHeightmap"); + cells = grid.cells; + p = grid.points; cells.h = new Uint8Array(grid.points.length); - switch (document.getElementById("templateInput").value) { - case "Volcano": templateVolcano(); break; - case "High Island": templateHighIsland(); break; - case "Low Island": templateLowIsland(); break; - case "Continents": templateContinents(); break; - case "Archipelago": templateArchipelago(); break; - case "Atoll": templateAtoll(); break; - case "Mediterranean": templateMediterranean(); break; - case "Peninsula": templatePeninsula(); break; - case "Pangea": templatePangea(); break; - case "Isthmus": templateIsthmus(); break; - case "Shattered": templateShattered(); break; + const template = document.getElementById("templateInput").value; + switch (template) { + case "Volcano": + templateVolcano(); + break; + case "High Island": + templateHighIsland(); + break; + case "Low Island": + templateLowIsland(); + break; + case "Continents": + templateContinents(); + break; + case "Archipelago": + templateArchipelago(); + break; + case "Atoll": + templateAtoll(); + break; + case "Mediterranean": + templateMediterranean(); + break; + case "Peninsula": + templatePeninsula(); + break; + case "Pangea": + templatePangea(); + break; + case "Isthmus": + templateIsthmus(); + break; + case "Shattered": + templateShattered(); + break; } - TIME && console.timeEnd('generateHeightmap'); - } + TIME && console.timeEnd("generateHeightmap"); + }; // parse template step function addStep(a1, a2, a3, a4, a5) { - if (a1 === "Hill") addHill(a2, a3, a4, a5); else - if (a1 === "Pit") addPit(a2, a3, a4, a5); else - if (a1 === "Range") addRange(a2, a3, a4, a5); else - if (a1 === "Trough") addTrough(a2, a3, a4, a5); else - if (a1 === "Strait") addStrait(a2, a3); else - if (a1 === "Add") modify(a3, a2, 1); else - if (a1 === "Multiply") modify(a3, 0, a2); else - if (a1 === "Smooth") smooth(a2); + if (a1 === "Hill") return addHill(a2, a3, a4, a5); + if (a1 === "Pit") return addPit(a2, a3, a4, a5); + if (a1 === "Range") return addRange(a2, a3, a4, a5); + if (a1 === "Trough") return addTrough(a2, a3, a4, a5); + if (a1 === "Strait") return addStrait(a2, a3); + if (a1 === "Add") return modify(a3, a2, 1); + if (a1 === "Multiply") return modify(a3, 0, a2); + if (a1 === "Smooth") return smooth(a2); } // Heighmap Template: Volcano function templateVolcano() { addStep("Hill", "1", "90-100", "44-56", "40-60"); - addStep("Multiply", .8, "50-100"); + addStep("Multiply", 0.8, "50-100"); addStep("Range", "1.5", "30-55", "45-55", "40-60"); addStep("Smooth", 2); addStep("Hill", "1.5", "25-35", "25-30", "20-75"); @@ -62,7 +85,7 @@ addStep("Trough", "2-3", "20-30", "60-80", "70-80"); addStep("Hill", "1", "10-15", "60-60", "50-50"); addStep("Hill", "1.5", "13-16", "15-20", "20-75"); - addStep("Multiply", .8, "20-100"); + addStep("Multiply", 0.8, "20-100"); addStep("Range", "1.5", "30-40", "15-85", "30-40"); addStep("Range", "1.5", "30-40", "15-85", "60-70"); addStep("Pit", "2-3", "10-15", "15-85", "20-80"); @@ -79,14 +102,14 @@ addStep("Hill", "1.5", "10-15", "5-15", "20-80"); addStep("Hill", "1", "10-15", "85-95", "70-80"); addStep("Pit", "3-5", "10-15", "15-85", "20-80"); - addStep("Multiply", .4, "20-100"); + addStep("Multiply", 0.4, "20-100"); } // Heighmap Template: Continents function templateContinents() { addStep("Hill", "1", "80-85", "75-80", "40-60"); addStep("Hill", "1", "80-85", "20-25", "40-60"); - addStep("Multiply", .22, "20-100"); + addStep("Multiply", 0.22, "20-100"); addStep("Hill", "5-6", "15-20", "25-75", "20-82"); addStep("Range", ".8", "30-60", "5-15", "20-45"); addStep("Range", ".8", "30-60", "5-15", "55-80"); @@ -118,7 +141,7 @@ addStep("Hill", "1.5", "30-50", "25-75", "30-70"); addStep("Hill", ".5", "30-50", "25-35", "30-70"); addStep("Smooth", 1); - addStep("Multiply", .2, "25-100"); + addStep("Multiply", 0.2, "25-100"); addStep("Hill", ".5", "10-20", "50-55", "48-52"); } @@ -131,7 +154,7 @@ addStep("Smooth", 1); addStep("Hill", "2-3", "30-70", "0-5", "20-80"); addStep("Hill", "2-3", "30-70", "95-100", "20-80"); - addStep("Multiply", .8, "land"); + addStep("Multiply", 0.8, "land"); addStep("Trough", "3-5", "40-50", "0-100", "0-10"); addStep("Trough", "3-5", "40-50", "0-100", "90-100"); } @@ -156,7 +179,7 @@ addStep("Hill", "1-2", "5-40", "15-50", "90-100"); addStep("Hill", "8-12", "20-40", "20-80", "48-52"); addStep("Smooth", 2); - addStep("Multiply", .7, "land"); + addStep("Multiply", 0.7, "land"); addStep("Trough", "3-4", "25-35", "5-95", "10-20"); addStep("Trough", "3-4", "25-35", "5-95", "80-90"); addStep("Range", "5-6", "30-40", "10-90", "35-65"); @@ -187,48 +210,78 @@ function getBlobPower() { switch (+pointsInput.dataset.cells) { - case 1000: return .93; - case 2000: return .95; - case 5000: return .96; - case 10000: return .98; - case 20000: return .985; - case 30000: return .987; - case 40000: return .9892; - case 50000: return .9911; - case 60000: return .9921; - case 70000: return .9934; - case 80000: return .9942; - case 90000: return .9946; - case 100000: return .995; + case 1000: + return 0.93; + case 2000: + return 0.95; + case 5000: + return 0.96; + case 10000: + return 0.98; + case 20000: + return 0.985; + case 30000: + return 0.987; + case 40000: + return 0.9892; + case 50000: + return 0.9911; + case 60000: + return 0.9921; + case 70000: + return 0.9934; + case 80000: + return 0.9942; + case 90000: + return 0.9946; + case 100000: + return 0.995; } } function getLinePower() { switch (+pointsInput.dataset.cells) { - case 1000: return .74; - case 2000: return .75; - case 5000: return .78; - case 10000: return .81; - case 20000: return .82; - case 30000: return .83; - case 40000: return .84; - case 50000: return .855; - case 60000: return .87; - case 70000: return .885; - case 80000: return .91; - case 90000: return .92; - case 100000: return .93; + case 1000: + return 0.74; + case 2000: + return 0.75; + case 5000: + return 0.78; + case 10000: + return 0.81; + case 20000: + return 0.82; + case 30000: + return 0.83; + case 40000: + return 0.84; + case 50000: + return 0.855; + case 60000: + return 0.87; + case 70000: + return 0.885; + case 80000: + return 0.91; + case 90000: + return 0.92; + case 100000: + return 0.93; } } - const addHill = function(count, height, rangeX, rangeY) { + const addHill = function (count, height, rangeX, rangeY) { count = getNumberInRange(count); const power = getBlobPower(); - while (count > 0) {addOneHill(); count--;} + while (count > 0) { + addOneHill(); + count--; + } function addOneHill() { - const change = new Uint8Array( cells.h.length); - let limit = 0, start; + const change = new Uint8Array(cells.h.length); + let limit = 0, + start; let h = lim(getNumberInRange(height)); do { @@ -236,7 +289,7 @@ const y = getPointInRange(rangeY, graphHeight); start = findGridCell(x, y); limit++; - } while (cells.h[start] + h > 90 && limit < 50) + } while (cells.h[start] + h > 90 && limit < 50); change[start] = h; const queue = [start]; @@ -245,23 +298,26 @@ for (const c of cells.c[q]) { if (change[c]) continue; - change[c] = change[q] ** power * (Math.random() * .2 + .9); + change[c] = change[q] ** power * (Math.random() * 0.2 + 0.9); if (change[c] > 1) queue.push(c); } } cells.h = cells.h.map((h, i) => lim(h + change[i])); } + }; - } - - const addPit = function(count, height, rangeX, rangeY) { + const addPit = function (count, height, rangeX, rangeY) { count = getNumberInRange(count); - while (count > 0) {addOnePit(); count--;} + while (count > 0) { + addOnePit(); + count--; + } function addOnePit() { const used = new Uint8Array(cells.h.length); - let limit = 0, start; + let limit = 0, + start; let h = lim(getNumberInRange(height)); do { @@ -269,28 +325,31 @@ const y = getPointInRange(rangeY, graphHeight); start = findGridCell(x, y); limit++; - } while (cells.h[start] < 20 && limit < 50) + } while (cells.h[start] < 20 && limit < 50); const queue = [start]; while (queue.length) { const q = queue.shift(); - h = h ** getBlobPower() * (Math.random() * .2 + .9); + h = h ** getBlobPower() * (Math.random() * 0.2 + 0.9); if (h < 1) return; - cells.c[q].forEach(function(c, i) { + cells.c[q].forEach(function (c, i) { if (used[c]) return; - cells.h[c] = lim(cells.h[c] - h * (Math.random() * .2 + .9)); + cells.h[c] = lim(cells.h[c] - h * (Math.random() * 0.2 + 0.9)); used[c] = 1; queue.push(c); }); } } - } + }; - const addRange = function(count, height, rangeX, rangeY) { + const addRange = function (count, height, rangeX, rangeY) { count = getNumberInRange(count); const power = getLinePower(); - while (count > 0) {addOneRange(); count--;} + while (count > 0) { + addOneRange(); + count--; + } function addOneRange() { const used = new Uint8Array(cells.h.length); @@ -300,13 +359,16 @@ const startX = getPointInRange(rangeX, graphWidth); const startY = getPointInRange(rangeY, graphHeight); - let dist = 0, limit = 0, endX, endY; + let dist = 0, + limit = 0, + endX, + endY; do { - endX = Math.random() * graphWidth * .8 + graphWidth * .1; - endY = Math.random() * graphHeight * .7 + graphHeight * .15; + endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1; + endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15; dist = Math.abs(endY - startY) + Math.abs(endX - startX); limit++; - } while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50) + } while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50); let range = getRange(findGridCell(startX, startY), findGridCell(endX, endY)); @@ -317,11 +379,14 @@ while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function(e) { + cells.c[cur].forEach(function (e) { if (used[e]) return; let diff = (p[end][0] - p[e][0]) ** 2 + (p[end][1] - p[e][1]) ** 2; - if (Math.random() > .85) diff = diff / 2; - if (diff < min) {min = diff; cur = e;} + if (Math.random() > 0.85) diff = diff / 2; + if (diff < min) { + min = diff; + cur = e; + } }); if (min === Infinity) return range; range.push(cur); @@ -332,25 +397,29 @@ } // add height to ridge and cells around - let queue = range.slice(), i = 0; + let queue = range.slice(), + i = 0; while (queue.length) { const frontier = queue.slice(); - queue = [], i++; + (queue = []), i++; frontier.forEach(i => { - cells.h[i] = lim(cells.h[i] + h * (Math.random() * .3 + .85)); + cells.h[i] = lim(cells.h[i] + h * (Math.random() * 0.3 + 0.85)); }); h = h ** power - 1; if (h < 2) break; frontier.forEach(f => { cells.c[f].forEach(i => { - if (!used[i]) {queue.push(i); used[i] = 1;} + if (!used[i]) { + queue.push(i); + used[i] = 1; + } }); }); } // generate prominences range.forEach((cur, d) => { - if (d%6 !== 0) return; + if (d % 6 !== 0) return; for (const l of d3.range(i)) { const min = cells.c[cur][d3.scan(cells.c[cur], (a, b) => cells.h[a] - cells.h[b])]; // downhill cell //debug.append("circle").attr("cx", p[min][0]).attr("cy", p[min][1]).attr("r", 1); @@ -358,35 +427,43 @@ cur = min; } }); - } - } + }; - const addTrough = function(count, height, rangeX, rangeY) { + const addTrough = function (count, height, rangeX, rangeY) { count = getNumberInRange(count); const power = getLinePower(); - while (count > 0) {addOneTrough(); count--;} + while (count > 0) { + addOneTrough(); + count--; + } function addOneTrough() { const used = new Uint8Array(cells.h.length); let h = lim(getNumberInRange(height)); // find start and end points - let limit = 0, startX, startY, start, dist = 0, endX, endY; + let limit = 0, + startX, + startY, + start, + dist = 0, + endX, + endY; do { startX = getPointInRange(rangeX, graphWidth); startY = getPointInRange(rangeY, graphHeight); start = findGridCell(startX, startY); limit++; - } while (cells.h[start] < 20 && limit < 50) + } while (cells.h[start] < 20 && limit < 50); limit = 0; do { - endX = Math.random() * graphWidth * .8 + graphWidth * .1; - endY = Math.random() * graphHeight * .7 + graphHeight * .15; + endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1; + endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15; dist = Math.abs(endY - startY) + Math.abs(endX - startX); limit++; - } while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50) + } while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50); let range = getRange(start, findGridCell(endX, endY)); @@ -397,11 +474,14 @@ while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function(e) { + cells.c[cur].forEach(function (e) { if (used[e]) return; let diff = (p[end][0] - p[e][0]) ** 2 + (p[end][1] - p[e][1]) ** 2; - if (Math.random() > .8) diff = diff / 2; - if (diff < min) {min = diff; cur = e;} + if (Math.random() > 0.8) diff = diff / 2; + if (diff < min) { + min = diff; + cur = e; + } }); if (min === Infinity) return range; range.push(cur); @@ -412,25 +492,29 @@ } // add height to ridge and cells around - let queue = range.slice(), i = 0; + let queue = range.slice(), + i = 0; while (queue.length) { const frontier = queue.slice(); - queue = [], i++; + (queue = []), i++; frontier.forEach(i => { - cells.h[i] = lim(cells.h[i] - h * (Math.random() * .3 + .85)); + cells.h[i] = lim(cells.h[i] - h * (Math.random() * 0.3 + 0.85)); }); h = h ** power - 1; if (h < 2) break; frontier.forEach(f => { cells.c[f].forEach(i => { - if (!used[i]) {queue.push(i); used[i] = 1;} + if (!used[i]) { + queue.push(i); + used[i] = 1; + } }); }); } // generate prominences range.forEach((cur, d) => { - if (d%6 !== 0) return; + if (d % 6 !== 0) return; for (const l of d3.range(i)) { const min = cells.c[cur][d3.scan(cells.c[cur], (a, b) => cells.h[a] - cells.h[b])]; // downhill cell //debug.append("circle").attr("cx", p[min][0]).attr("cy", p[min][1]).attr("r", 1); @@ -438,21 +522,21 @@ cur = min; } }); - } - } + }; - const addStrait = function(width, direction = "vertical") { - width = Math.min(getNumberInRange(width), grid.cellsX/3); + const addStrait = function (width, direction = "vertical") { + width = Math.min(getNumberInRange(width), grid.cellsX / 3); if (width < 1 && P(width)) return; const used = new Uint8Array(cells.h.length); const vert = direction === "vertical"; - const startX = vert ? Math.floor(Math.random() * graphWidth * .4 + graphWidth * .3) : 5; - const startY = vert ? 5 : Math.floor(Math.random() * graphHeight * .4 + graphHeight * .3); - const endX = vert ? Math.floor((graphWidth - startX) - (graphWidth * .1) + (Math.random() * graphWidth * .2)) : graphWidth - 5; - const endY = vert ? graphHeight - 5 : Math.floor((graphHeight - startY) - (graphHeight * .1) + (Math.random() * graphHeight * .2)); + const startX = vert ? Math.floor(Math.random() * graphWidth * 0.4 + graphWidth * 0.3) : 5; + const startY = vert ? 5 : Math.floor(Math.random() * graphHeight * 0.4 + graphHeight * 0.3); + const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5; + const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2); - const start = findGridCell(startX, startY), end = findGridCell(endX, endY); + const start = findGridCell(startX, startY), + end = findGridCell(endX, endY); let range = getRange(start, end); const query = []; @@ -461,10 +545,13 @@ while (cur !== end) { let min = Infinity; - cells.c[cur].forEach(function(e) { + cells.c[cur].forEach(function (e) { let diff = (p[end][0] - p[e][0]) ** 2 + (p[end][1] - p[e][1]) ** 2; if (Math.random() > 0.8) diff = diff / 2; - if (diff < min) {min = diff; cur = e;} + if (diff < min) { + min = diff; + cur = e; + } }); range.push(cur); } @@ -472,12 +559,12 @@ return range; } - const step = .1 / width; + const step = 0.1 / width; while (width > 0) { - const exp = .9 - step * width; - range.forEach(function(r) { - cells.c[r].forEach(function(e) { + const exp = 0.9 - step * width; + range.forEach(function (r) { + cells.c[r].forEach(function (e) { if (used[e]) return; used[e] = 1; query.push(e); @@ -486,39 +573,42 @@ }); }); range = query.slice(); - + width--; } - } + }; - const modify = function(range, add, mult, power) { + const modify = function (range, add, mult, power) { const min = range === "land" ? 20 : range === "all" ? 0 : +range.split("-")[0]; const max = range === "land" || range === "all" ? 100 : +range.split("-")[1]; - grid.cells.h = grid.cells.h.map(h => h >= min && h <= max ? mod(h) : h); + grid.cells.h = grid.cells.h.map(h => (h >= min && h <= max ? mod(h) : h)); function mod(v) { if (add) v = min === 20 ? Math.max(v + add, 20) : v + add; - if (mult !== 1) v = min === 20 ? (v-20) * mult + 20 : v * mult; - if (power) v = min === 20 ? (v-20) ** power + 20 : v ** power; + if (mult !== 1) v = min === 20 ? (v - 20) * mult + 20 : v * mult; + if (power) v = min === 20 ? (v - 20) ** power + 20 : v ** power; return lim(v); } - } + }; - const smooth = function(fr = 2, add = 0) { + const smooth = function (fr = 2, add = 0) { cells.h = cells.h.map((h, i) => { const a = [h]; cells.c[i].forEach(c => a.push(cells.h[c])); - return lim((h * (fr-1) + d3.mean(a) + add) / fr); + return lim((h * (fr - 1) + d3.mean(a) + add) / fr); }); - } + }; function getPointInRange(range, length) { - if (typeof range !== "string") {ERROR && console.error("Range should be a string"); return;} - const min = range.split("-")[0]/100 || 0; - const max = range.split("-")[1]/100 || 100; + if (typeof range !== "string") { + ERROR && console.error("Range should be a string"); + return; + } + + const min = range.split("-")[0] / 100 || 0; + const max = range.split("-")[1] / 100 || min; return rand(min * length, max * length); } return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify}; - -}))); +}); diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 1b4e58bf..7c8cf5b1 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -950,7 +950,8 @@ function editHeightmap() { for (const s of steps) { if (s.style.opacity == 0.5) continue; - const type = s.getAttribute("data-type"); + const type = s.dataset.type; + const elCount = s.querySelector(".templateCount") || ""; const elHeight = s.querySelector(".templateHeight") || ""; From af1d369e3171f58958a7cd3414b0a2219ebe89a0 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 13 Jun 2021 19:57:00 +0300 Subject: [PATCH 22/34] addOverseaRoute --- modules/routes-generator.js | 173 +++++++++++++++++++++--------------- 1 file changed, 99 insertions(+), 74 deletions(-) diff --git a/modules/routes-generator.js b/modules/routes-generator.js index d9d1905c..bd43ed36 100644 --- a/modules/routes-generator.js +++ b/modules/routes-generator.js @@ -1,33 +1,36 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Routes = factory()); -}(this, (function () {'use strict'; + typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Routes = factory()); +})(this, function () { + "use strict"; - const getRoads = function() { + const getRoads = function () { TIME && console.time("generateMainRoads"); - const cells = pack.cells, burgs = pack.burgs.filter(b => b.i && !b.removed); + const cells = pack.cells; + const burgs = pack.burgs.filter(b => b.i && !b.removed); const capitals = burgs.filter(b => b.capital); + if (capitals.length < 2) return []; // not enough capitals to build main roads const paths = []; // array to store path segments for (const b of capitals) { const connect = capitals.filter(c => c.i > b.i && c.feature === b.feature); if (!connect.length) continue; - const farthest = d3.scan(connect, (a, c) => ((c.y - b.y) ** 2 + (c.x - b.x) ** 2) - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2)); + const farthest = d3.scan(connect, (a, c) => (c.y - b.y) ** 2 + (c.x - b.x) ** 2 - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2)); const [from, exit] = findLandPath(b.cell, connect[farthest].cell, null); const segments = restorePath(b.cell, exit, "main", from); segments.forEach(s => paths.push(s)); } - cells.i.forEach(i => cells.s[i] += cells.road[i] / 2); // add roads to suitability score + cells.i.forEach(i => (cells.s[i] += cells.road[i] / 2)); // add roads to suitability score TIME && console.timeEnd("generateMainRoads"); return paths; - } + }; - const getTrails = function() { + const getTrails = function () { TIME && console.time("generateTrails"); - const cells = pack.cells, burgs = pack.burgs.filter(b => b.i && !b.removed); + const cells = pack.cells; + const burgs = pack.burgs.filter(b => b.i && !b.removed); + if (burgs.length < 2) return []; // not enough burgs to build trails let paths = []; // array to store path segments @@ -35,11 +38,11 @@ const isle = burgs.filter(b => b.feature === f.i); // burgs on island if (isle.length < 2) continue; - isle.forEach(function(b, i) { + isle.forEach(function (b, i) { let path = []; if (!i) { // build trail from the first burg on island to the farthest one on the same island - const farthest = d3.scan(isle, (a, c) => ((c.y - b.y) ** 2 + (c.x - b.x) ** 2) - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2)); + const farthest = d3.scan(isle, (a, c) => (c.y - b.y) ** 2 + (c.x - b.x) ** 2 - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2)); const to = isle[farthest].cell; if (cells.road[to]) return; const [from, exit] = findLandPath(b.cell, to, null); @@ -57,26 +60,31 @@ TIME && console.timeEnd("generateTrails"); return paths; - } + }; - const getSearoutes = function() { + const getSearoutes = function () { TIME && console.time("generateSearoutes"); - const allPorts = pack.burgs.filter(b => b.port > 0 && !b.removed); - if (allPorts.length < 2) return []; + const {cells, burgs, features} = pack; + const allPorts = burgs.filter(b => b.port > 0 && !b.removed); - const bodies = new Set(allPorts.map(b => b.port)); // features with ports + if (!allPorts.length) return []; + + const bodies = new Set(allPorts.map(b => b.port)); // water features with ports let paths = []; // array to store path segments const connected = []; // store cell id of connected burgs - bodies.forEach(function(f) { + bodies.forEach(f => { const ports = allPorts.filter(b => b.port === f); // all ports on the same feature - if (ports.length < 2) return; + if (!ports.length) return; - for (let s=0; s < ports.length; s++) { + if (features[f].border) addOverseaRoute(f, ports[0]); + + // get inner-map routes + for (let s = 0; s < ports.length; s++) { const source = ports[s].cell; if (connected[source]) continue; - for (let t=s+1; t < ports.length; t++) { + for (let t = s + 1; t < ports.length; t++) { const target = ports[t].cell; if (connected[target]) continue; @@ -90,61 +98,63 @@ connected[target] = 1; } } - }); + function addOverseaRoute(f, port) { + const {x, y, cell: source} = port; + const dist = p => Math.abs(p[0] - x) + Math.abs(p[1] - y); + const [x1, y1] = [ + [0, y], + [x, 0], + [graphWidth, y], + [x, graphHeight] + ].sort((a, b) => dist(a) - dist(b))[0]; + const target = findCell(x1, y1); + + if (cells.f[target] === f && cells.h[target] < 20) { + const [from, exit, passable] = findOceanPath(target, source, true); + + if (passable) { + const path = restorePath(target, exit, "ocean", from); + paths = paths.concat(path); + last(path).push([x1, y1]); + } + } + } + TIME && console.timeEnd("generateSearoutes"); return paths; - } + }; - const draw = function(main, small, ocean) { + const draw = function (main, small, water) { TIME && console.time("drawRoutes"); - const cells = pack.cells, burgs = pack.burgs; + const {cells, burgs} = pack; + const {burg, p} = cells; + + const getBurgCoords = b => [burgs[b].x, burgs[b].y]; + const getPathPoints = cells => cells.map(i => (Array.isArray(i) ? i : burg[i] ? getBurgCoords(burg[i]) : p[i])); + const getPath = segment => round(lineGen(getPathPoints(segment)), 1); + const getPathsHTML = (paths, type) => paths.map((path, i) => ``).join(""); + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); + roads.html(getPathsHTML(main, "road")); + trails.html(getPathsHTML(small, "trail")); - // main routes - roads.selectAll("path").data(main).enter().append("path") - .attr("id", (d, i) => "road" + i) - .attr("d", d => round(lineGen(d.map(c => { - const b = cells.burg[c]; - const x = b ? burgs[b].x : cells.p[c][0]; - const y = b ? burgs[b].y : cells.p[c][1]; - return [x, y]; - })), 1)); - - // small routes - trails.selectAll("path").data(small).enter().append("path") - .attr("id", (d, i) => "trail" + i) - .attr("d", d => round(lineGen(d.map(c => { - const b = cells.burg[c]; - const x = b ? burgs[b].x : cells.p[c][0]; - const y = b ? burgs[b].y : cells.p[c][1]; - return [x, y]; - })), 1)); - - // ocean routes lineGen.curve(d3.curveBundle.beta(1)); - searoutes.selectAll("path").data(ocean).enter().append("path") - .attr("id", (d, i) => "searoute" + i) - .attr("d", d => round(lineGen(d.map(c => { - const b = cells.burg[c]; - const x = b ? burgs[b].x : cells.p[c][0]; - const y = b ? burgs[b].y : cells.p[c][1]; - return [x, y]; - })), 1)); + searoutes.html(getPathsHTML(water, "searoute")); TIME && console.timeEnd("drawRoutes"); - } + }; - const regenerate = function() { + const regenerate = function () { routes.selectAll("path").remove(); pack.cells.road = new Uint16Array(pack.cells.i.length); pack.cells.crossroad = new Uint16Array(pack.cells.i.length); const main = getRoads(); const small = getTrails(); - const ocean = getSearoutes(); - draw(main, small, ocean); - } + const water = getSearoutes(); + draw(main, small, water); + }; return {getRoads, getTrails, getSearoutes, draw, regenerate}; @@ -152,11 +162,14 @@ function findLandPath(start, exit = null, toRoad = null) { const cells = pack.cells; const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); - const cost = [], from = []; + const cost = [], + from = []; queue.queue({e: start, p: 0}); while (queue.length) { - const next = queue.dequeue(), n = next.e, p = next.p; + const next = queue.dequeue(), + n = next.e, + p = next.p; if (toRoad && cells.road[n]) return [from, n]; for (const c of cells.c[n]) { @@ -175,7 +188,6 @@ cost[c] = totalCost; queue.queue({e: c, p: totalCost}); } - } return [from, exit]; } @@ -183,7 +195,9 @@ function restorePath(start, end, type, from) { const cells = pack.cells; const path = []; // to store all segments; - let segment = [], current = end, prev = end; + let segment = [], + current = end, + prev = end; const score = type === "main" ? 5 : 1; // to incrade road score at cell if (type === "ocean" || !cells.road[prev]) segment.push(end); @@ -197,8 +211,14 @@ if (segment.length) { segment.push(current); path.push(segment); - if (segment[0] !== end) {cells.road[segment[0]] += score; cells.crossroad[segment[0]] += score;} - if (current !== start) {cells.road[current] += score; cells.crossroad[current] += score;} + if (segment[0] !== end) { + cells.road[segment[0]] += score; + cells.crossroad[segment[0]] += score; + } + if (current !== start) { + cells.road[current] += score; + cells.crossroad[current] += score; + } } segment = []; prev = current; @@ -218,29 +238,34 @@ // find water paths function findOceanPath(start, exit = null, toRoute = null) { - const cells = pack.cells, temp = grid.cells.temp; + const cells = pack.cells, + temp = grid.cells.temp; const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); - const cost = [], from = []; + const cost = [], + from = []; queue.queue({e: start, p: 0}); while (queue.length) { - const next = queue.dequeue(), n = next.e, p = next.p; + const next = queue.dequeue(), + n = next.e, + p = next.p; if (toRoute && n !== start && cells.road[n]) return [from, n, true]; for (const c of cells.c[n]) { - if (c === exit) {from[c] = n; return [from, exit, true];} + if (c === exit) { + from[c] = n; + return [from, exit, true]; + } if (cells.h[c] >= 20) continue; // ignore land cells if (temp[cells.g[c]] <= -5) continue; // ignore cells with term <= -5 const dist2 = (cells.p[c][1] - cells.p[n][1]) ** 2 + (cells.p[c][0] - cells.p[n][0]) ** 2; const totalCost = p + (cells.road[c] ? 1 + dist2 / 2 : dist2 + (cells.t[c] ? 1 : 100)); if (from[c] || totalCost >= cost[c]) continue; - from[c] = n, cost[c] = totalCost; + (from[c] = n), (cost[c] = totalCost); queue.queue({e: c, p: totalCost}); } - } return [from, exit, false]; } - -}))); +}); From e9fa4cbd6c15e019459aa7c8c468bd2ce86660af Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 24 Jun 2021 22:53:27 +0300 Subject: [PATCH 23/34] download tiles --- index.css | 5 ++++ index.html | 28 +++++++++++++++++- libs/jszip.min.js | 13 +++++++++ modules/save.js | 68 ++++++++++++++++++++++++++++++++++++++++++- modules/ui/options.js | 66 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 libs/jszip.min.js diff --git a/index.css b/index.css index 771e6422..ab9cea5d 100644 --- a/index.css +++ b/index.css @@ -1513,6 +1513,11 @@ div.states > .coaIcon > use { width: 5.5em; } +#saveTilesScreen div.label { + display: inline-block; + width: 5em; +} + #regimentBody div { margin: 0.1em 0; } diff --git a/index.html b/index.html index 4d9f90cb..0d699afd 100644 --- a/index.html +++ b/index.html @@ -3389,6 +3389,7 @@ + @@ -3396,7 +3397,7 @@

    Generator uses pop-up window to download files. Please ensure your browser does not block popups.

    PNG / JPEG scale: - +
    @@ -3410,6 +3411,30 @@ + + @@ -4164,5 +4189,6 @@ + diff --git a/libs/jszip.min.js b/libs/jszip.min.js new file mode 100644 index 00000000..131b697d --- /dev/null +++ b/libs/jszip.min.js @@ -0,0 +1,13 @@ +/*! + +JSZip v3.6.0 - A JavaScript class for generating and reading zip files + + +(c) 2009-2016 Stuart Knightley +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown. + +JSZip uses the library pako released under the MIT license : +https://github.com/nodeca/pako/blob/master/LICENSE +*/ + +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,u){function h(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(f)return f(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return h(t||e)},i,i.exports,s,a,o,u)}return o[r].exports}for(var f="function"==typeof require&&require,e=0;e>2,s=(3&t)<<4|r>>4,a=1>6:64,o=2>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),h[u++]=t,64!==s&&(h[u++]=r),64!==a&&(h[u++]=n);return h}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(e){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils"),a=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r){var n=a,i=0+r;e^=-1;for(var s=0;s>>8^n[255&(e^t[s])];return-1^e}(0|t,e,e.length):function(e,t,r){var n=a,i=0+r;e^=-1;for(var s=0;s>>8^n[255&(e^t.charCodeAt(s))];return-1^e}(0|t,e,e.length):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function u(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(u,a),u.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},u.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},u.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},u.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(e){t.push({data:e,meta:t.meta})}},r.compressWorker=function(e){return new u("Deflate",e)},r.uncompressWorker=function(){return new u("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(e,t,r){"use strict";function I(e,t){var r,n="";for(r=0;r>>=8;return n}function i(e,t,r,n,i,s){var a,o,u=e.file,h=e.compression,f=s!==B.utf8encode,l=O.transformTo("string",s(u.name)),d=O.transformTo("string",B.utf8encode(u.name)),c=u.comment,p=O.transformTo("string",s(c)),m=O.transformTo("string",B.utf8encode(c)),_=d.length!==u.name.length,g=m.length!==c.length,v="",b="",w="",y=u.dir,k=u.date,x={crc32:0,compressedSize:0,uncompressedSize:0};t&&!r||(x.crc32=e.crc32,x.compressedSize=e.compressedSize,x.uncompressedSize=e.uncompressedSize);var S=0;t&&(S|=8),f||!_&&!g||(S|=2048);var z,E=0,C=0;y&&(E|=16),"UNIX"===i?(C=798,E|=((z=u.unixPermissions)||(z=y?16893:33204),(65535&z)<<16)):(C=20,E|=63&(u.dosPermissions||0)),a=k.getUTCHours(),a<<=6,a|=k.getUTCMinutes(),a<<=5,a|=k.getUTCSeconds()/2,o=k.getUTCFullYear()-1980,o<<=4,o|=k.getUTCMonth()+1,o<<=5,o|=k.getUTCDate(),_&&(v+="up"+I((b=I(1,1)+I(T(l),4)+d).length,2)+b),g&&(v+="uc"+I((w=I(1,1)+I(T(p),4)+m).length,2)+w);var A="";return A+="\n\0",A+=I(S,2),A+=h.magic,A+=I(a,2),A+=I(o,2),A+=I(x.crc32,4),A+=I(x.compressedSize,4),A+=I(x.uncompressedSize,4),A+=I(l.length,2),A+=I(v.length,2),{fileRecord:R.LOCAL_FILE_HEADER+A+l+v,dirRecord:R.CENTRAL_FILE_HEADER+I(C,2)+A+I(p.length,2)+"\0\0\0\0"+I(E,4)+I(n,4)+l+v+p}}var O=e("../utils"),s=e("../stream/GenericWorker"),B=e("../utf8"),T=e("../crc32"),R=e("../signature");function n(e,t,r,n){s.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=t,this.zipPlatform=r,this.encodeFileName=n,this.streamFiles=e,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}O.inherits(n,s),n.prototype.push=function(e){var t=e.meta.percent||0,r=this.entriesCount,n=this._sources.length;this.accumulate?this.contentBuffer.push(e):(this.bytesWritten+=e.data.length,s.prototype.push.call(this,{data:e.data,meta:{currentFile:this.currentFile,percent:r?(t+100*(r-n-1))/r:100}}))},n.prototype.openedSource=function(e){this.currentSourceOffset=this.bytesWritten,this.currentFile=e.file.name;var t=this.streamFiles&&!e.file.dir;if(t){var r=i(e,t,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},n.prototype.closedSource=function(e){this.accumulate=!1;var t,r=this.streamFiles&&!e.file.dir,n=i(e,r,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(n.dirRecord),r)this.push({data:(t=e,R.DATA_DESCRIPTOR+I(t.crc32,4)+I(t.compressedSize,4)+I(t.uncompressedSize,4)),meta:{percent:100}});else for(this.push({data:n.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},n.prototype.flush=function(){for(var e=this.bytesWritten,t=0;t=this.index;t--)r=(r<<8)+this.byteAt(t);return this.index+=e,r},readString:function(e){return n.transformTo("string",this.readData(e))},readData:function(e){},lastIndexOfSignature:function(e){},readAndCheckSignature:function(e){},readDate:function(){var e=this.readInt(4);return new Date(Date.UTC(1980+(e>>25&127),(e>>21&15)-1,e>>16&31,e>>11&31,e>>5&63,(31&e)<<1))}},t.exports=i},{"../utils":32}],19:[function(e,t,r){"use strict";var n=e("./Uint8ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data.charCodeAt(this.zero+e)},i.prototype.lastIndexOfSignature=function(e){return this.data.lastIndexOf(e)-this.zero},i.prototype.readAndCheckSignature=function(e){return e===this.readData(4)},i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],21:[function(e,t,r){"use strict";var n=e("./ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return new Uint8Array(0);var t=this.data.subarray(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./ArrayReader":17}],22:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../support"),s=e("./ArrayReader"),a=e("./StringReader"),o=e("./NodeBufferReader"),u=e("./Uint8ArrayReader");t.exports=function(e){var t=n.getTypeOf(e);return n.checkSupport(t),"string"!==t||i.uint8array?"nodebuffer"===t?new o(e):i.uint8array?new u(n.transformTo("uint8array",e)):new s(n.transformTo("array",e)):new a(e)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(e,t,r){"use strict";r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../utils");function s(e){n.call(this,"ConvertWorker to "+e),this.destType=e}i.inherits(s,n),s.prototype.processChunk=function(e){this.push({data:i.transformTo(this.destType,e.data),meta:e.meta})},t.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../crc32");function s(){n.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}e("../utils").inherits(s,n),s.prototype.processChunk=function(e){this.streamInfo.crc32=i(e.data,this.streamInfo.crc32||0),this.push(e)},t.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataLengthProbe for "+e),this.propName=e,this.withStreamInfo(e,0)}n.inherits(s,i),s.prototype.processChunk=function(e){if(e){var t=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=t+e.data.length}i.prototype.processChunk.call(this,e)},t.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataWorker");var t=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,e.then(function(e){t.dataIsReady=!0,t.data=e,t.max=e&&e.length||0,t.type=n.getTypeOf(e),t.isPaused||t._tickAndRepeat()},function(e){t.error(e)})}n.inherits(s,i),s.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this.data=null},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,n.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(n.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var e=null,t=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":e=this.data.substring(this.index,t);break;case"uint8array":e=this.data.subarray(this.index,t);break;case"array":case"nodebuffer":e=this.data.slice(this.index,t)}return this.index=t,this.push({data:e,meta:{percent:this.max?this.index/this.max*100:0}})},t.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(e,t,r){"use strict";function n(e){this.name=e||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}n.prototype={push:function(e){this.emit("data",e)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(e){this.emit("error",e)}return!0},error:function(e){return!this.isFinished&&(this.isPaused?this.generatedError=e:(this.isFinished=!0,this.emit("error",e),this.previous&&this.previous.error(e),this.cleanUp()),!0)},on:function(e,t){return this._listeners[e].push(t),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(e,t){if(this._listeners[e])for(var r=0;r "+e:e}},t.exports=n},{}],29:[function(e,t,r){"use strict";var h=e("../utils"),i=e("./ConvertWorker"),s=e("./GenericWorker"),f=e("../base64"),n=e("../support"),a=e("../external"),o=null;if(n.nodestream)try{o=e("../nodejs/NodejsStreamOutputAdapter")}catch(e){}function u(e,t,r){var n=t;switch(t){case"blob":case"arraybuffer":n="uint8array";break;case"base64":n="string"}try{this._internalType=n,this._outputType=t,this._mimeType=r,h.checkSupport(n),this._worker=e.pipe(new i(n)),e.lock()}catch(e){this._worker=new s("error"),this._worker.error(e)}}u.prototype={accumulate:function(e){return o=this,u=e,new a.Promise(function(t,r){var n=[],i=o._internalType,s=o._outputType,a=o._mimeType;o.on("data",function(e,t){n.push(e),u&&u(t)}).on("error",function(e){n=[],r(e)}).on("end",function(){try{var e=function(e,t,r){switch(e){case"blob":return h.newBlob(h.transformTo("arraybuffer",t),r);case"base64":return f.encode(t);default:return h.transformTo(e,t)}}(s,function(e,t){var r,n=0,i=null,s=0;for(r=0;r>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t}(e)},s.utf8decode=function(e){return u.nodebuffer?o.transformTo("nodebuffer",e).toString("utf-8"):function(e){var t,r,n,i,s=e.length,a=new Array(2*s);for(t=r=0;t>10&1023,a[r++]=56320|1023&n)}return a.length!==r&&(a.subarray?a=a.subarray(0,r):a.length=r),o.applyFromCharCode(a)}(e=o.transformTo(u.uint8array?"uint8array":"array",e))},o.inherits(a,n),a.prototype.processChunk=function(e){var t=o.transformTo(u.uint8array?"uint8array":"array",e.data);if(this.leftOver&&this.leftOver.length){if(u.uint8array){var r=t;(t=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),t.set(r,this.leftOver.length)}else t=this.leftOver.concat(t);this.leftOver=null}var n=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+h[e[r]]>t?r:t}(t),i=t;n!==t.length&&(u.uint8array?(i=t.subarray(0,n),this.leftOver=t.subarray(n,t.length)):(i=t.slice(0,n),this.leftOver=t.slice(n,t.length))),this.push({data:s.utf8decode(i),meta:e.meta})},a.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=a,o.inherits(f,n),f.prototype.processChunk=function(e){this.push({data:s.utf8encode(e.data),meta:e.meta})},s.Utf8EncodeWorker=f},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(e,t,o){"use strict";var u=e("./support"),h=e("./base64"),r=e("./nodejsUtils"),n=e("set-immediate-shim"),f=e("./external");function i(e){return e}function l(e,t){for(var r=0;r>8;this.dir=!!(16&this.externalFileAttributes),0==e&&(this.dosPermissions=63&this.externalFileAttributes),3==e&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(e){if(this.extraFields[1]){var t=n(this.extraFields[1].value);this.uncompressedSize===s.MAX_VALUE_32BITS&&(this.uncompressedSize=t.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=t.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=t.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS&&(this.diskNumberStart=t.readInt(4))}},readExtraFields:function(e){var t,r,n,i=e.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});e.index+4>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t},r.buf2binstring=function(e){return f(e,e.length)},r.binstring2buf=function(e){for(var t=new u.Buf8(e.length),r=0,n=t.length;r>10&1023,o[n++]=56320|1023&i)}return f(o,n)},r.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+h[e[r]]>t?r:t}},{"./common":41}],43:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){for(var i=65535&e|0,s=e>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a>>8^i[255&(e^t[a])];return-1^e}},{}],46:[function(e,t,r){"use strict";var u,d=e("../utils/common"),h=e("./trees"),c=e("./adler32"),p=e("./crc32"),n=e("./messages"),f=0,l=0,m=-2,i=2,_=8,s=286,a=30,o=19,g=2*s+1,v=15,b=3,w=258,y=w+b+1,k=42,x=113;function S(e,t){return e.msg=n[t],t}function z(e){return(e<<1)-(4e.avail_out&&(r=e.avail_out),0!==r&&(d.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function A(e,t){h._tr_flush_block(e,0<=e.block_start?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,C(e.strm)}function I(e,t){e.pending_buf[e.pending++]=t}function O(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function B(e,t){var r,n,i=e.max_chain_length,s=e.strstart,a=e.prev_length,o=e.nice_match,u=e.strstart>e.w_size-y?e.strstart-(e.w_size-y):0,h=e.window,f=e.w_mask,l=e.prev,d=e.strstart+w,c=h[s+a-1],p=h[s+a];e.prev_length>=e.good_match&&(i>>=2),o>e.lookahead&&(o=e.lookahead);do{if(h[(r=t)+a]===p&&h[r+a-1]===c&&h[r]===h[s]&&h[++r]===h[s+1]){s+=2,r++;do{}while(h[++s]===h[++r]&&h[++s]===h[++r]&&h[++s]===h[++r]&&h[++s]===h[++r]&&h[++s]===h[++r]&&h[++s]===h[++r]&&h[++s]===h[++r]&&h[++s]===h[++r]&&su&&0!=--i);return a<=e.lookahead?a:e.lookahead}function T(e){var t,r,n,i,s,a,o,u,h,f,l=e.w_size;do{if(i=e.window_size-e.lookahead-e.strstart,e.strstart>=l+(l-y)){for(d.arraySet(e.window,e.window,l,l,0),e.match_start-=l,e.strstart-=l,e.block_start-=l,t=r=e.hash_size;n=e.head[--t],e.head[t]=l<=n?n-l:0,--r;);for(t=r=l;n=e.prev[--t],e.prev[t]=l<=n?n-l:0,--r;);i+=l}if(0===e.strm.avail_in)break;if(a=e.strm,o=e.window,u=e.strstart+e.lookahead,f=void 0,(h=i)<(f=a.avail_in)&&(f=h),r=0===f?0:(a.avail_in-=f,d.arraySet(o,a.input,a.next_in,f,u),1===a.state.wrap?a.adler=c(a.adler,o,f,u):2===a.state.wrap&&(a.adler=p(a.adler,o,f,u)),a.next_in+=f,a.total_in+=f,f),e.lookahead+=r,e.lookahead+e.insert>=b)for(s=e.strstart-e.insert,e.ins_h=e.window[s],e.ins_h=(e.ins_h<=b&&(e.ins_h=(e.ins_h<=b)if(n=h._tr_tally(e,e.strstart-e.match_start,e.match_length-b),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=b){for(e.match_length--;e.strstart++,e.ins_h=(e.ins_h<=b&&(e.ins_h=(e.ins_h<=b&&e.match_length<=e.prev_length){for(i=e.strstart+e.lookahead-b,n=h._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-b),e.lookahead-=e.prev_length-1,e.prev_length-=2;++e.strstart<=i&&(e.ins_h=(e.ins_h<e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(T(e),0===e.lookahead&&t===f)return 1;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var n=e.block_start+r;if((0===e.strstart||e.strstart>=n)&&(e.lookahead=e.strstart-n,e.strstart=n,A(e,!1),0===e.strm.avail_out))return 1;if(e.strstart-e.block_start>=e.w_size-y&&(A(e,!1),0===e.strm.avail_out))return 1}return e.insert=0,4===t?(A(e,!0),0===e.strm.avail_out?3:4):(e.strstart>e.block_start&&(A(e,!1),e.strm.avail_out),1)}),new F(4,4,8,4,R),new F(4,5,16,8,R),new F(4,6,32,32,R),new F(4,4,16,16,D),new F(8,16,32,32,D),new F(8,16,128,128,D),new F(8,32,128,256,D),new F(32,128,258,1024,D),new F(32,258,258,4096,D)],r.deflateInit=function(e,t){return L(e,t,_,15,8,0)},r.deflateInit2=L,r.deflateReset=P,r.deflateResetKeep=U,r.deflateSetHeader=function(e,t){return e&&e.state?2!==e.state.wrap?m:(e.state.gzhead=t,l):m},r.deflate=function(e,t){var r,n,i,s;if(!e||!e.state||5>8&255),I(n,n.gzhead.time>>16&255),I(n,n.gzhead.time>>24&255),I(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),I(n,255&n.gzhead.os),n.gzhead.extra&&n.gzhead.extra.length&&(I(n,255&n.gzhead.extra.length),I(n,n.gzhead.extra.length>>8&255)),n.gzhead.hcrc&&(e.adler=p(e.adler,n.pending_buf,n.pending,0)),n.gzindex=0,n.status=69):(I(n,0),I(n,0),I(n,0),I(n,0),I(n,0),I(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),I(n,3),n.status=x);else{var a=_+(n.w_bits-8<<4)<<8;a|=(2<=n.strategy||n.level<2?0:n.level<6?1:6===n.level?2:3)<<6,0!==n.strstart&&(a|=32),a+=31-a%31,n.status=x,O(n,a),0!==n.strstart&&(O(n,e.adler>>>16),O(n,65535&e.adler)),e.adler=1}if(69===n.status)if(n.gzhead.extra){for(i=n.pending;n.gzindex<(65535&n.gzhead.extra.length)&&(n.pending!==n.pending_buf_size||(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),C(e),i=n.pending,n.pending!==n.pending_buf_size));)I(n,255&n.gzhead.extra[n.gzindex]),n.gzindex++;n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),n.gzindex===n.gzhead.extra.length&&(n.gzindex=0,n.status=73)}else n.status=73;if(73===n.status)if(n.gzhead.name){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),C(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindexi&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.gzindex=0,n.status=91)}else n.status=91;if(91===n.status)if(n.gzhead.comment){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),C(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindexi&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.status=103)}else n.status=103;if(103===n.status&&(n.gzhead.hcrc?(n.pending+2>n.pending_buf_size&&C(e),n.pending+2<=n.pending_buf_size&&(I(n,255&e.adler),I(n,e.adler>>8&255),e.adler=0,n.status=x)):n.status=x),0!==n.pending){if(C(e),0===e.avail_out)return n.last_flush=-1,l}else if(0===e.avail_in&&z(t)<=z(r)&&4!==t)return S(e,-5);if(666===n.status&&0!==e.avail_in)return S(e,-5);if(0!==e.avail_in||0!==n.lookahead||t!==f&&666!==n.status){var o=2===n.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(T(e),0===e.lookahead)){if(t===f)return 1;break}if(e.match_length=0,r=h._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(A(e,!1),0===e.strm.avail_out))return 1}return e.insert=0,4===t?(A(e,!0),0===e.strm.avail_out?3:4):e.last_lit&&(A(e,!1),0===e.strm.avail_out)?1:2}(n,t):3===n.strategy?function(e,t){for(var r,n,i,s,a=e.window;;){if(e.lookahead<=w){if(T(e),e.lookahead<=w&&t===f)return 1;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=b&&0e.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=b?(r=h._tr_tally(e,1,e.match_length-b),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=h._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(A(e,!1),0===e.strm.avail_out))return 1}return e.insert=0,4===t?(A(e,!0),0===e.strm.avail_out?3:4):e.last_lit&&(A(e,!1),0===e.strm.avail_out)?1:2}(n,t):u[n.level].func(n,t);if(3!==o&&4!==o||(n.status=666),1===o||3===o)return 0===e.avail_out&&(n.last_flush=-1),l;if(2===o&&(1===t?h._tr_align(n):5!==t&&(h._tr_stored_block(n,0,0,!1),3===t&&(E(n.head),0===n.lookahead&&(n.strstart=0,n.block_start=0,n.insert=0))),C(e),0===e.avail_out))return n.last_flush=-1,l}return 4!==t?l:n.wrap<=0?1:(2===n.wrap?(I(n,255&e.adler),I(n,e.adler>>8&255),I(n,e.adler>>16&255),I(n,e.adler>>24&255),I(n,255&e.total_in),I(n,e.total_in>>8&255),I(n,e.total_in>>16&255),I(n,e.total_in>>24&255)):(O(n,e.adler>>>16),O(n,65535&e.adler)),C(e),0=r.w_size&&(0===s&&(E(r.head),r.strstart=0,r.block_start=0,r.insert=0),h=new d.Buf8(r.w_size),d.arraySet(h,t,f-r.w_size,r.w_size,0),t=h,f=r.w_size),a=e.avail_in,o=e.next_in,u=e.input,e.avail_in=f,e.next_in=0,e.input=t,T(r);r.lookahead>=b;){for(n=r.strstart,i=r.lookahead-(b-1);r.ins_h=(r.ins_h<>>=w=b>>>24,p-=w,0==(w=b>>>16&255))E[s++]=65535&b;else{if(!(16&w)){if(0==(64&w)){b=m[(65535&b)+(c&(1<>>=w,p-=w),p<15&&(c+=z[n++]<>>=w=b>>>24,p-=w,!(16&(w=b>>>16&255))){if(0==(64&w)){b=_[(65535&b)+(c&(1<>>=w,p-=w,(w=s-a)>3,c&=(1<<(p-=y<<3))-1,e.next_in=n,e.next_out=s,e.avail_in=n>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=P,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new I.Buf32(n),t.distcode=t.distdyn=new I.Buf32(i),t.sane=1,t.back=-1,N):U}function o(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,a(e)):U}function u(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15=s.wsize?(I.arraySet(s.window,t,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(n<(i=s.wsize-s.wnext)&&(i=n),I.arraySet(s.window,t,r-n,i,s.wnext),(n-=i)?(I.arraySet(s.window,t,r-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=i,s.wnext===s.wsize&&(s.wnext=0),s.whave>>8&255,r.check=B(r.check,C,2,0),f=h=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&h)<<8)+(h>>8))%31){e.msg="incorrect header check",r.mode=30;break}if(8!=(15&h)){e.msg="unknown compression method",r.mode=30;break}if(f-=4,k=8+(15&(h>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){e.msg="invalid window size",r.mode=30;break}r.dmax=1<>8&1),512&r.flags&&(C[0]=255&h,C[1]=h>>>8&255,r.check=B(r.check,C,2,0)),f=h=0,r.mode=3;case 3:for(;f<32;){if(0===o)break e;o--,h+=n[s++]<>>8&255,C[2]=h>>>16&255,C[3]=h>>>24&255,r.check=B(r.check,C,4,0)),f=h=0,r.mode=4;case 4:for(;f<16;){if(0===o)break e;o--,h+=n[s++]<>8),512&r.flags&&(C[0]=255&h,C[1]=h>>>8&255,r.check=B(r.check,C,2,0)),f=h=0,r.mode=5;case 5:if(1024&r.flags){for(;f<16;){if(0===o)break e;o--,h+=n[s++]<>>8&255,r.check=B(r.check,C,2,0)),f=h=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(c=r.length)&&(c=o),c&&(r.head&&(k=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,n,s,c,k)),512&r.flags&&(r.check=B(r.check,n,c,s)),o-=c,s+=c,r.length-=c),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break e;for(c=0;k=n[s+c++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&c>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;f<32;){if(0===o)break e;o--,h+=n[s++]<>>=7&f,f-=7&f,r.mode=27;break}for(;f<3;){if(0===o)break e;o--,h+=n[s++]<>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==t)break;h>>>=2,f-=2;break e;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=30}h>>>=2,f-=2;break;case 14:for(h>>>=7&f,f-=7&f;f<32;){if(0===o)break e;o--,h+=n[s++]<>>16^65535)){e.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&h,f=h=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(c=r.length){if(o>>=5,f-=5,r.ndist=1+(31&h),h>>>=5,f-=5,r.ncode=4+(15&h),h>>>=4,f-=4,286>>=3,f-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=R(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have>>16&255,v=65535&E,!((_=E>>>24)<=f);){if(0===o)break e;o--,h+=n[s++]<>>=_,f-=_,r.lens[r.have++]=v;else{if(16===v){for(z=_+2;f>>=_,f-=_,0===r.have){e.msg="invalid bit length repeat",r.mode=30;break}k=r.lens[r.have-1],c=3+(3&h),h>>>=2,f-=2}else if(17===v){for(z=_+3;f>>=_)),h>>>=3,f-=3}else{for(z=_+7;f>>=_)),h>>>=7,f-=7}if(r.have+c>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=30;break}for(;c--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=R(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=R(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){e.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=o&&258<=u){e.next_out=a,e.avail_out=u,e.next_in=s,e.avail_in=o,r.hold=h,r.bits=f,T(e,d),a=e.next_out,i=e.output,u=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,h=r.hold,f=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(E=r.lencode[h&(1<>>16&255,v=65535&E,!((_=E>>>24)<=f);){if(0===o)break e;o--,h+=n[s++]<>b)])>>>16&255,v=65535&E,!(b+(_=E>>>24)<=f);){if(0===o)break e;o--,h+=n[s++]<>>=b,f-=b,r.back+=b}if(h>>>=_,f-=_,r.back+=_,r.length=v,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){e.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;f>>=r.extra,f-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(E=r.distcode[h&(1<>>16&255,v=65535&E,!((_=E>>>24)<=f);){if(0===o)break e;o--,h+=n[s++]<>b)])>>>16&255,v=65535&E,!(b+(_=E>>>24)<=f);){if(0===o)break e;o--,h+=n[s++]<>>=b,f-=b,r.back+=b}if(h>>>=_,f-=_,r.back+=_,64&g){e.msg="invalid distance code",r.mode=30;break}r.offset=v,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;f>>=r.extra,f-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===u)break e;if(c=d-u,r.offset>c){if((c=r.offset-c)>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=30;break}p=c>r.wnext?(c-=r.wnext,r.wsize-c):r.wnext-c,c>r.length&&(c=r.length),m=r.window}else m=i,p=a-r.offset,c=r.length;for(uc?(m=T[R+a[b]],A[I+a[b]]):(m=96,0),u=1<>S)+(h-=u)]=p<<24|m<<16|_|0,0!==h;);for(u=1<>=1;if(0!==u?(C&=u-1,C+=u):C=0,b++,0==--O[v]){if(v===y)break;v=t[r+a[b]]}if(k>>7)]}function x(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function S(e,t,r){e.bi_valid>i-r?(e.bi_buf|=t<>i-e.bi_valid,e.bi_valid+=r-i):(e.bi_buf|=t<>>=1,r<<=1,0<--t;);return r>>>1}function C(e,t,r){var n,i,s=new Array(_+1),a=0;for(n=1;n<=_;n++)s[n]=a=a+r[n-1]<<1;for(i=0;i<=t;i++){var o=e[2*i+1];0!==o&&(e[2*i]=E(s[o]++,o))}}function A(e){var t;for(t=0;t<286;t++)e.dyn_ltree[2*t]=0;for(t=0;t<30;t++)e.dyn_dtree[2*t]=0;for(t=0;t<19;t++)e.bl_tree[2*t]=0;e.dyn_ltree[512]=1,e.opt_len=e.static_len=0,e.last_lit=e.matches=0}function I(e){8>1;1<=r;r--)B(e,s,r);for(i=u;r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],B(e,s,1),n=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=n,s[2*i]=s[2*r]+s[2*n],e.depth[i]=(e.depth[r]>=e.depth[n]?e.depth[r]:e.depth[n])+1,s[2*r+1]=s[2*n+1]=i,e.heap[1]=i++,B(e,s,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,n,i,s,a,o,u=t.dyn_tree,h=t.max_code,f=t.stat_desc.static_tree,l=t.stat_desc.has_stree,d=t.stat_desc.extra_bits,c=t.stat_desc.extra_base,p=t.stat_desc.max_length,m=0;for(s=0;s<=_;s++)e.bl_count[s]=0;for(u[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r<573;r++)p<(s=u[2*u[2*(n=e.heap[r])+1]+1]+1)&&(s=p,m++),u[2*n+1]=s,h>=7;n<30;n++)for(w[n]=i<<7,e=0;e<1<>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return 0;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return 1;for(t=32;t<256;t++)if(0!==e.dyn_ltree[2*t])return 1;return 0}(e)),R(e,e.l_desc),R(e,e.d_desc),a=function(e){var t;for(D(e,e.dyn_ltree,e.l_desc.max_code),D(e,e.dyn_dtree,e.d_desc.max_code),R(e,e.bl_desc),t=18;3<=t&&0===e.bl_tree[2*f[t]+1];t--);return e.opt_len+=3*(t+1)+5+5+4,t}(e),i=e.opt_len+3+7>>>3,(s=e.static_len+3+7>>>3)<=i&&(i=s)):i=s=r+5,r+4<=i&&-1!==t?U(e,t,r,n):4===e.strategy||s===i?(S(e,2+(n?1:0),3),T(e,l,d)):(S(e,4+(n?1:0),3),function(e,t,r,n){var i;for(S(e,t-257,5),S(e,r-1,5),S(e,n-4,4),i=0;i>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(p[r]+256+1)]++,e.dyn_dtree[2*k(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){var t;S(e,2,3),z(e,256,l),16===(t=e).bi_valid?(x(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):8<=t.bi_valid&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}},{"../utils/common":41}],53:[function(e,t,r){"use strict";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(e,t,r){"use strict";t.exports="function"==typeof setImmediate?setImmediate:function(){var e=[].slice.apply(arguments);e.splice(1,0,0),setTimeout.apply(null,e)}},{}]},{},[10])(10)})}).call(this,void 0!==r?r:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)})}).call(this,void 0!==r?r:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)})}).call(this,void 0!==r?r:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)})}).call(this,void 0!==r?r:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)}); \ No newline at end of file diff --git a/modules/save.js b/modules/save.js index 1a2e910d..07f24b83 100644 --- a/modules/save.js +++ b/modules/save.js @@ -70,13 +70,78 @@ async function saveJPEG() { TIME && console.timeEnd("saveJPEG"); } +// download map as png tiles +async function saveTiles() { + return new Promise(async (resolve, reject) => { + // download schema + const urlSchema = await getMapURL("tiles", "schema"); + const zip = new JSZip(); + + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + canvas.width = graphWidth; + canvas.height = graphHeight; + + const imgSchema = new Image(); + imgSchema.src = urlSchema; + imgSchema.onload = function () { + ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height); + canvas.toBlob(blob => zip.file(`fmg_tile_schema.png`, blob)); + }; + + // download tiles + const url = await getMapURL("tiles"); + const tilesX = +document.getElementById("tileColsInput").value; + const tilesY = +document.getElementById("tileRowsInput").value; + const scale = +document.getElementById("tileScaleInput").value; + + const tileW = (graphWidth / tilesX) | 0; + const tileH = (graphHeight / tilesY) | 0; + const tolesTotal = tilesX * tilesY; + + canvas.width = graphWidth * scale; + canvas.height = graphHeight * scale; + + let loaded = 0; + const img = new Image(); + img.src = url; + img.onload = function () { + for (let y = 0, i = 0; y < graphHeight; y += tileH) { + for (let x = 0; x < graphWidth; x += tileW, i++) { + ctx.drawImage(img, x, y, tileW, tileH, 0, 0, canvas.width, canvas.height); + const name = `fmg_tile_${i}.png`; + canvas.toBlob(blob => { + zip.file(name, blob); + loaded += 1; + if (loaded === tolesTotal) return downloadZip(); + }); + } + } + }; + + function downloadZip() { + const name = `${getFileName()}.zip`; + zip.generateAsync({type: "blob"}).then(blob => { + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + link.download = name; + link.click(); + link.remove(); + + setTimeout(() => URL.revokeObjectURL(link.href), 5000); + resolve(true); + }); + } + }); +} + // parse map svg to object url async function getMapURL(type, subtype) { const cloneEl = document.getElementById("map").cloneNode(true); // clone svg cloneEl.id = "fantasyMap"; document.body.appendChild(cloneEl); const clone = d3.select(cloneEl); - clone.select("#debug").remove(); + if (subtype !== "schema") clone.select("#debug").remove(); const cloneDefs = cloneEl.getElementsByTagName("defs")[0]; const svgDefs = document.getElementById("defElements"); @@ -93,6 +158,7 @@ async function getMapURL(type, subtype) { clone.attr("width", graphWidth).attr("height", graphHeight); clone.select("#viewbox").attr("transform", null); } + if (type === "svg") removeUnusedElements(clone); if (customization && type === "mesh") updateMeshCells(clone); inlineStyle(clone); diff --git a/modules/ui/options.js b/modules/ui/options.js index 48efa169..dcfdd58b 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -638,7 +638,7 @@ function showSavePane() { $("#saveMapData").dialog({ title: "Save map", resizable: false, - width: "27em", + width: "30em", position: {my: "center", at: "center", of: "svg"}, buttons: { Close: function () { @@ -719,6 +719,70 @@ document.getElementById("mapToLoad").addEventListener("change", function () { uploadMap(fileToLoad); }); +function openSaveTiles() { + closeDialogs(); + updateTilesOptions(); + const status = document.getElementById("tileStatus"); + status.innerHTML = ""; + + $("#saveTilesScreen").dialog({ + resizable: false, + title: "Download tiles", + width: "23em", + buttons: { + Download: function () { + status.innerHTML = "Preparing for download..."; + setTimeout(() => (status.innerHTML = "Downloading. It may take some time."), 1000); + const loading = setInterval(() => (status.innerHTML += "."), 1000); + saveTiles().then(() => { + clearInterval(loading); + status.innerHTML = `Done. Check file in "Downloads" (crtl + J)`; + setTimeout(() => (status.innerHTML = ""), 8000); + }); + }, + Cancel: function () { + $(this).dialog("close"); + } + }, + close: () => debug.selectAll("*").remove() + }); +} + +document + .getElementById("saveTilesScreen") + .querySelectorAll("input") + .forEach(el => el.addEventListener("input", updateTilesOptions)); + +function updateTilesOptions() { + const tileSize = document.getElementById("tileSize"); + const tilesX = +document.getElementById("tileColsInput").value; + const tilesY = +document.getElementById("tileRowsInput").value; + + // calculate size + const scale = +document.getElementById("tileScaleInput").value; + const sizeX = graphWidth * scale * tilesX; + const sizeY = graphHeight * scale * tilesY; + const totalSize = sizeX * sizeY; + + tileSize.innerHTML = `${sizeX} x ${sizeY} px`; + tileSize.style.color = totalSize > 1e9 ? "#053305" : totalSize > 1e7 ? "#9e6409" : "#1a941a"; + + // draw tiles + const rects = []; + const labels = []; + const tileW = (graphWidth / tilesX) | 0; + const tileH = (graphHeight / tilesY) | 0; + for (let y = 0, i = 0; y < graphHeight; y += tileH) { + for (let x = 0; x < graphWidth; x += tileW, i++) { + rects.push(``); + labels.push(`${i}`); + } + } + const rectsG = "" + rects.join("") + ""; + const labelsG = "" + labels.join("") + ""; + debug.html(rectsG + labelsG); +} + // View mode viewMode.addEventListener("click", changeViewMode); function changeViewMode(event) { From e923cf06c92923a74503a3e97ad54e92544f7824 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 24 Jun 2021 23:13:45 +0300 Subject: [PATCH 24/34] toggle labels from save dialog --- index.html | 4 ++++ modules/ui/options.js | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 0d699afd..4df5a7c1 100644 --- a/index.html +++ b/index.html @@ -3400,6 +3400,10 @@ +
    + + +
    -
    +
    +
    + Depressions filling max iterations: + + +
    + +
    + Depression depth threshold: + + +
    diff --git a/main.js b/main.js index 23617a39..0abcf164 100644 --- a/main.js +++ b/main.js @@ -774,9 +774,10 @@ function markup(cells, start, increment, limit) { function addLakesInDeepDepressions() { console.time("addLakesInDeepDepressions"); - const {cells, features, points} = grid; + const {cells, features} = grid; const {c, h, b} = cells; - const ELEVATION_LIMIT = 10; + const ELEVATION_LIMIT = +document.getElementById("lakeElevationLimitOutput").value; + if (ELEVATION_LIMIT === 80) return; for (const i of cells.i) { if (b[i] || h[i] < 20) continue; @@ -785,7 +786,7 @@ function addLakesInDeepDepressions() { if (h[i] > minHeight) continue; let deep = true; - const treshold = h[i] + ELEVATION_LIMIT; + const threshold = h[i] + ELEVATION_LIMIT; const queue = [i]; const checked = []; checked[i] = true; @@ -796,7 +797,7 @@ function addLakesInDeepDepressions() { for (const n of c[q]) { if (checked[n]) continue; - if (h[n] >= treshold) continue; + if (h[n] >= threshold) continue; if (h[n] < 20) { deep = false; break; @@ -830,7 +831,7 @@ function addLakesInDeepDepressions() { console.timeEnd("addLakesInDeepDepressions"); } -// near sea lakes usually get a lot of water inflow, most of them should brake treshold and flow out to sea (see Ancylus Lake) +// near sea lakes usually get a lot of water inflow, most of them should brake threshold and flow out to sea (see Ancylus Lake) function openNearSeaLakes() { if (templateInput.value === "Atoll") return; // no need for Atolls @@ -856,11 +857,11 @@ function openNearSeaLakes() { } } - function removeLake(treshold, lake, ocean) { - cells.h[treshold] = 19; - cells.t[treshold] = -1; - cells.f[treshold] = ocean; - cells.c[treshold].forEach(function (c) { + function removeLake(threshold, lake, ocean) { + cells.h[threshold] = 19; + cells.t[threshold] = -1; + cells.f[threshold] = ocean; + cells.c[threshold].forEach(function (c) { if (cells.h[c] >= 20) cells.t[c] = 1; // mark as coastline }); features[lake].type = "ocean"; // mark former lake as ocean diff --git a/modules/lakes.js b/modules/lakes.js index 261fd485..d127c2cf 100644 --- a/modules/lakes.js +++ b/modules/lakes.js @@ -39,7 +39,7 @@ const prepareLakeData = h => { const cells = pack.cells; - const ELEVATION_LIMIT = 10; + const ELEVATION_LIMIT = +document.getElementById("lakeElevationLimitOutput").value; pack.features.forEach(f => { if (f.type !== "lake") return; @@ -55,8 +55,13 @@ f.height = h[min] - 0.1; // check if lake can be open (not in deep depression) + if (ELEVATION_LIMIT === 80) { + f.closed = false; + return; + } + let deep = true; - const treshold = f.height + ELEVATION_LIMIT; + const threshold = f.height + ELEVATION_LIMIT; const queue = [min]; const checked = []; checked[min] = true; @@ -67,7 +72,7 @@ for (const n of cells.c[q]) { if (checked[n]) continue; - if (h[n] >= treshold) continue; + if (h[n] >= threshold) continue; if (h[n] < 20) { const nFeature = pack.features[cells.f[n]]; diff --git a/modules/river-generator.js b/modules/river-generator.js index 81f3457f..d34c0a0e 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -17,7 +17,7 @@ const h = alterHeights(); Lakes.prepareLakeData(h); - resolveDepressions(h, 200); + resolveDepressions(h); drainWater(); defineRivers(); Lakes.cleanupLakeData(); @@ -184,8 +184,12 @@ }; // depression filling algorithm (for a correct water flux modeling) - const resolveDepressions = function (h, maxIterations) { + const resolveDepressions = function (h) { const {cells, features} = pack; + const maxIterations = +document.getElementById("resolveDepressionsStepsOutput").value; + const checkLakeMaxIteration = maxIterations * 0.8; + const elevateLakeMaxIteration = (maxIterations - checkLakeMaxIteration) / 2; + const height = i => features[cells.f[i]].height || h[i]; // height of lake or specific cell const lakes = features.filter(f => f.type === "lake"); @@ -195,8 +199,7 @@ const progress = []; let depressions = Infinity; let prevDepressions = null; - let iteration = 0; - for (; depressions && iteration < maxIterations; iteration++) { + for (let iteration = 0; depressions && iteration < maxIterations; iteration++) { if (progress.length > 5 && d3.sum(progress) > 0) { // bad progress, abort and set heights back h = alterHeights(); @@ -206,13 +209,13 @@ depressions = 0; - if (iteration < 180) { + if (iteration < checkLakeMaxIteration) { for (const l of lakes) { if (l.closed) continue; const minHeight = d3.min(l.shoreline.map(s => h[s])); if (minHeight >= 100 || l.height > minHeight) continue; - if (iteration > 150) { + if (iteration < elevateLakeMaxIteration) { l.shoreline.forEach(i => (h[i] = cells.h[i])); l.height = d3.min(l.shoreline.map(s => h[s])) - 1; l.closed = true; @@ -342,8 +345,8 @@ const rivers = pack.rivers; if (!rivers.length) return; Math.random = aleaPRNG(seed); - const tresholdElement = Math.ceil(rivers.length * 0.15); - const smallLength = rivers.map(r => r.length || 0).sort((a, b) => a - b)[tresholdElement]; + const thresholdElement = Math.ceil(rivers.length * 0.15); + const smallLength = rivers.map(r => r.length || 0).sort((a, b) => a - b)[thresholdElement]; const smallType = {Creek: 9, River: 3, Brook: 3, Stream: 1}; // weighted small river types for (const r of rivers) { diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 1a5dd1a1..4cc390bc 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -541,7 +541,7 @@ function addRiverOnClick() { const h = Rivers.alterHeights(); Lakes.prepareLakeData(h); - Rivers.resolveDepressions(h, 200); + Rivers.resolveDepressions(h); while (i) { cells.r[i] = river; From 389237081626468ec702bbbcba7a84cae2ef7163 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 26 Jun 2021 00:41:43 +0300 Subject: [PATCH 29/34] automatically pit output to input and store data --- index.html | 43 ++++--- modules/load.js | 2 +- modules/save.js | 2 +- modules/ui/measurers.js | 254 +++++++++++++++++++++++++++---------- modules/ui/options.js | 63 +++++---- modules/ui/style.js | 15 +-- modules/ui/units-editor.js | 238 ++++++++++++++-------------------- 7 files changed, 347 insertions(+), 270 deletions(-) diff --git a/index.html b/index.html index 33c29a5a..2c9fce59 100644 --- a/index.html +++ b/index.html @@ -752,7 +752,7 @@ Color scheme - @@ -764,14 +764,14 @@ Terracing - + 0 Reduce layers - + 5 @@ -779,7 +779,7 @@ Simplify line - + 0 @@ -787,7 +787,7 @@ Line style - @@ -953,7 +953,7 @@ - 10K + 10K @@ -1151,7 +1151,7 @@ - + @@ -1364,14 +1364,14 @@
    Depressions filling max iterations: - - + +
    Depression depth threshold: - - + +
    @@ -3086,8 +3086,8 @@
    Bar size:
    - - + +
    @@ -3408,8 +3408,8 @@

    Generator uses pop-up window to download files. Please ensure your browser does not block popups.

    PNG / JPEG scale: - - + +
    @@ -3430,18 +3430,19 @@

    Map will be split into tiles and downloaded as a single zip file. Avoid saving to big images

    Columns:
    - - + +
    Rows:
    - - + +
    Scale:
    - - + +
    Total size:
    diff --git a/modules/load.js b/modules/load.js index 500c390a..60c5b3e3 100644 --- a/modules/load.js +++ b/modules/load.js @@ -125,7 +125,7 @@ function parseLoadedData(data) { if (settings[3]) applyOption(heightUnit, settings[3]); if (settings[4]) heightExponentInput.value = heightExponentOutput.value = settings[4]; if (settings[5]) temperatureScale.value = settings[5]; - if (settings[6]) barSize.value = barSizeOutput.value = settings[6]; + if (settings[6]) barSizeInput.value = barSizeOutput.value = settings[6]; if (settings[7] !== undefined) barLabel.value = settings[7]; if (settings[8] !== undefined) barBackOpacity.value = settings[8]; if (settings[9]) barBackColor.value = settings[9]; diff --git a/modules/save.js b/modules/save.js index 72e73028..d612799c 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, barSize.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.value, urbanization.value, 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/measurers.js b/modules/ui/measurers.js index 6f27a53b..1d97a2d1 100644 --- a/modules/ui/measurers.js +++ b/modules/ui/measurers.js @@ -20,10 +20,7 @@ class Rulers { for (const rulerString of rulers) { const [type, pointsString] = rulerString.split(": "); const points = pointsString.split(" ").map(el => el.split(",").map(n => +n)); - const Type = type === "Ruler" ? Ruler : - type === "Opisometer" ? Opisometer : - type === "RouteOpisometer" ? RouteOpisometer : - type === "Planimeter" ? Planimeter : null; + const Type = type === "Ruler" ? Ruler : type === "Opisometer" ? Opisometer : type === "RouteOpisometer" ? RouteOpisometer : type === "Planimeter" ? Planimeter : null; this.create(Type, points); } } @@ -57,7 +54,7 @@ class Measurer { } getSize() { - return rn(1 / scale ** .3 * 2, 2); + return rn((1 / scale ** 0.3) * 2, 2); } getDash() { @@ -66,10 +63,11 @@ class Measurer { drag() { 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); }); } @@ -89,9 +87,9 @@ class Measurer { const MIN_DIST2 = 900; const optimized = []; - for (let i=0, p1 = this.points[0]; i < this.points.length; i++) { + for (let i = 0, p1 = this.points[0]; i < this.points.length; i++) { const p2 = this.points[i]; - const dist2 = !i || i === this.points.length-1 ? Infinity : (p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2; + const dist2 = !i || i === this.points.length - 1 ? Infinity : (p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2; if (dist2 < MIN_DIST2) continue; optimized.push(p2); p1 = p2; @@ -105,7 +103,6 @@ class Measurer { undraw() { this.el?.remove(); } - } class Ruler extends Measurer { @@ -136,12 +133,29 @@ class Ruler extends Measurer { const size = this.getSize(); const dash = this.getDash(); - const el = this.el = ruler.append("g").attr("class", "ruler").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size); - el.append("polyline").attr("points", points).attr("class", "white").attr("stroke-width", size) + const el = (this.el = ruler + .append("g") + .attr("class", "ruler") + .call(d3.drag().on("start", this.drag)) + .attr("font-size", 10 * size)); + el.append("polyline") + .attr("points", points) + .attr("class", "white") + .attr("stroke-width", size) .call(d3.drag().on("start", () => this.addControl(this))); - el.append("polyline").attr("points", points).attr("class", "gray").attr("stroke-width", rn(size * 1.2, 2)).attr("stroke-dasharray", dash); - el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size); - el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id)); + el.append("polyline") + .attr("points", points) + .attr("class", "gray") + .attr("stroke-width", rn(size * 1.2, 2)) + .attr("stroke-dasharray", dash); + el.append("g") + .attr("class", "rulerPoints") + .attr("stroke-width", 0.5 * size) + .attr("font-size", 2 * size); + el.append("text") + .attr("dx", ".35em") + .attr("dy", "-.45em") + .on("click", () => rulers.remove(this.id)); this.drawPoints(el); this.updateLabel(); return this; @@ -151,7 +165,7 @@ class Ruler extends Measurer { const g = el.select(".rulerPoints"); g.selectAll("circle").remove(); - for (let i=0; i < this.points.length; i++) { + for (let i = 0; i < this.points.length; i++) { const [x, y] = this.points[i]; this.drawPoint(g, x, y, i); } @@ -160,14 +174,25 @@ class Ruler extends Measurer { drawPoint(el, x, y, i) { const context = this; el.append("circle") - .attr("r", "1em").attr("cx", x).attr("cy", y) + .attr("r", "1em") + .attr("cx", x) + .attr("cy", y) .attr("class", this.isEdge(i) ? "edge" : "control") - .on("click", function() {context.removePoint(context, i)}) - .call(d3.drag().clickDistance(3).on("start", function() {context.dragControl(context, i)})); + .on("click", function () { + context.removePoint(context, i); + }) + .call( + d3 + .drag() + .clickDistance(3) + .on("start", function () { + context.dragControl(context, i); + }) + ); } isEdge(i) { - return i === 0 || i === this.points.length-1; + return i === 0 || i === this.points.length - 1; } updateLabel() { @@ -179,9 +204,9 @@ class Ruler extends Measurer { getLength() { let length = 0; - for (let i=0; i < this.points.length - 1; i++) { + for (let i = 0; i < this.points.length - 1; i++) { const [x1, y1] = this.points[i]; - const [x2, y2] = this.points[i+1]; + const [x2, y2] = this.points[i + 1]; length += Math.hypot(x1 - x2, y1 - y2); } return length; @@ -189,20 +214,20 @@ class Ruler extends Measurer { dragControl(context, pointId) { let addPoint = context.isEdge(pointId) && d3.event.sourceEvent.ctrlKey; - let circle = context.el.select(`circle:nth-child(${pointId+1})`); + let circle = context.el.select(`circle:nth-child(${pointId + 1})`); const line = context.el.selectAll("polyline"); let x0 = rn(d3.event.x, 1); let y0 = rn(d3.event.y, 1); let axis; - d3.event.on("drag", function() { + d3.event.on("drag", function () { if (addPoint) { - if (d3.event.dx < .1 && d3.event.dy < .1) return; + if (d3.event.dx < 0.1 && d3.event.dy < 0.1) return; context.pushPoint(pointId); context.drawPoints(context.el); if (pointId) pointId++; - circle = context.el.select(`circle:nth-child(${pointId+1})`); + circle = context.el.select(`circle:nth-child(${pointId + 1})`); addPoint = false; } @@ -253,13 +278,38 @@ class Opisometer extends Measurer { const dash = this.getDash(); const context = this; - const el = this.el = ruler.append("g").attr("class", "opisometer").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size); + const el = (this.el = ruler + .append("g") + .attr("class", "opisometer") + .call(d3.drag().on("start", this.drag)) + .attr("font-size", 10 * size)); el.append("path").attr("class", "white").attr("stroke-width", size); el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash); - const rulerPoints = el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size); - rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 0)})); - rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 1)})); - el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id)); + const rulerPoints = el + .append("g") + .attr("class", "rulerPoints") + .attr("stroke-width", 0.5 * size) + .attr("font-size", 2 * size); + rulerPoints + .append("circle") + .attr("r", "1em") + .call( + d3.drag().on("start", function () { + context.dragControl(context, 0); + }) + ); + rulerPoints + .append("circle") + .attr("r", "1em") + .call( + d3.drag().on("start", function () { + context.dragControl(context, 1); + }) + ); + el.append("text") + .attr("dx", ".35em") + .attr("dy", "-.45em") + .on("click", () => rulers.remove(this.id)); this.updateCurve(); this.updateLabel(); @@ -267,7 +317,7 @@ class Opisometer extends Measurer { } updateCurve() { - lineGen.curve(d3.curveCatmullRom.alpha(.5)); + lineGen.curve(d3.curveCatmullRom.alpha(0.5)); const path = round(lineGen(this.points)); this.el.selectAll("path").attr("d", path); @@ -288,7 +338,7 @@ class Opisometer extends Measurer { const MIN_DIST = d3.event.sourceEvent.shiftKey ? 9 : 100; let prev = rigth ? last(context.points) : context.points[0]; - d3.event.on("drag", function() { + d3.event.on("drag", function () { const point = [d3.event.x | 0, d3.event.y | 0]; const dist2 = (prev[0] - point[0]) ** 2 + (prev[1] - point[1]) ** 2; @@ -301,7 +351,7 @@ class Opisometer extends Measurer { context.updateLabel(); }); - d3.event.on("end", function() { + d3.event.on("end", function () { if (!d3.event.sourceEvent.shiftKey) context.optimize(); }); } @@ -367,13 +417,37 @@ class RouteOpisometer extends Measurer { const dash = this.getDash(); const context = this; - const el = this.el = ruler.append("g").attr("class", "opisometer").attr("font-size", 10 * size); + const el = (this.el = ruler + .append("g") + .attr("class", "opisometer") + .attr("font-size", 10 * size)); el.append("path").attr("class", "white").attr("stroke-width", size); el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash); - const rulerPoints = el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size); - rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 0)})); - rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 1)})); - el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id)); + const rulerPoints = el + .append("g") + .attr("class", "rulerPoints") + .attr("stroke-width", 0.5 * size) + .attr("font-size", 2 * size); + rulerPoints + .append("circle") + .attr("r", "1em") + .call( + d3.drag().on("start", function () { + context.dragControl(context, 0); + }) + ); + rulerPoints + .append("circle") + .attr("r", "1em") + .call( + d3.drag().on("start", function () { + context.dragControl(context, 1); + }) + ); + el.append("text") + .attr("dx", ".35em") + .attr("dy", "-.45em") + .on("click", () => rulers.remove(this.id)); this.updateCurve(); this.updateLabel(); @@ -381,7 +455,7 @@ class RouteOpisometer extends Measurer { } updateCurve() { - lineGen.curve(d3.curveCatmullRom.alpha(.5)); + lineGen.curve(d3.curveCatmullRom.alpha(0.5)); const path = round(lineGen(this.points)); this.el.selectAll("path").attr("d", path); @@ -399,7 +473,7 @@ class RouteOpisometer extends Measurer { } dragControl(context, rigth) { - d3.event.on("drag", function() { + d3.event.on("drag", function () { const mousePoint = [d3.event.x | 0, d3.event.y | 0]; const cells = pack.cells; @@ -422,7 +496,11 @@ class Planimeter extends Measurer { if (this.el) this.el.selectAll("*").remove(); const size = this.getSize(); - const el = this.el = ruler.append("g").attr("class", "planimeter").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size); + const el = (this.el = ruler + .append("g") + .attr("class", "planimeter") + .call(d3.drag().on("start", this.drag)) + .attr("font-size", 10 * size)); el.append("path").attr("class", "planimeter").attr("stroke-width", size); el.append("text").on("click", () => rulers.remove(this.id)); @@ -432,7 +510,7 @@ class Planimeter extends Measurer { } updateCurve() { - lineGen.curve(d3.curveCatmullRomClosed.alpha(.5)); + lineGen.curve(d3.curveCatmullRomClosed.alpha(0.5)); const path = round(lineGen(this.points)); this.el.selectAll("path").attr("d", path); } @@ -458,36 +536,79 @@ function drawScaleBar() { // calculate size const init = 100; // actual length in pixels if scale, dScale and size = 1; - const size = +barSize.value; - let val = init * size * dScale / scale; // bar length in distance unit - if (val > 900) val = rn(val, -3); // round to 1000 - else if (val > 90) val = rn(val, -2); // round to 100 - else if (val > 9) val = rn(val, -1); // round to 10 - else val = rn(val) // round to 1 - const l = val * scale / dScale; // actual length in pixels on this scale + const size = +barSizeInput.value; + let val = (init * size * dScale) / scale; // bar length in distance unit + if (val > 900) val = rn(val, -3); + // round to 1000 + else if (val > 90) val = rn(val, -2); + // round to 100 + else if (val > 9) val = rn(val, -1); + // round to 10 + else val = rn(val); // round to 1 + const l = (val * scale) / dScale; // actual length in pixels on this scale - scaleBar.append("line").attr("x1", 0.5).attr("y1", 0).attr("x2", l+size-0.5).attr("y2", 0).attr("stroke-width", size).attr("stroke", "white"); - scaleBar.append("line").attr("x1", 0).attr("y1", size).attr("x2", l+size).attr("y2", size).attr("stroke-width", size).attr("stroke", "#3d3d3d"); + scaleBar + .append("line") + .attr("x1", 0.5) + .attr("y1", 0) + .attr("x2", l + size - 0.5) + .attr("y2", 0) + .attr("stroke-width", size) + .attr("stroke", "white"); + scaleBar + .append("line") + .attr("x1", 0) + .attr("y1", size) + .attr("x2", l + size) + .attr("y2", size) + .attr("stroke-width", size) + .attr("stroke", "#3d3d3d"); const dash = size + " " + rn(l / 5 - size, 2); - scaleBar.append("line").attr("x1", 0).attr("y1", 0).attr("x2", l+size).attr("y2", 0) - .attr("stroke-width", rn(size * 3, 2)).attr("stroke-dasharray", dash).attr("stroke", "#3d3d3d"); + scaleBar + .append("line") + .attr("x1", 0) + .attr("y1", 0) + .attr("x2", l + size) + .attr("y2", 0) + .attr("stroke-width", rn(size * 3, 2)) + .attr("stroke-dasharray", dash) + .attr("stroke", "#3d3d3d"); const fontSize = rn(5 * size, 1); - scaleBar.selectAll("text").data(d3.range(0,6)).enter().append("text") - .attr("x", d => rn(d * l/5, 2)).attr("y", 0).attr("dy", "-.5em") - .attr("font-size", fontSize).text(d => rn(d * l/5 * dScale / scale) + (d<5 ? "" : " " + unit)); + scaleBar + .selectAll("text") + .data(d3.range(0, 6)) + .enter() + .append("text") + .attr("x", d => rn((d * l) / 5, 2)) + .attr("y", 0) + .attr("dy", "-.5em") + .attr("font-size", fontSize) + .text(d => rn((((d * l) / 5) * dScale) / scale) + (d < 5 ? "" : " " + unit)); if (barLabel.value !== "") { - scaleBar.append("text").attr("x", (l+1) / 2).attr("y", 2 * size) + scaleBar + .append("text") + .attr("x", (l + 1) / 2) + .attr("y", 2 * size) .attr("dominant-baseline", "text-before-edge") - .attr("font-size", fontSize).text(barLabel.value); + .attr("font-size", fontSize) + .text(barLabel.value); } const bbox = scaleBar.node().getBBox(); // append backbround rectangle - scaleBar.insert("rect", ":first-child").attr("x", -10).attr("y", -20).attr("width", bbox.width + 10).attr("height", bbox.height + 15) - .attr("stroke-width", size).attr("stroke", "none").attr("filter", "url(#blur5)") - .attr("fill", barBackColor.value).attr("opacity", +barBackOpacity.value); + scaleBar + .insert("rect", ":first-child") + .attr("x", -10) + .attr("y", -20) + .attr("width", bbox.width + 10) + .attr("height", bbox.height + 15) + .attr("stroke-width", size) + .attr("stroke", "none") + .attr("filter", "url(#blur5)") + .attr("fill", barBackColor.value) + .attr("opacity", +barBackOpacity.value); fitScaleBar(); } @@ -495,9 +616,10 @@ function drawScaleBar() { // fit ScaleBar to canvas size function fitScaleBar() { if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return; - const px = isNaN(+barPosX.value) ? .99 : barPosX.value / 100; - const py = isNaN(+barPosY.value) ? .99 : barPosY.value / 100; + const px = isNaN(+barPosX.value) ? 0.99 : barPosX.value / 100; + const py = isNaN(+barPosY.value) ? 0.99 : barPosY.value / 100; const bbox = scaleBar.select("rect").node().getBBox(); - const x = rn(svgWidth * px - bbox.width + 10), y = rn(svgHeight * py - bbox.height + 20); + const x = rn(svgWidth * px - bbox.width + 10), + y = rn(svgHeight * py - bbox.height + 20); scaleBar.attr("transform", `translate(${x},${y})`); } diff --git a/modules/ui/options.js b/modules/ui/options.js index 3990be2a..9aa77a5a 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -108,35 +108,51 @@ function showSupporters() { $("#alert").dialog({resizable: false, title: "Patreon Supporters", width: "54vw", position: {my: "center", at: "center", of: "svg"}}); } +// on any option or dialog change +document.getElementById("options").addEventListener("change", checkIfStored); +document.getElementById("dialogs").addEventListener("change", checkIfStored); +document.getElementById("options").addEventListener("input", updateOutputToFollowInput); +document.getElementById("dialogs").addEventListener("input", updateOutputToFollowInput); + +function checkIfStored(ev) { + if (ev.target.dataset.stored) lock(ev.target.dataset.stored); +} + +function updateOutputToFollowInput(ev) { + const id = ev.target.id; + const value = ev.target.value; + + // specific cases + if (id === "manorsInput") return (manorsOutput.value = value == 1000 ? "auto" : value); + + // generic case + if (id.slice(-5) === "Input") { + const output = document.getElementById(id.slice(0, -5) + "Output"); + if (output) output.value = value; + } else if (id.slice(-6) === "Output") { + const input = document.getElementById(id.slice(0, -6) + "Input"); + if (input) input.value = value; + } +} + // Option listeners const optionsContent = document.getElementById("optionsContent"); optionsContent.addEventListener("input", function (event) { - const id = event.target.id, - value = event.target.value; + const id = event.target.id; + const value = event.target.value; if (id === "mapWidthInput" || id === "mapHeightInput") mapSizeInputChange(); else if (id === "pointsInput") changeCellsDensity(+value); - else if (id === "culturesInput") culturesOutput.value = value; - else if (id === "culturesOutput") culturesInput.value = value; else if (id === "culturesSet") changeCultureSet(); else if (id === "regionsInput" || id === "regionsOutput") changeStatesNumber(value); - else if (id === "provincesInput") provincesOutput.value = value; - else if (id === "provincesOutput") provincesOutput.value = value; - else if (id === "provincesOutput") powerOutput.value = value; - else if (id === "powerInput") powerOutput.value = value; - else if (id === "powerOutput") powerInput.value = value; - else if (id === "neutralInput") neutralOutput.value = value; - else if (id === "neutralOutput") neutralInput.value = value; - else if (id === "manorsInput") changeBurgsNumberSlider(value); - else if (id === "religionsInput") religionsOutput.value = value; else if (id === "emblemShape") changeEmblemShape(value); else if (id === "tooltipSizeInput" || id === "tooltipSizeOutput") changeTooltipSize(value); else if (id === "transparencyInput") changeDialogsTransparency(value); }); optionsContent.addEventListener("change", function (event) { - if (event.target.dataset.stored) lock(event.target.dataset.stored); - const id = event.target.id, - value = event.target.value; + const id = event.target.id; + const value = event.target.value; + if (id === "zoomExtentMin" || id === "zoomExtentMax") changeZoomExtent(value); else if (id === "optionsSeed") generateMapWithSeed(); else if (id === "uiSizeInput" || id === "uiSizeOutput") changeUIsize(value); @@ -332,8 +348,8 @@ function changeCellsDensity(value) { const cells = convert(value); pointsInput.setAttribute("data-cells", cells); - pointsOutput.value = cells / 1000 + "K"; - pointsOutput.style.color = cells > 50000 ? "#b12117" : cells !== 10000 ? "#dfdf12" : "#053305"; + pointsOutput_formatted.value = cells / 1000 + "K"; + pointsOutput_formatted.style.color = cells > 50000 ? "#b12117" : cells !== 10000 ? "#dfdf12" : "#053305"; } function changeCultureSet() { @@ -384,16 +400,11 @@ function changeEmblemShape(emblemShape) { } function changeStatesNumber(value) { - regionsInput.value = regionsOutput.value = value; regionsOutput.style.color = +value ? null : "#b12117"; burgLabels.select("#capitals").attr("data-size", Math.max(rn(6 - value / 20), 3)); labels.select("#countries").attr("data-size", Math.max(rn(18 - value / 6), 4)); } -function changeBurgsNumberSlider(value) { - manorsOutput.value = value == 1000 ? "auto" : value; -} - function changeUIsize(value) { if (isNaN(+value) || +value < 0.5) return; @@ -410,7 +421,6 @@ function getUImaxSize() { } function changeTooltipSize(value) { - tooltipSizeInput.value = tooltipSizeOutput.value = value; tooltip.style.fontSize = `calc(${value}px + 0.5vw)`; } @@ -448,8 +458,9 @@ function applyStoredOptions() { if (localStorage.getItem("heightUnit")) applyOption(heightUnit, localStorage.getItem("heightUnit")); for (let i = 0; i < localStorage.length; i++) { - const stored = localStorage.key(i), - value = localStorage.getItem(stored); + const stored = localStorage.key(i); + const value = localStorage.getItem(stored); + if (stored === "speakerVoice") continue; const input = document.getElementById(stored + "Input") || document.getElementById(stored); const output = document.getElementById(stored + "Output"); diff --git a/modules/ui/style.js b/modules/ui/style.js index 161636b6..44605bcc 100644 --- a/modules/ui/style.js +++ b/modules/ui/style.js @@ -92,9 +92,9 @@ function selectStyleElement() { if (sel === "terrs") { styleHeightmap.style.display = "block"; styleHeightmapScheme.value = terrs.attr("scheme"); - styleHeightmapTerracing.value = styleHeightmapTerracingOutput.value = terrs.attr("terracing"); - styleHeightmapSkip.value = styleHeightmapSkipOutput.value = terrs.attr("skip"); - styleHeightmapSimplification.value = styleHeightmapSimplificationOutput.value = terrs.attr("relax"); + styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = terrs.attr("terracing"); + styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = terrs.attr("skip"); + styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = terrs.attr("relax"); styleHeightmapCurve.value = terrs.attr("curve"); } @@ -419,20 +419,17 @@ styleHeightmapScheme.addEventListener("change", function () { drawHeightmap(); }); -styleHeightmapTerracing.addEventListener("input", function () { - styleHeightmapTerracingOutput.value = this.value; +styleHeightmapTerracingInput.addEventListener("input", function () { terrs.attr("terracing", this.value); drawHeightmap(); }); -styleHeightmapSkip.addEventListener("input", function () { - styleHeightmapSkipOutput.value = this.value; +styleHeightmapSkipInput.addEventListener("input", function () { terrs.attr("skip", this.value); drawHeightmap(); }); -styleHeightmapSimplification.addEventListener("input", function () { - styleHeightmapSimplificationOutput.value = this.value; +styleHeightmapSimplificationInput.addEventListener("input", function () { terrs.attr("relax", this.value); drawHeightmap(); }); diff --git a/modules/ui/units-editor.js b/modules/ui/units-editor.js index 9c47a0ef..fe12ecf9 100644 --- a/modules/ui/units-editor.js +++ b/modules/ui/units-editor.js @@ -15,16 +15,15 @@ function editUnits() { document.getElementById("distanceUnitInput").addEventListener("change", changeDistanceUnit); document.getElementById("distanceScaleOutput").addEventListener("input", changeDistanceScale); document.getElementById("distanceScaleInput").addEventListener("change", changeDistanceScale); - document.getElementById("areaUnit").addEventListener("change", () => lock("areaUnit")); document.getElementById("heightUnit").addEventListener("change", changeHeightUnit); document.getElementById("heightExponentInput").addEventListener("input", changeHeightExponent); document.getElementById("heightExponentOutput").addEventListener("input", changeHeightExponent); document.getElementById("temperatureScale").addEventListener("change", changeTemperatureScale); - document.getElementById("barSizeOutput").addEventListener("input", changeScaleBarSize); - document.getElementById("barSize").addEventListener("input", changeScaleBarSize); - document.getElementById("barLabel").addEventListener("input", changeScaleBarLabel); - document.getElementById("barPosX").addEventListener("input", changeScaleBarPosition); - document.getElementById("barPosY").addEventListener("input", changeScaleBarPosition); + document.getElementById("barSizeOutput").addEventListener("input", drawScaleBar); + document.getElementById("barSizeInput").addEventListener("input", drawScaleBar); + document.getElementById("barLabel").addEventListener("input", drawScaleBar); + document.getElementById("barPosX").addEventListener("input", fitScaleBar); + document.getElementById("barPosY").addEventListener("input", fitScaleBar); document.getElementById("barBackOpacity").addEventListener("input", changeScaleBarOpacity); document.getElementById("barBackColor").addEventListener("input", changeScaleBarColor); @@ -42,7 +41,7 @@ function editUnits() { function changeDistanceUnit() { if (this.value === "custom_name") { - prompt("Provide a custom name for a distance unit", {default:""}, custom => { + prompt("Provide a custom name for a distance unit", {default: ""}, custom => { this.options.add(new Option(custom, custom, false, true)); lock("distanceUnit"); drawScaleBar(); @@ -51,114 +50,55 @@ function editUnits() { return; } - lock("distanceUnit"); drawScaleBar(); calculateFriendlyGridSize(); } function changeDistanceScale() { - const scale = +this.value; - if (!scale || isNaN(scale) || scale < 0) { - tip("Distance scale should be a positive number", false, "error"); - this.value = document.getElementById("distanceScaleInput").dataset.value; - return; - } - - document.getElementById("distanceScaleOutput").value = scale; - document.getElementById("distanceScaleInput").value = scale; - document.getElementById("distanceScaleInput").dataset.value = scale; - lock("distanceScale"); - drawScaleBar(); calculateFriendlyGridSize(); } function changeHeightUnit() { - if (this.value === "custom_name") { - prompt("Provide a custom name for a height unit", {default:""}, custom => { - this.options.add(new Option(custom, custom, false, true)); - lock("heightUnit"); - }); - return; - } + if (this.value !== "custom_name") return; - lock("heightUnit"); + prompt("Provide a custom name for a height unit", {default: ""}, custom => { + this.options.add(new Option(custom, custom, false, true)); + lock("heightUnit"); + }); } function changeHeightExponent() { - document.getElementById("heightExponentInput").value = this.value; - document.getElementById("heightExponentOutput").value = this.value; calculateTemperatures(); if (layerIsOn("toggleTemp")) drawTemp(); - lock("heightExponent"); } function changeTemperatureScale() { - lock("temperatureScale"); if (layerIsOn("toggleTemp")) drawTemp(); } - function changeScaleBarSize() { - document.getElementById("barSize").value = this.value; - document.getElementById("barSizeOutput").value = this.value; - drawScaleBar(); - lock("barSize"); - } - - function changeScaleBarPosition() { - lock("barPosX"); - lock("barPosY"); - fitScaleBar(); - } - - function changeScaleBarLabel() { - lock("barLabel"); - drawScaleBar(); - } - function changeScaleBarOpacity() { scaleBar.select("rect").attr("opacity", this.value); - lock("barBackOpacity"); } function changeScaleBarColor() { scaleBar.select("rect").attr("fill", this.value); - lock("barBackColor"); } function changePopulationRate() { - const rate = +this.value; - if (!rate || isNaN(rate) || rate <= 0) { - tip("Population rate should be a positive number", false, "error"); - this.value = document.getElementById("populationRate").dataset.value; - return; - } - - document.getElementById("populationRateOutput").value = rate; - document.getElementById("populationRate").value = rate; - document.getElementById("populationRate").dataset.value = rate; - lock("populationRate"); + document.getElementById("populationRateOutput").value = this.value; + document.getElementById("populationRate").value = this.value; } function changeUrbanizationRate() { - const rate = +this.value; - if (!rate || isNaN(rate) || rate < 0) { - tip("Urbanization rate should be a number", false, "error"); - this.value = document.getElementById("urbanization").dataset.value; - return; - } - - document.getElementById("urbanizationOutput").value = rate; - document.getElementById("urbanization").value = rate; - document.getElementById("urbanization").dataset.value = rate; - lock("urbanization"); + document.getElementById("urbanizationOutput").value = this.value; + document.getElementById("urbanization").value = this.value; } function restoreDefaultUnits() { // distanceScale document.getElementById("distanceScaleOutput").value = 3; document.getElementById("distanceScaleInput").value = 3; - document.getElementById("distanceScaleInput").dataset.value = 3; unlock("distanceScale"); // units @@ -180,9 +120,9 @@ function editUnits() { calculateTemperatures(); // scale bar - barSizeOutput.value = barSize.value = 2; + barSizeOutput.value = barSizeInput.value = 2; barLabel.value = ""; - barBackOpacity.value = .2; + barBackOpacity.value = 0.2; barBackColor.value = "#ffffff"; barPosX.value = barPosY.value = 99; @@ -203,13 +143,13 @@ function editUnits() { function addRuler() { if (!layerIsOn("toggleRulers")) toggleRulers(); - const pt = document.getElementById('map').createSVGPoint(); - pt.x = graphWidth / 2, pt.y = graphHeight / 4; + const pt = document.getElementById("map").createSVGPoint(); + (pt.x = graphWidth / 2), (pt.y = graphHeight / 4); const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse()); const dx = graphWidth / 4 / scale; const dy = (rulers.data.length * 40) % (graphHeight / 2); - const from = [p.x-dx | 0, p.y+dy | 0]; - const to = [p.x+dx | 0, p.y+dy | 0]; + const from = [(p.x - dx) | 0, (p.y + dy) | 0]; + const to = [(p.x + dx) | 0, (p.y + dy) | 0]; rulers.create(Ruler, [from, to]).draw(); } @@ -223,23 +163,25 @@ function editUnits() { tip("Draw a curve to measure length. Hold Shift to disallow path optimization", true); unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed")); this.classList.add("pressed"); - viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() { - const point = d3.mouse(this); - const opisometer = rulers.create(Opisometer, [point]).draw(); - - d3.event.on("drag", function() { + viewbox.style("cursor", "crosshair").call( + d3.drag().on("start", function () { const point = d3.mouse(this); - opisometer.addPoint(point); - }); + const opisometer = rulers.create(Opisometer, [point]).draw(); - d3.event.on("end", function() { - restoreDefaultEvents(); - clearMainTip(); - addOpisometer.classList.remove("pressed"); - if (opisometer.points.length < 2) rulers.remove(opisometer.id); - if (!d3.event.sourceEvent.shiftKey) opisometer.optimize(); - }); - })); + d3.event.on("drag", function () { + const point = d3.mouse(this); + opisometer.addPoint(point); + }); + + d3.event.on("end", function () { + restoreDefaultEvents(); + clearMainTip(); + addOpisometer.classList.remove("pressed"); + if (opisometer.points.length < 2) rulers.remove(opisometer.id); + if (!d3.event.sourceEvent.shiftKey) opisometer.optimize(); + }); + }) + ); } } @@ -253,40 +195,42 @@ function editUnits() { tip("Draw a curve along routes to measure length. Hold Shift to measure away from roads.", true); unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed")); this.classList.add("pressed"); - viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() { - const cells = pack.cells; - const burgs = pack.burgs; - const point = d3.mouse(this); - const c = findCell(point[0], point[1]); - if (cells.road[c] || d3.event.sourceEvent.shiftKey) { - const b = cells.burg[c]; - const x = b ? burgs[b].x : cells.p[c][0]; - const y = b ? burgs[b].y : cells.p[c][1]; - const routeOpisometer = rulers.create(RouteOpisometer, [[x, y]]).draw(); + viewbox.style("cursor", "crosshair").call( + d3.drag().on("start", function () { + const cells = pack.cells; + const burgs = pack.burgs; + const point = d3.mouse(this); + const c = findCell(point[0], point[1]); + if (cells.road[c] || d3.event.sourceEvent.shiftKey) { + const b = cells.burg[c]; + const x = b ? burgs[b].x : cells.p[c][0]; + const y = b ? burgs[b].y : cells.p[c][1]; + const routeOpisometer = rulers.create(RouteOpisometer, [[x, y]]).draw(); - d3.event.on("drag", function () { - const point = d3.mouse(this); - const c = findCell(point[0], point[1]); - if (cells.road[c] || d3.event.sourceEvent.shiftKey) { - routeOpisometer.trackCell(c, true); - } - }); + d3.event.on("drag", function () { + const point = d3.mouse(this); + const c = findCell(point[0], point[1]); + if (cells.road[c] || d3.event.sourceEvent.shiftKey) { + routeOpisometer.trackCell(c, true); + } + }); - d3.event.on("end", function () { + d3.event.on("end", function () { + restoreDefaultEvents(); + clearMainTip(); + addRouteOpisometer.classList.remove("pressed"); + if (routeOpisometer.points.length < 2) { + rulers.remove(routeOpisometer.id); + } + }); + } else { restoreDefaultEvents(); clearMainTip(); addRouteOpisometer.classList.remove("pressed"); - if (routeOpisometer.points.length < 2) { - rulers.remove(routeOpisometer.id); - } - }); - } else { - restoreDefaultEvents(); - clearMainTip(); - addRouteOpisometer.classList.remove("pressed"); - tip("Must start in a cell with a route in it", false, "error"); - } - })); + tip("Must start in a cell with a route in it", false, "error"); + } + }) + ); } } @@ -300,24 +244,25 @@ function editUnits() { tip("Draw a curve to measure its area. Hold Shift to disallow path optimization", true); unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed")); this.classList.add("pressed"); - viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() { - const point = d3.mouse(this); - const planimeter = rulers.create(Planimeter, [point]).draw(); - - d3.event.on("drag", function() { + viewbox.style("cursor", "crosshair").call( + d3.drag().on("start", function () { const point = d3.mouse(this); - planimeter.addPoint(point); - }); + const planimeter = rulers.create(Planimeter, [point]).draw(); - d3.event.on("end", function() { - restoreDefaultEvents(); - clearMainTip(); - addPlanimeter.classList.remove("pressed"); - if (planimeter.points.length < 3) rulers.remove(planimeter.id); - else if (!d3.event.sourceEvent.shiftKey) planimeter.optimize(); - }); - })); + d3.event.on("drag", function () { + const point = d3.mouse(this); + planimeter.addPoint(point); + }); + d3.event.on("end", function () { + restoreDefaultEvents(); + clearMainTip(); + addPlanimeter.classList.remove("pressed"); + if (planimeter.points.length < 3) rulers.remove(planimeter.id); + else if (!d3.event.sourceEvent.shiftKey) planimeter.optimize(); + }); + }) + ); } } @@ -326,18 +271,19 @@ function editUnits() { alertMessage.innerHTML = ` Are you sure you want to remove all placed rulers?
    If you just want to hide rulers, toggle the Rulers layer off in Menu`; - $("#alert").dialog({resizable: false, title: "Remove all rulers", + $("#alert").dialog({ + resizable: false, + title: "Remove all rulers", buttons: { - Remove: function() { + Remove: function () { $(this).dialog("close"); rulers.undraw(); rulers = new Rulers(); }, - Cancel: function() {$(this).dialog("close");} + Cancel: function () { + $(this).dialog("close"); + } } }); } - - } - From 5ed08e156a838323dd8e5ddb4949efad25d04513 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 27 Jun 2021 14:24:55 +0300 Subject: [PATCH 30/34] store populationRate and urbanization in memory --- index.html | 8 +- main.js | 5 +- modules/load.js | 4 +- modules/military-generator.js | 302 ++++++++----- modules/save.js | 2 +- modules/ui/battle-screen.js | 362 ++++++++------- modules/ui/biomes-editor.js | 168 ++++--- modules/ui/burg-editor.js | 209 ++++++--- modules/ui/burgs-overview.js | 275 ++++++++---- modules/ui/cultures-editor.js | 377 ++++++++++------ modules/ui/elevation-profile.js | 197 ++++++--- modules/ui/general.js | 6 +- modules/ui/layers.js | 758 +++++++++++++++++++++++--------- modules/ui/military-overview.js | 135 +++--- modules/ui/provinces-editor.js | 501 +++++++++++++-------- modules/ui/religions-editor.js | 362 +++++++++------ modules/ui/states-editor.js | 507 +++++++++++++-------- modules/ui/units-editor.js | 14 +- modules/ui/zones-editor.js | 230 ++++++---- 19 files changed, 2870 insertions(+), 1552 deletions(-) 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(); } - } From f2f170ff29bd5e75e6bf3d2a392e176a04f4cd39 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Tue, 29 Jun 2021 00:59:58 +0300 Subject: [PATCH 31/34] fix lake elevation limits --- modules/river-generator.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/river-generator.js b/modules/river-generator.js index d34c0a0e..f1f35900 100644 --- a/modules/river-generator.js +++ b/modules/river-generator.js @@ -187,8 +187,8 @@ const resolveDepressions = function (h) { const {cells, features} = pack; const maxIterations = +document.getElementById("resolveDepressionsStepsOutput").value; - const checkLakeMaxIteration = maxIterations * 0.8; - const elevateLakeMaxIteration = (maxIterations - checkLakeMaxIteration) / 2; + const checkLakeMaxIteration = maxIterations * 0.85; + const elevateLakeMaxIteration = maxIterations * 0.75; const height = i => features[cells.f[i]].height || h[i]; // height of lake or specific cell @@ -215,7 +215,7 @@ const minHeight = d3.min(l.shoreline.map(s => h[s])); if (minHeight >= 100 || l.height > minHeight) continue; - if (iteration < elevateLakeMaxIteration) { + if (iteration > elevateLakeMaxIteration) { l.shoreline.forEach(i => (h[i] = cells.h[i])); l.height = d3.min(l.shoreline.map(s => h[s])) - 1; l.closed = true; From 05700cc338b43d575cdfa03dc368722918c7e89f Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 1 Jul 2021 21:00:49 +0300 Subject: [PATCH 32/34] allow to style text-shadow for labels --- index.css | 1 - index.html | 11 ++++++++++- main.js | 2 +- modules/load.js | 24 +++++++++++++++++++----- modules/ui/style.js | 45 ++++++++++++++++++++++++++++++--------------- 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/index.css b/index.css index ab9cea5d..be4e2143 100644 --- a/index.css +++ b/index.css @@ -239,7 +239,6 @@ i.icon-lock { #labels { text-anchor: start; dominant-baseline: central; - text-shadow: 0 0 4px white; cursor: pointer; } diff --git a/index.html b/index.html index eb8a3e97..ad8f238f 100644 --- a/index.html +++ b/index.html @@ -234,7 +234,7 @@
    Azgaar's
    Fantasy Map Generator
    -
    v. 1.62
    +
    v. 1.63

    LOADING...

    @@ -669,6 +669,15 @@ + + + Text shadow + + + + + + Font diff --git a/main.js b/main.js index 091acf11..538b1440 100644 --- a/main.js +++ b/main.js @@ -2,7 +2,7 @@ // https://github.com/Azgaar/Fantasy-Map-Generator "use strict"; -const version = "1.62"; // generator version +const version = "1.63"; // generator version document.title += " v" + version; // Switches to disable/enable logging features diff --git a/modules/load.js b/modules/load.js index f723acfe..ca5bf23b 100644 --- a/modules/load.js +++ b/modules/load.js @@ -674,12 +674,26 @@ function parseLoadedData(data) { const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : ""; pattern.innerHTML = ``; } - })(); - if (version < 1.62) { - // v 1.62 changed grid data - gridOverlay.attr("size", null); - } + if (version < 1.62) { + // v 1.62 changed grid data + gridOverlay.attr("size", null); + } + + if (version < 1.63) { + // v.1.63 change ocean pattern opacity element + const oceanPattern = document.getElementById("oceanPattern"); + if (oceanPattern) oceanPattern.removeAttribute("opacity"); + const oceanicPattern = document.getElementById("oceanicPattern"); + if (!oceanicPattern.getAttribute("opacity")) oceanicPattern.setAttribute("opacity", 0.2); + + // v 1.63 moved label text-shadow from css to editable inline style + burgLabels.select("#cities").style("text-shadow", "white 0 0 4px"); + burgLabels.select("#towns").style("text-shadow", "white 0 0 4px"); + labels.select("#states").style("text-shadow", "white 0 0 4px"); + labels.select("#addedLabels").style("text-shadow", "white 0 0 4px"); + } + })(); void (function checkDataIntegrity() { const cells = pack.cells; diff --git a/modules/ui/style.js b/modules/ui/style.js index 44605bcc..d986bc28 100644 --- a/modules/ui/style.js +++ b/modules/ui/style.js @@ -147,11 +147,13 @@ function selectStyleElement() { styleStrokeWidth.style.display = "block"; loadDefaultFonts(); styleFont.style.display = "block"; + styleShadow.style.display = "block"; styleSize.style.display = "block"; styleVisibility.style.display = "block"; styleFillInput.value = styleFillOutput.value = el.attr("fill") || "#3e3e4b"; styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke") || "#3a3a3a"; styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || 0; + styleShadowInput.value = el.style("text-shadow") || "white 0 0 4px"; styleSelectFont.value = fonts.indexOf(el.attr("data-font")); styleInputFont.style.display = "none"; styleInputFont.value = ""; @@ -531,6 +533,10 @@ function changeFont() { if (styleElementSelect.value === "legend") redrawLegend(); } +styleShadowInput.addEventListener("input", function () { + getEl().style("text-shadow", this.value); +}); + styleFontAdd.addEventListener("click", function () { if (styleInputFont.style.display === "none") { styleInputFont.style.display = "inline-block"; @@ -716,10 +722,10 @@ function fetchTextureURL(url) { } const defaultStyles = { - styleAncient: `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#emblems":{"opacity":0.6,"stroke-width":0.8,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanicPattern":{"href":"./images/pattern1.png", "opacity":0.2},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`, - styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanicPattern":{"href":"./images/pattern3.png", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, - styleClean: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#emblems":{"opacity":1,"stroke-width":1,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`, - styleMonochrome: `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#emblems": {"opacity": 0.5,"stroke-width": 0.5,"filter": null},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}` + styleAncient: `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#emblems":{"opacity":0.6,"stroke-width":0.8,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanicPattern":{"href":"./images/pattern1.png", "opacity":0.2},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","text-shadow":"white 0 0 4px","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"text-shadow":"white 0 0 4px","data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"text-shadow":"white 0 0 4px","data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`, + styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanicPattern":{"href":"./images/pattern3.png", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","text-shadow":"white 0 0 4px","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"text-shadow":"white 0 0 4px","data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"text-shadow":"white 0 0 4px","data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, + styleClean: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#emblems":{"opacity":1,"stroke-width":1,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","text-shadow":"white 0 0 4px","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"text-shadow":"white 0 0 2px","data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"text-shadow":"white 0 0 4px","data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`, + styleMonochrome: `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"scale":1,"dx":0,"dy":"0","type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.7,"fill":"#000000","data-size":10,"font-size":10,"font-family":"Georgia","data-font":"Georgia","filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#emblems": {"opacity": 0.5,"stroke-width": 0.5,"filter": null},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanicPattern":{"href":"", "opacity":0.2},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","text-shadow":"white 0 0 4px","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"text-shadow":"white 0 0 4px","data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"text-shadow":"white 0 0 4px","data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}` }; // apply default or custom style settings on load @@ -807,17 +813,17 @@ function applyDefaultStyle() { legend.select("#legendBox").attr("fill", "#ffffff").attr("fill-opacity", 0.8); const citiesSize = Math.max(rn(8 - regionsInput.value / 20), 3); - burgLabels.select("#cities").attr("fill", "#3e3e4b").attr("opacity", 1).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", citiesSize).attr("data-size", citiesSize); + burgLabels.select("#cities").attr("fill", "#3e3e4b").attr("opacity", 1).style("text-shadow", "white 0 0 4px").attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", citiesSize).attr("data-size", citiesSize); burgIcons.select("#cities").attr("opacity", 1).attr("size", 1).attr("stroke-width", 0.24).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("fill-opacity", 0.7).attr("stroke-dasharray", "").attr("stroke-linecap", "butt"); anchors.select("#cities").attr("opacity", 1).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("stroke-width", 1.2).attr("size", 2); - burgLabels.select("#towns").attr("fill", "#3e3e4b").attr("opacity", 1).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 3).attr("data-size", 4); + burgLabels.select("#towns").attr("fill", "#3e3e4b").attr("opacity", 1).style("text-shadow", "white 0 0 4px").attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 3).attr("data-size", 4); burgIcons.select("#towns").attr("opacity", 1).attr("size", 0.5).attr("stroke-width", 0.12).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("fill-opacity", 0.7).attr("stroke-dasharray", "").attr("stroke-linecap", "butt"); anchors.select("#towns").attr("opacity", 1).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("stroke-width", 1.2).attr("size", 1); const stateLabelSize = Math.max(rn(24 - regionsInput.value / 6), 6); - labels.select("#states").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", stateLabelSize).attr("data-size", stateLabelSize).attr("filter", null); - labels.select("#addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null); + labels.select("#states").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).style("text-shadow", "white 0 0 4px").attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", stateLabelSize).attr("data-size", stateLabelSize).attr("filter", null); + labels.select("#addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).style("text-shadow", "white 0 0 4px").attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null); fogging.attr("opacity", 0.98).attr("fill", "#30426f"); emblems.attr("opacity", 0.9).attr("stroke-width", 1).attr("filter", null); @@ -830,8 +836,17 @@ function applyStyle(style) { if (!el) continue; for (const attribute in style[selector]) { const value = style[selector][attribute]; - if (value === "null" || value === null) el.removeAttribute(attribute); - else el.setAttribute(attribute, value); + + if (value === "null" || value === null) { + el.removeAttribute(attribute); + continue; + } + + if (attribute === "text-shadow") { + el.style[attribute] = value; + } else { + el.setAttribute(attribute, value); + } } } } @@ -988,14 +1003,14 @@ function addStylePreset() { "#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"], "#legend": ["data-size", "font-size", "data-font", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"], "#legendBox": ["fill", "fill-opacity"], - "#burgLabels > #cities": ["opacity", "fill", "data-size", "font-size", "data-font", "font-family"], + "#burgLabels > #cities": ["opacity", "fill", "text-shadow", "data-size", "font-size", "data-font", "font-family"], "#burgIcons > #cities": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"], "#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"], - "#burgLabels > #towns": ["opacity", "fill", "data-size", "font-size", "data-font", "font-family"], + "#burgLabels > #towns": ["opacity", "fill", "text-shadow", "data-size", "font-size", "data-font", "font-family"], "#burgIcons > #towns": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"], "#anchors > #towns": ["opacity", "fill", "size", "stroke", "stroke-width"], - "#labels > #states": ["opacity", "fill", "stroke", "stroke-width", "data-size", "font-size", "data-font", "font-family", "filter"], - "#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "data-size", "font-size", "data-font", "font-family", "filter"], + "#labels > #states": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "data-font", "font-family", "filter"], + "#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "data-font", "font-family", "filter"], "#fogging": ["opacity", "fill", "filter"] }; @@ -1004,7 +1019,7 @@ function addStylePreset() { attributes[selector].forEach(attr => { const el = document.querySelector(selector); if (!el) return; - let value = el.getAttribute(attr); + let value = el.getAttribute(attr) || el.style[attr]; if (attr === "font-size" && el.hasAttribute("data-size")) value = el.getAttribute("data-size"); s[attr] = parseValue(value); }); From ba8a529c9eacf9b2a148090459e05e9f3655f28e Mon Sep 17 00:00:00 2001 From: Azgaar Date: Thu, 1 Jul 2021 23:24:38 +0300 Subject: [PATCH 33/34] enter debug mode from localStorage --- main.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/main.js b/main.js index 538b1440..c53c5f65 100644 --- a/main.js +++ b/main.js @@ -7,10 +7,11 @@ document.title += " v" + version; // Switches to disable/enable logging features const PRODUCTION = window.location.host; -const INFO = !PRODUCTION; -const TIME = !PRODUCTION; -const WARN = 1; -const ERROR = 1; +const DEBUG = localStorage.getItem("debug"); +const INFO = DEBUG || !PRODUCTION; +const TIME = DEBUG || !PRODUCTION; +const WARN = true; +const ERROR = true; // if map version is not stored, clear localStorage and show a message if (rn(localStorage.getItem("version"), 1) !== rn(version, 1)) { @@ -776,7 +777,7 @@ function markup(cells, start, increment, limit) { } function addLakesInDeepDepressions() { - console.time("addLakesInDeepDepressions"); + TIME && console.time("addLakesInDeepDepressions"); const {cells, features} = grid; const {c, h, b} = cells; const ELEVATION_LIMIT = +document.getElementById("lakeElevationLimitOutput").value; @@ -831,7 +832,7 @@ function addLakesInDeepDepressions() { features.push({i: f, land: false, border: false, type: "lake"}); } - console.timeEnd("addLakesInDeepDepressions"); + TIME && console.timeEnd("addLakesInDeepDepressions"); } // near sea lakes usually get a lot of water inflow, most of them should brake threshold and flow out to sea (see Ancylus Lake) From 44b3911e65ed329f857b86ee1ce0b84f6b1aab3d Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 5 Jul 2021 18:56:35 +0300 Subject: [PATCH 34/34] update supporters list --- modules/ui/options.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui/options.js b/modules/ui/options.js index 9aa77a5a..2a9fe433 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -97,7 +97,8 @@ function showSupporters() { Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,Cooper Counts,Patrick Jones,Clonetone, PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram, Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee, - Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber`; + Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth, + Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray`; const array = supporters .replace(/(?:\r\n|\r|\n)/g, "")