mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
v1.4.48
This commit is contained in:
parent
4920ccccb4
commit
554f51e463
10 changed files with 154 additions and 133 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
run_php_server.bat
|
||||||
|
.vscode
|
||||||
Binary file not shown.
12
README.md
12
README.md
|
|
@ -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).
|
||||||
|
|
||||||
[](https://i.redd.it/8bf81ir2cy631.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:_
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
theme: jekyll-theme-slate
|
|
||||||
|
|
@ -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%);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
index.html
12
index.html
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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");}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue