This commit is contained in:
Azgaar 2020-12-05 19:55:08 +03:00
parent 4920ccccb4
commit 554f51e463
10 changed files with 154 additions and 133 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
run_php_server.bat
.vscode

Binary file not shown.

View file

@ -1,10 +1,10 @@
# Fantasy Map Generator # Fantasy Map Generator
Azgaar's _Fantasy Map Generator_. Online tool generating interactive and highly customizable svg maps based on voronoi diagram. Azgaar's _Fantasy Map Generator_ is a free client-side web application generating interactive and highly customizable svg maps based on voronoi diagram.
Project is under development, check out the current version [here](https://azgaar.github.io/Fantasy-Map-Generator). You can also try an Electron desktop application - download [an archive](https://github.com/Azgaar/Fantasy-Map-Generator/releases) for your architecture, unzip and run the _Azgaar's Fantasy Map Generator.exe_. Project is under development, the current version is available on [Github Pages](https://azgaar.github.io/Fantasy-Map-Generator).
Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for a guidance. Some details are covered in my blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com), you may also keep an eye on my [Trello devboard](https://trello.com/b/7x832DG4/fantasy-map-generator). Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for a guidance. Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com), you may also keep an eye on my [Trello devboard](https://trello.com/b/7x832DG4/fantasy-map-generator).
[![preview](https://cdn.discordapp.com/attachments/587406457725779968/594840629213659136/preview1.png)](https://i.redd.it/8bf81ir2cy631.png) [![preview](https://cdn.discordapp.com/attachments/587406457725779968/594840629213659136/preview1.png)](https://i.redd.it/8bf81ir2cy631.png)
@ -14,7 +14,11 @@ Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki
Join our [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) and [Discord server](https://discordapp.com/invite/X7E84HU) to share the created maps, discuss the Generator, suggest ideas and get a most recent updates. You may also contact me directly via [email](mailto:azgaar.fmg@yandex.by). For bug reports please use the project [issues page](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or Discord "Bugs" channel. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips). Join our [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) and [Discord server](https://discordapp.com/invite/X7E84HU) to share the created maps, discuss the Generator, suggest ideas and get a most recent updates. You may also contact me directly via [email](mailto:azgaar.fmg@yandex.by). For bug reports please use the project [issues page](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or Discord "Bugs" channel. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips).
You can support the project [on Patreon](https://www.patreon.com/azgaar). Electron desktop application is available in [releases](https://github.com/Azgaar/Fantasy-Map-Generator/releases). Download archive for your architecture, unzip and run.
Pull requests are welcomed. The Tool codebase is messy and requires re-design, but I will appreciate if you start with minor changes.
You can support the project on [Patreon](https://www.patreon.com/azgaar).
_Inspiration:_ _Inspiration:_

View file

@ -1 +0,0 @@
theme: jekyll-theme-slate

View file

@ -833,7 +833,7 @@ body button.noicon {
} }
#brushesButtons > button { #brushesButtons > button {
padding: 0; padding: .3em;
} }
#brushesButtons svg { #brushesButtons svg {
@ -1088,6 +1088,7 @@ i.resetButton:active {
box-shadow: inset 1px 1px 0 0 #ccc; box-shadow: inset 1px 1px 0 0 #ccc;
border-color: #a6a6da; border-color: #a6a6da;
background-color: #ecd8d8; background-color: #ecd8d8;
border-radius: 10%;
} }
.ui-dialog input[type="range"] { .ui-dialog input[type="range"] {
@ -1187,7 +1188,7 @@ div.slider .ui-slider-handle {
#brushPower, #brushPower,
#brushRadius { #brushRadius {
width: 8em; width: 12em;
} }
#rescaleHigher, #rescaleHigher,
@ -1261,7 +1262,7 @@ div.states {
line-height: 1.5em; line-height: 1.5em;
} }
div.states:hover { div.states:hover, div.states.hovered {
border: 1px solid #c4c4c4; border: 1px solid #c4c4c4;
background-image: linear-gradient(to right, #dedede 100%, #f2f2f2 50%, #fcfcfc 0%); background-image: linear-gradient(to right, #dedede 100%, #f2f2f2 50%, #fcfcfc 0%);
} }

View file

@ -7,8 +7,6 @@
<meta name="application-name" content="Azgaar's Fantasy Map Generator"> <meta name="application-name" content="Azgaar's Fantasy Map Generator">
<meta name="author" content="Azgaar (Max Ganiev)"> <meta name="author" content="Azgaar (Max Ganiev)">
<meta name="description" content="Azgaar's Fantasy Map Generator and Editor"> <meta name="description" content="Azgaar's Fantasy Map Generator and Editor">
<meta name="google" content="notranslate">
<meta name="google-site-verification" content="6N9TRdPptDN1dCZKaMA5zJ-_UmNQE-3c4VizSlQcEeU"/>
<meta property="og:url" content="https://azgaar.github.io/Fantasy-Map-Generator"> <meta property="og:url" content="https://azgaar.github.io/Fantasy-Map-Generator">
<meta property="og:title" content="Azgaar's Fantasy Map Generator"> <meta property="og:title" content="Azgaar's Fantasy Map Generator">
<meta property="og:description" content="Web application generating interactive and customizable maps"> <meta property="og:description" content="Web application generating interactive and customizable maps">
@ -34,13 +32,9 @@
#loading-text span:nth-child(3), #mapOverlay > span:nth-child(3) {animation-delay: 2s;} #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;}} @keyframes blink {0% {opacity: 0;} 20% {opacity: 1;} 100% {opacity: .1;}}
</style> </style>
<link rel="preload" href="index.css?version=1.4" as="style">
<link rel="preload" href="icons.css?version=1.4" as="style">
<link rel="preload" href="libs/jquery-ui.css" as="style">
<link rel="stylesheet" href="index.css?version=1.4"> <link rel="stylesheet" href="index.css?version=1.4">
<link rel="stylesheet" href="icons.css?version=1.4"> <link rel="stylesheet" href="icons.css?version=1.4">
<link rel="stylesheet" href="libs/jquery-ui.css"> <link rel="stylesheet" href="libs/jquery-ui.css">
</head> </head>
<body> <body>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="map" width="100%" height="100%"> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="map" width="100%" height="100%">
@ -2649,8 +2643,8 @@
<div id="brushesSliders" style="display: none"> <div id="brushesSliders" style="display: none">
<div data-tip="Change brush size. Shortcut: + (increase), (decrease)" style="padding-bottom: 1px"><div style="width:3.2em; display: inline-block"><i>Radius:</i></div> <div data-tip="Change brush size. Shortcut: + (increase), (decrease)" style="padding-bottom: 1px"><div style="width:3.2em; display: inline-block"><i>Radius:</i></div>
<input id="brushRadius" oninput="tip('Brush radius: '+this.value); brushRadiusNumber.value = this.value" type="range" min=1 max=50 value=25> <input id="brushRadius" oninput="tip('Brush radius: '+this.value); brushRadiusNumber.value = this.value" type="range" min=1 max=99 value=25>
<input id="brushRadiusNumber" oninput="tip('Brush radius: '+this.value); brushRadius.value = this.value" type="number" min=1 max=50 value=25 style="border: 1px solid #d4d4d4"> <input id="brushRadiusNumber" oninput="tip('Brush radius: '+this.value); brushRadius.value = this.value" type="number" min=1 max=99 value=25 style="border: 1px solid #d4d4d4">
</div> </div>
<div data-tip="Set the brush power"><div style="width:3.2em; display: inline-block"><i>Power:</i></div> <div data-tip="Set the brush power"><div style="width:3.2em; display: inline-block"><i>Power:</i></div>
@ -2659,7 +2653,7 @@
</div> </div>
</div> </div>
<div data-tip="Allow brush to change only land cells and hence restrict the coastline modification"> <div data-tip="Allow brush to change only land cells and hence restrict the coastline modification" style="margin-bottom: .6em">
<input id="changeOnlyLand" class="checkbox" type="checkbox"> <input id="changeOnlyLand" class="checkbox" type="checkbox">
<label for="changeOnlyLand" class="checkbox-label"><i>change only land cells</i></label> <label for="changeOnlyLand" class="checkbox-label"><i>change only land cells</i></label>
</div> </div>

View file

@ -286,7 +286,6 @@ function getMapData() {
TIME && console.timeEnd("createMapDataBlob"); TIME && console.timeEnd("createMapDataBlob");
resolve(blob); resolve(blob);
}); });
} }
// Download .map file // Download .map file
@ -306,116 +305,103 @@ async function saveMap() {
} }
function saveGeoJSON_Cells() { function saveGeoJSON_Cells() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; const json = {type: "FeatureCollection", features: []};
const cells = pack.cells, v = pack.vertices; const cells = pack.cells;
const getPopulation = i => {const [r, u] = getCellPopulation(i); return rn(r+u)}; 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 => { cells.i.forEach(i => {
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [["; const coordinates = getCellPoints(cells.v[i]);
cells.v[i].forEach(n => { const height = getHeight(i);
let x = mapCoordinates.lonW + (v.p[n][0] / graphWidth) * mapCoordinates.lonT; const biome = cells.biome[i];
let y = mapCoordinates.latN - (v.p[n][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise const type = pack.features[cells.f[i]].type;
data += "["+x+","+y+"],"; const population = getPopulation(i);
}); const state = cells.state[i];
// close the ring const province = cells.province[i];
let x = mapCoordinates.lonW + (v.p[cells.v[i][0]][0] / graphWidth) * mapCoordinates.lonT; const culture = cells.culture[i];
let y = mapCoordinates.latN - (v.p[cells.v[i][0]][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise const religion = cells.religion[i];
data += "["+x+","+y+"]"; const neighbors = cells.c[i];
data += "]] },\n \"properties\": {\n";
const height = parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]])); const properties = {id:i, height, biome, type, population, state, province, culture, religion, neighbors}
const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties};
data += " \"id\": \""+i+"\",\n"; json.features.push(feature);
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";
}); });
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const name = getFileName("Cells") + ".geojson"; const name = getFileName("Cells") + ".geojson";
downloadFile(data, name, "application/json"); downloadFile(JSON.stringify(json), name, "application/json");
} }
function saveGeoJSON_Roads() { function saveGeoJSON_Routes() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; const json = {type: "FeatureCollection", features: []};
routes._groups[0][0].childNodes.forEach(n => { routes.selectAll("g > path").each(function() {
n.childNodes.forEach(r => { const coordinates = getRoutePoints(this);
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"LineString\", \"coordinates\": "; const id = this.id;
data += JSON.stringify(getRoadPoints(r)); const type = this.parentElement.id;
data += " },\n \"properties\": {\n";
data += " \"id\": \""+r.id+"\",\n"; const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}};
data += " \"type\": \""+n.id+"\"\n"; json.features.push(feature);
data +=" }\n},\n";
});
}); });
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const name = getFileName("Routes") + ".geojson"; const name = getFileName("Routes") + ".geojson";
downloadFile(data, name, "application/json"); downloadFile(JSON.stringify(json), name, "application/json");
} }
function saveGeoJSON_Rivers() { function saveGeoJSON_Rivers() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; const json = {type: "FeatureCollection", features: []};
rivers._groups[0][0].childNodes.forEach(n => { rivers.selectAll("path").each(function() {
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"LineString\", \"coordinates\": "; const coordinates = getRiverPoints(this);
data += JSON.stringify(getRiverPoints(n)); const id = this.id;
data += " },\n \"properties\": {\n"; const width = +this.dataset.increment;
data += " \"id\": \""+n.id+"\",\n"; const increment = +this.dataset.increment;
data += " \"width\": \""+n.dataset.width+"\",\n"; const river = pack.rivers.find(r => r.i === +id.slice(5));
data += " \"increment\": \""+n.dataset.increment+"\"\n"; const name = river ? river.name : "";
data +=" }\n},\n"; 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"; const name = getFileName("Rivers") + ".geojson";
downloadFile(data, name, "application/json"); downloadFile(JSON.stringify(json), name, "application/json");
} }
function saveGeoJSON_Markers() { function saveGeoJSON_Markers() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; const json = {type: "FeatureCollection", features: []};
markers._groups[0][0].childNodes.forEach(n => { markers.selectAll("use").each(function() {
let x = mapCoordinates.lonW + (n.dataset.x / graphWidth) * mapCoordinates.lonT; const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
let y = mapCoordinates.latN - (n.dataset.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise const id = this.id;
const type = (this.dataset.id).substring(1);
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Point\", \"coordinates\": ["+x+", "+y+"]"; const icon = document.getElementById(type).textContent;
data += " },\n \"properties\": {\n"; const note = notes.length ? notes.find(note => note.id === this.id) : null;
data += " \"id\": \""+n.id+"\",\n"; const name = note ? note.name : "";
data += " \"type\": \""+n.dataset.id.substring(8)+"\"\n"; const legend = note ? note.legend : "";
data +=" }\n},\n";
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"; 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 = []; let points = [];
const l = node.getTotalLength(); const l = node.getTotalLength();
const increment = l / Math.ceil(l / 2); 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); const p = node.getPointAtLength(i);
points.push(getQGIScoordinates(p.x, p.y));
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]);
} }
return points; return points;
} }
@ -427,9 +413,7 @@ function getRiverPoints(node) {
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 p1 = node.getPointAtLength(i);
const p2 = node.getPointAtLength(c); const p2 = node.getPointAtLength(c);
const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
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
points.push([x,y]); points.push([x,y]);
} }
return points; return points;

View file

@ -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 (type === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)";
if (main) tooltip.dataset.main = tip; // set main tip 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() { function showMainTip() {
@ -49,7 +49,8 @@ function showDataTip(e) {
tip(dataTip); tip(dataTip);
} }
function moved() { const moved = debounce(mouseMove, 100);
function mouseMove() {
const point = d3.mouse(this); const point = d3.mouse(this);
const i = findCell(point[0], point[1]); // pack cell id const i = findCell(point[0], point[1]); // pack cell id
if (i === undefined) return; if (i === undefined) return;
@ -89,11 +90,28 @@ function showMapTooltip(point, e, i, g) {
const land = pack.cells.h[i] >= 20; const land = pack.cells.h[i] >= 20;
// specific elements // specific elements
if (group === "armies") {tip(e.target.parentNode.dataset.name + ". Click to edit"); return;} if (group === "armies") {
if (group === "rivers") {tip(getRiverName(e.target.id) + "Click to edit"); return;} 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 === "routes") {tip("Click to edit the Route"); return;}
if (group === "terrain") {tip("Click to edit the Relief Icon"); 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 === "labels") {tip("Click to edit the Label"); return;}
if (group === "markers") {tip("Click to edit the Marker"); return;} if (group === "markers") {tip("Click to edit the Marker"); return;}
if (group === "ruler") { if (group === "ruler") {
@ -106,32 +124,54 @@ function showMapTooltip(point, e, i, g) {
if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;} if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;}
if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); 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 === "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;} if (group === "ice") {tip("Click to edit the Ice"); return;}
// covering elements // covering elements
if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else
if (layerIsOn("togglePopulation")) tip(getPopulationTip(i)); else if (layerIsOn("togglePopulation")) tip(getPopulationTip(i)); else
if (layerIsOn("toggleTemp")) tip("Temperature: " + convertTemperature(grid.cells.temp[g])); 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]) { if (layerIsOn("toggleReligions") && pack.cells.religion[i]) {
const religion = pack.religions[pack.cells.religion[i]]; const religion = pack.cells.religion[i];
const type = religion.type === "Cult" || religion.type == "Heresy" ? religion.type : religion.type + " religion"; const r = pack.religions[religion];
tip(type + ": " + religion.name); const type = r.type === "Cult" || r.type == "Heresy" ? r.type : r.type + " religion";
tip(type + ": " + r.name);
if (religionsEditor.offsetParent) highlightEditorLine(religionsEditor, religion);
} else } else
if (pack.cells.state[i] && (layerIsOn("toggleProvinces") || layerIsOn("toggleStates"))) { 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 province = pack.cells.province[i];
const prov = province ? pack.provinces[province].fullName + ", " : ""; 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 } 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)); if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point));
} }
function getRiverName(id) { function highlightEditorLine(editor, id, timeout = 15000) {
const r = pack.rivers.find(r => r.i == id.slice(5)); Array.from(editor.getElementsByClassName("states hovered")).forEach(el => el.classList.remove("hovered")); // clear all hovered
return r ? r.name + " " + r.type + ". " : ""; 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 // get cell info on mouse move

View file

@ -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, 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, 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, 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(); const array = supporters.replace(/(?:\r\n|\r|\n)/g, "").split(",").map(v => capitalize(v.trim())).sort();
alertMessage.innerHTML = "<ul style='column-count: 5; column-gap: 2em'>" + array.map(n => `<li>${n}</li>`).join("") + "</ul>"; alertMessage.innerHTML = "<ul style='column-count: 5; column-gap: 2em'>" + array.map(n => `<li>${n}</li>`).join("") + "</ul>";
$("#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"}});
@ -481,7 +483,7 @@ function saveGeoJSON() {
$("#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: { buttons: {
Cells: saveGeoJSON_Cells, Cells: saveGeoJSON_Cells,
Routes: saveGeoJSON_Roads, Routes: saveGeoJSON_Routes,
Rivers: saveGeoJSON_Rivers, Rivers: saveGeoJSON_Rivers,
Markers: saveGeoJSON_Markers, Markers: saveGeoJSON_Markers,
Close: function() {$(this).dialog("close");} Close: function() {$(this).dialog("close");}

View file

@ -516,26 +516,15 @@ function getNextId(core, i = 1) {
return core + i; return core + i;
} }
// from https://davidwalsh.name/javascript-debounce-function function debounce(f, ms) {
function debounce(func, wait, immediate) { let isCooldown = false;
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);
}
}
// pause/block JS execution for a while return function() {
function sleep(delay) { if (isCooldown) return;
const start = new Date().getTime(); f.apply(this, arguments);
while (new Date().getTime() < start + delay); isCooldown = true;
setTimeout(() => isCooldown = false, ms);
};
} }
// parse error to get the readable string in Chrome and Firefox // 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'}); 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) // prompt replacer (prompt does not work in Electron)
void function() { void function() {
const prompt = document.getElementById("prompt"); const prompt = document.getElementById("prompt");