From 6214269a918c4dea98470522ad142f3eb7741014 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sat, 5 Dec 2020 19:55:08 +0300 Subject: [PATCH] v1.4.48 --- .gitignore | 2 + Fantasy Map Generator.lnk | Bin 2304 -> 0 bytes README.md | 12 ++-- _config.yml | 1 - index.css | 7 +- index.html | 12 +--- modules/save-and-load.js | 142 +++++++++++++++++--------------------- modules/ui/general.js | 72 ++++++++++++++----- modules/ui/options.js | 6 +- modules/utils.js | 33 ++++----- 10 files changed, 154 insertions(+), 133 deletions(-) create mode 100644 .gitignore delete mode 100644 Fantasy Map Generator.lnk delete mode 100644 _config.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1104b06f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +run_php_server.bat +.vscode \ No newline at end of file diff --git a/Fantasy Map Generator.lnk b/Fantasy Map Generator.lnk deleted file mode 100644 index 1e1f20124b03a3e63bebe5c0f83cf3ac117927cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2304 zcmeZaU|?VrVPXJ*10aHd!R@vK2ZI6w0|S@cdY2%MYmB|xEWaMPBWQX9UietSR10#bP!~1=L z3=9mjJb12t{dtw~f4%dugZ9@L?FHjy>jbYciZFyQ7=jJ&4*1zAz~G));!&E!z+lI~ z$-u(!eqVmT%3_d=9s>h|WD3YFqF~hs!p8GFgF8beLkWWiLn%WN0|SE?LkZN(jREP) z1sL2M{X!gr>kJtfVxXor227s>k^`BV%+JEWz`%_#5X=hp31DzzNMy)kC}BusC}yZ+ z&}HytNMtBr&}DFENM*=lNM$HuNMtBs$Y&^GU|^77NMJBx0EOArfHk!W44!^2t`T*H zdLALZ3~q2+XHEjif^5~94Dw3?*e(Q79A?On$&km8!jQ^P!Jx;G!BE1G%aFssz#zrI z0H$R?G0DKd0HSrF8nYP~7(ijn%)r3#DN6K(07%@~DkiizwWv78qaem5Ke;qFHLs*N z1`@n6Zi#s%iN%$=zKI38?x}gHMTsT(MKPIqDXA5D86~+n3=BLBdJHjG-D1Sx%wWY3 z!w||)3=V^0h8PA9h608d1{a2WhGd3PhFoy?lrVr~kYgr>gb;P2i!C4@<}xHQq%)*~ zU7N{}%#hEJ$DqdmVS(a@nSo(J1Smuq7#I?((i0Pl3X-8*1_{egb&VNaE;IdBRjEJX z*w%V}aq|65uLPGWvGCbU=1UC1eSheO6ZyQ zdW|M1e}XKNhbB?%iGzUwlqvW@3LQX1Zj~!2M}q`;KztAblr%wXTLuOOP=aRw83~eb zX2@VDV#o)_ngW9dLkNQ}gAaoOMr?vKgVghZ7$9q41qO(}66%3vS;-o>3mhjAO?R1HwI4z9|l(jRVtTMgvyYi8-l~f button { - padding: 0; + padding: .3em; } #brushesButtons svg { @@ -1088,6 +1088,7 @@ i.resetButton:active { box-shadow: inset 1px 1px 0 0 #ccc; border-color: #a6a6da; background-color: #ecd8d8; + border-radius: 10%; } .ui-dialog input[type="range"] { @@ -1187,7 +1188,7 @@ div.slider .ui-slider-handle { #brushPower, #brushRadius { - width: 8em; + width: 12em; } #rescaleHigher, @@ -1261,7 +1262,7 @@ div.states { line-height: 1.5em; } -div.states:hover { +div.states:hover, div.states.hovered { border: 1px solid #c4c4c4; background-image: linear-gradient(to right, #dedede 100%, #f2f2f2 50%, #fcfcfc 0%); } diff --git a/index.html b/index.html index 963a72d8..e94c3839 100644 --- a/index.html +++ b/index.html @@ -7,8 +7,6 @@ - - @@ -34,13 +32,9 @@ #loading-text span:nth-child(3), #mapOverlay > span:nth-child(3) {animation-delay: 2s;} @keyframes blink {0% {opacity: 0;} 20% {opacity: 1;} 100% {opacity: .1;}} - - - - @@ -2649,8 +2643,8 @@ -
+
diff --git a/modules/save-and-load.js b/modules/save-and-load.js index 7ee574a5..4cd42ad8 100644 --- a/modules/save-and-load.js +++ b/modules/save-and-load.js @@ -286,7 +286,6 @@ function getMapData() { TIME && console.timeEnd("createMapDataBlob"); resolve(blob); }); - } // Download .map file @@ -306,116 +305,103 @@ async function saveMap() { } function saveGeoJSON_Cells() { - let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; - const cells = pack.cells, v = pack.vertices; + 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 => { - data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [["; - cells.v[i].forEach(n => { - let x = mapCoordinates.lonW + (v.p[n][0] / graphWidth) * mapCoordinates.lonT; - let y = mapCoordinates.latN - (v.p[n][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise - data += "["+x+","+y+"],"; - }); - // close the ring - let x = mapCoordinates.lonW + (v.p[cells.v[i][0]][0] / graphWidth) * mapCoordinates.lonT; - let y = mapCoordinates.latN - (v.p[cells.v[i][0]][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise - data += "["+x+","+y+"]"; - data += "]] },\n \"properties\": {\n"; + const coordinates = getCellPoints(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 height = parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]])); - - data += " \"id\": \""+i+"\",\n"; - data += " \"height\": \""+height+"\",\n"; - data += " \"biome\": \""+cells.biome[i]+"\",\n"; - data += " \"type\": \""+pack.features[cells.f[i]].type+"\",\n"; - data += " \"population\": \""+getPopulation(i)+"\",\n"; - data += " \"state\": \""+cells.state[i]+"\",\n"; - data += " \"province\": \""+cells.province[i]+"\",\n"; - data += " \"culture\": \""+cells.culture[i]+"\",\n"; - data += " \"religion\": \""+cells.religion[i]+"\",\n"; - data += " \"neighbors\": ["+cells.c[i]+"]\n"; - data +=" }\n},\n"; + 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); }); - data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma - data += "]}"; - const name = getFileName("Cells") + ".geojson"; - downloadFile(data, name, "application/json"); + downloadFile(JSON.stringify(json), name, "application/json"); } -function saveGeoJSON_Roads() { - let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; +function saveGeoJSON_Routes() { + const json = {type: "FeatureCollection", features: []}; - routes._groups[0][0].childNodes.forEach(n => { - n.childNodes.forEach(r => { - data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"LineString\", \"coordinates\": "; - data += JSON.stringify(getRoadPoints(r)); - data += " },\n \"properties\": {\n"; - data += " \"id\": \""+r.id+"\",\n"; - data += " \"type\": \""+n.id+"\"\n"; - data +=" }\n},\n"; - }); + 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); }); - data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma - data += "]}"; const name = getFileName("Routes") + ".geojson"; - downloadFile(data, name, "application/json"); + downloadFile(JSON.stringify(json), name, "application/json"); } function saveGeoJSON_Rivers() { - let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; + const json = {type: "FeatureCollection", features: []}; - rivers._groups[0][0].childNodes.forEach(n => { - data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"LineString\", \"coordinates\": "; - data += JSON.stringify(getRiverPoints(n)); - data += " },\n \"properties\": {\n"; - data += " \"id\": \""+n.id+"\",\n"; - data += " \"width\": \""+n.dataset.width+"\",\n"; - data += " \"increment\": \""+n.dataset.increment+"\"\n"; - data +=" }\n},\n"; + 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); }); - data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma - data += "]}"; const name = getFileName("Rivers") + ".geojson"; - downloadFile(data, name, "application/json"); + downloadFile(JSON.stringify(json), name, "application/json"); } function saveGeoJSON_Markers() { - let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; + const json = {type: "FeatureCollection", features: []}; - markers._groups[0][0].childNodes.forEach(n => { - let x = mapCoordinates.lonW + (n.dataset.x / graphWidth) * mapCoordinates.lonT; - let y = mapCoordinates.latN - (n.dataset.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise - - data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Point\", \"coordinates\": ["+x+", "+y+"]"; - data += " },\n \"properties\": {\n"; - data += " \"id\": \""+n.id+"\",\n"; - data += " \"type\": \""+n.dataset.id.substring(8)+"\"\n"; - data +=" }\n},\n"; + 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); }); - data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma - data += "]}"; const name = getFileName("Markers") + ".geojson"; - downloadFile(data, name, "application/json"); + downloadFile(JSON.stringify(json), name, "application/json"); } -function getRoadPoints(node) { +function getCellPoints(vertices) { + const p = pack.vertices.p; + const points = vertices.map(n => getQGIScoordinates(p[n][0] / graphWidth, p[n][1] / graphHeight)); + return points.concat([points[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); - - let x = mapCoordinates.lonW + (p.x / graphWidth) * mapCoordinates.lonT; - let y = mapCoordinates.latN - (p.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise - - points.push([x,y]); + points.push(getQGIScoordinates(p.x, p.y)); } return points; } @@ -427,9 +413,7 @@ function getRiverPoints(node) { for (let i=l, c=i; i >= 0; i -= increment, c += increment) { const p1 = node.getPointAtLength(i); const p2 = node.getPointAtLength(c); - - let x = mapCoordinates.lonW + (((p1.x+p2.x)/2) / graphWidth) * mapCoordinates.lonT; - let y = mapCoordinates.latN - (((p1.y+p2.y)/2) / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise + const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); points.push([x,y]); } return points; diff --git a/modules/ui/general.js b/modules/ui/general.js index 7689b1b5..ca6ee9f7 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -27,7 +27,7 @@ function tip(tip = "Tip is undefined", main, type, time) { 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() { @@ -49,7 +49,8 @@ function showDataTip(e) { tip(dataTip); } -function moved() { +const moved = debounce(mouseMove, 100); +function mouseMove() { const point = d3.mouse(this); const i = findCell(point[0], point[1]); // pack cell id if (i === undefined) return; @@ -89,11 +90,28 @@ function showMapTooltip(point, e, i, g) { const land = pack.cells.h[i] >= 20; // specific elements - if (group === "armies") {tip(e.target.parentNode.dataset.name + ". Click to edit"); return;} - if (group === "rivers") {tip(getRiverName(e.target.id) + "Click to edit"); return;} + if (group === "armies") { + tip(e.target.parentNode.dataset.name + ". Click to edit"); + return; + } + if (group === "rivers") { + const river = +e.target.id.slice(5); + const r = pack.rivers.find(r => r.i === river); + const name = r ? r.name + " " + r.type : ""; + tip(name + ". Click to edit"); + 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 (subgroup === "burgLabels" || subgroup === "burgIcons") {tip("Click to open Burg Editor"); return;} + 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); + tip(`${b.name}. Population: ${population}. Click to edit`); + 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 === "ruler") { @@ -106,32 +124,54 @@ function showMapTooltip(point, e, i, g) { if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;} if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;} if (group === "coastline") {tip("Click to edit the coastline"); return;} - if (group === "zones") {tip(path[path.length-8].dataset.description); return;} + if (group === "zones") { + 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;} // 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]) tip("Biome: " + biomesData.name[pack.cells.biome[i]]); 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]) { - const religion = pack.religions[pack.cells.religion[i]]; - const type = religion.type === "Cult" || religion.type == "Heresy" ? religion.type : religion.type + " religion"; - tip(type + ": " + religion.name); + 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"))) { - const state = pack.states[pack.cells.state[i]].fullName; + const state = pack.cells.state[i]; + const stateName = pack.states[state].fullName; const province = pack.cells.province[i]; const prov = province ? pack.provinces[province].fullName + ", " : ""; - tip(prov + state); + tip(prov + stateName); + if (statesEditor.offsetParent) highlightEditorLine(statesEditor, state); + 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]) { + const culture = pack.cells.culture[i]; + tip("Culture: " + pack.cultures[culture].name); + if (culturesEditor.offsetParent) highlightEditorLine(culturesEditor, culture); } else - if (layerIsOn("toggleCultures") && pack.cells.culture[i]) tip("Culture: " + pack.cultures[pack.cells.culture[i]].name); else if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point)); } -function getRiverName(id) { - const r = pack.rivers.find(r => r.i == id.slice(5)); - return r ? r.name + " " + r.type + ". " : ""; +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.classList.remove("hovered"), timeout); } // get cell info on mouse move diff --git a/modules/ui/options.js b/modules/ui/options.js index b46b541d..4abe22ea 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -86,7 +86,9 @@ function showSupporters() { Seth Fusion,Adam Butler,Gus,StroboWolf,Sadie Blackthorne,Zewen Senpai,Dell McKnight,Oneiris,Darinius Dragonclaw Studios,Christopher Whitney,Rhodes HvZ, Jeppe Skov Jensen,María Martín López,Martin Seeger,Annie Rishor,Aram Sabatés,MadNomadMedia,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta, Thirty-OneR ,ThatGuyGW ,Dee Chiu,MontyBoosh ,Achillain ,Jaden ,SashaTK,Steve Johnson,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,Thirty-OneR, - ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill`; + ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill, + Char, Jack, Barna Csíkos, Ian Rousseau, Nicholas Grabstas, Tom Van Orden jr, Bryan Brake, Akylos, Riley Seaman`; + const array = supporters.replace(/(?:\r\n|\r|\n)/g, "").split(",").map(v => capitalize(v.trim())).sort(); alertMessage.innerHTML = "
    " + array.map(n => `
  • ${n}
  • `).join("") + "
"; $("#alert").dialog({resizable: false,title: "Patreon Supporters",width: "54vw",position: {my: "center",at: "center",of: "svg"}}); @@ -481,7 +483,7 @@ function saveGeoJSON() { $("#alert").dialog({title: "GIS data export", resizable: false, width: "35em", position: {my: "center", at: "center", of: "svg"}, buttons: { Cells: saveGeoJSON_Cells, - Routes: saveGeoJSON_Roads, + Routes: saveGeoJSON_Routes, Rivers: saveGeoJSON_Rivers, Markers: saveGeoJSON_Markers, Close: function() {$(this).dialog("close");} diff --git a/modules/utils.js b/modules/utils.js index 352c7b59..005d0738 100644 --- a/modules/utils.js +++ b/modules/utils.js @@ -516,26 +516,15 @@ function getNextId(core, i = 1) { return core + i; } -// from https://davidwalsh.name/javascript-debounce-function -function debounce(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) func.apply(context, args); - } - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - } -} +function debounce(f, ms) { + let isCooldown = false; -// pause/block JS execution for a while -function sleep(delay) { - const start = new Date().getTime(); - while (new Date().getTime() < start + delay); + return function() { + if (isCooldown) return; + f.apply(this, arguments); + isCooldown = true; + setTimeout(() => isCooldown = false, ms); + }; } // parse error to get the readable string in Chrome and Firefox @@ -597,6 +586,12 @@ function generateDate(from = 100, to = 1000) { return new Date(rand(from, to),rand(12),rand(31)).toLocaleDateString("en", {year:'numeric', month:'long', day:'numeric'}); } +function getQGIScoordinates(x, y) { + const cx = mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT; + const cy = mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise + return [cx, cy]; +} + // prompt replacer (prompt does not work in Electron) void function() { const prompt = document.getElementById("prompt");