This commit is contained in:
Azgaar 2019-09-07 19:09:48 +03:00
parent baf23bee37
commit e743735e57
11 changed files with 789 additions and 546 deletions

View file

@ -996,7 +996,7 @@ div.slider .ui-slider-handle {
top: 100%;
border: 1px solid #5e4fa2;
background-color: #a4879b;
width: 3.6em;
width: 5em;
}
#loadDropdown {
@ -1006,7 +1006,7 @@ div.slider .ui-slider-handle {
top: 100%;
border: 1px solid #5e4fa2;
background-color: #a4879b;
width: 6em;
width: 9em;
}
#loadDropdown>div,
@ -1510,6 +1510,7 @@ div.states > div.biomeArea {
cursor: default;
-moz-user-select: none;
user-select: none;
pointer-events: none;
}
#title_name {
@ -1808,6 +1809,12 @@ svg.button {
margin: 10px 0;
}
#info-line {
font-size: .9em;
color: gray;
user-select: none;
}
.optionsSeedRestore {
font-size: 12px;
cursor: pointer;
@ -1922,6 +1929,15 @@ svg.button {
margin: 10px 0 0 7px;
}
#errorBox {
font-size: .9em;
font-family: monospace;
color: #920303;
background-color: #dabdbd91;
padding: 2px;
border: 1px solid #916e7f;
}
#debug {
font-size: 1px;
opacity: 0.8;

View file

@ -882,7 +882,7 @@
<div id="collapsible">
<button id="optionsTrigger" data-tip="Click to show options pane. Shortcut: Tab" class="options glow" onclick="showOptions(event)" style="padding:7px 5px"></button>
<button id="regenerate" data-tip="Click to generate a new map. Shortcut: F7" onclick="regeneratePrompt()" class="options" style="display:none; padding:7px 8px">New Map!</button>
<button id="regenerate" data-tip="Click to generate a new map. Shortcut: F2" onclick="regeneratePrompt()" class="options" style="display:none; padding:7px 8px">New Map!</button>
</div>
<div id="options" style="display:none">
@ -909,7 +909,8 @@
<option value="landmass">Pure landmass</option>
<option hidden value="custom">Custom (not saved)</option>
</select>
<button id="savePreset" data-tip="Click to save displayed layers as a new preset" class="icon-plus styleButton" onclick="savePreset()"></button>
<button id="savePresetButton" data-tip="Click to save displayed layers as a new preset" class="icon-plus styleButton" style="display:none" onclick="savePreset()"></button>
<button id="removePresetButton" data-tip="Click to remove current custom preset" class="icon-minus styleButton" style="display:none" onclick="removePreset()"></button>
<p data-tip="Click to toggle a layer, drag to raise or lower a layer">Displayed layers:</p>
<ul data-tip="Click to toggle a layer, drag to raise or lower a layer" id="mapLayers">
@ -1470,7 +1471,7 @@
</div>
<div id="optionsContent" class="tabcontent">
<p data-tip="Map generation settings. Generate a new map to apply the settings">Map Generation (new map to apply):</p>
<p data-tip="Map generation settings. Generate a new map to apply the settings">Map settings (new map to apply):</p>
<table>
<tr data-tip="Map height and width in pixels. Please consider reducing map size in case of performance issues">
@ -1563,7 +1564,7 @@
<td>
<i data-locked=0 id="lock_provinces" class="icon-lock-open"></i>
</td>
<td>Provinces number</td>
<td>Provinces ratio</td>
<td>
<input id="provincesInput" data-stored="provinces" type="range" min=0 max=100 value=30>
</td>
@ -1585,7 +1586,7 @@
</td>
</tr>
<tr data-tip="Define state and cultures growth rate. Defines how many lands will stay neutral">
<tr data-tip="Set state and cultures growth rate. Defines how many lands will stay neutral">
<td>
<i data-locked=0 id="lock_neutral" class="icon-lock-open"></i>
</td>
@ -1598,7 +1599,7 @@
</td>
</tr>
<tr data-tip="Define how many towns (non-capital burgs) should be generated">
<tr data-tip="Define a number of towns to be placed (if suitable area is enougth)">
<td>
<i data-locked=0 id="lock_manors" class="icon-lock-open"></i>
</td>
@ -1611,7 +1612,7 @@
</td>
</tr>
<tr data-tip="Define how many organized religions and cults should be generated">
<tr data-tip="Define how many organized (!) religions and cults should be generated">
<td>
<i data-locked=0 id="lock_religions" class="icon-lock-open"></i>
</td>
@ -1625,16 +1626,28 @@
</tr>
</table>
<p data-tip="Interface settings that don't affect generation. They are applied immediately on change">User Interface:</p>
<p data-tip="Tool settings that don't affect maps. Changes are getting applied immediately">Generator settings:</p>
<table>
<tr data-tip="Set user interface size">
<tr data-tip="Set what Generator should do on opening">
<td></td>
<td>Onload behavior</td>
<td>
<select id="onloadMap" data-stored="onloadMap">
<option value="random" selected>Generate random map</option>
<option value="saved">Open last saved map</option>
</select>
</td>
<td></td>
</tr>
<tr data-tip="Set user interface size. Please note browser zoom also affects interface size (Ctrl + or Ctrl - to change)">
<td></td>
<td>Interface size</td>
<td>
<input id="uiSizeInput" data-stored="uiSize" type="range" min=.6 max=1.4 step=.1 value=1>
<input id="uiSizeInput" data-stored="uiSize" type="range" min=.8 max=2 step=.1 value=1>
</td>
<td>
<input id="uiSizeOutput" data-stored="uiSize" type="number" min=.6 max=1.4 step=.1 value=1>
<input id="uiSizeOutput" data-stored="uiSize" type="number" min=.6 max=2 step=.1 value=1>
</td>
</tr>
@ -1819,18 +1832,20 @@
</div>
<div id="sticked">
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F7">New Map</button>
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F2">New Map</button>
<button id="saveButton" data-tip="Select file format to save map">Save</button>
<div id="saveDropdown">
<div id="saveMap" data-tip="Download the map as fully functional .map file to your machine. Shortcut: Ctrl + M">.map</div>
<div id="saveSVG" data-tip="Download the map as vector image (open in browser or Inkscape). Shortcut: Ctrl + S">.svg</div>
<div id="savePNG" data-tip="Download visible part of the map as .png image. Texture will not be shown. Shortcut: Ctrl + P">.png</div>
<div id="savePNG" data-tip="Download visible part of the map as .png image. Shortcut: Ctrl + P">.png</div>
<div id="quickSave" data-tip="Save map to browser storage. Shortcut: F6">storage</div>
<!-- <div id="saveDropbox" data-tip="Save fully functional .map file to Dropbox. Shortcut: Ctrl + B">Dropbox</div> -->
</div>
<button id="loadButton" data-tip="Load fully functional map in a .map format">Load</button>
<div id="loadDropdown">
<div id="loadMap" data-tip="Load .map file from local disk. Shortcut: Ctrl + L">from disk</div>
<div id="loadMap" data-tip="Load .map file from local disk. Shortcut: Ctrl + L">from local disk</div>
<div id="loadURL" data-tip="Load .map file from URL (server should allow CORS). Shortcut: Ctrl + U">from URL</div>
<div id="quickLoad" data-tip="Load map from browser storage (if saved before). Shortcut: F9">from storage</div>
<!-- <div id="loadDropbox" data-tip="Load .map file from Dropbox. Shortcut: Ctrl + D">from Dropbox</div> -->
</div>
<button id="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
@ -2006,7 +2021,7 @@
<span id="riverReset" data-tip="Reset transformation to default" class="icon-cancel pointer"></span>
</div>
<button id="riverLength" data-tip="Route length in selected units">0</button>
<button id="riverLength" data-tip="River length in selected units">0</button>
<button id="riverCopy" data-tip="Copy river" class="icon-clone"></button>
<button id="riverNew" data-tip="Create new river clicking on map" class="icon-map-pin"></button>
<button id="riverLegend" data-tip="Edit free text notes (legend) for the river" class="icon-edit"></button>
@ -2022,7 +2037,7 @@
<span id="routeGroupAdd" data-tip="Create new group for this route" class="icon-plus pointer"></span>
<span id="routeGroupRemove" data-tip="Remove all routes of this group" class="icon-trash-empty pointer"></span>
</div>
<button id="routeLength" data-tip="River length in selected units">0</button>
<button id="routeLength" data-tip="Route length in selected units">0</button>
<button id="routeSplit" data-tip="Click on a control point to split the route" class="icon-unlink"></button>
<button id="routeLegend" data-tip="Edit free text notes (legend) for the route" class="icon-edit"></button>
<button id="routeNew" data-tip="Create new route clicking on map" class="icon-map-pin"></button>
@ -2952,8 +2967,8 @@
<script src="libs/polylabel.min.js"></script>
<script src="libs/jquery-ui.min.js"></script>
<script src="libs/seedrandom.min.js"></script>
<script src="modules/ui/layers.js"></script>
<script defer src="modules/ui/general.js"></script>
<script defer src="modules/ui/options.js"></script>
<script defer src="modules/ui/measurers.js"></script>

410
main.js
View file

@ -8,7 +8,13 @@
"use strict";
const version = "1.0"; // generator version
document.title += " v " + version;
document.title += " v" + version;
// if map version is not stored, clear localStorage and show a message
if (localStorage.getItem("version") != version) {
localStorage.clear();
setTimeout(showWelcomeMessage, 8000);
}
// append svg layers (in default order)
let svg = d3.select("#map");
@ -111,20 +117,58 @@ oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y",
oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
applyDefaultNamesData(); // always apply default namesbase load as namesdata is not stored in .map file
void function removeLoading() {
d3.select("#loading").transition().duration(5000).style("opacity", 0).remove();
d3.select("#initial").transition().duration(5000).attr("opacity", 0).remove();
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
d3.select("#tooltip").transition().duration(3000).style("opacity", 1);
}()
// load linked map from url or generate a random map
// decide which map should be loaded or generated on page load
void function checkLoadParameters() {
const url = new URL(window.location.href);
const maplink = url.searchParams.get("maplink");
const params = url.searchParams;
// check if URL is valid
if (maplink) {
// of there is a valid maplink, try to load .map file from URL
if (params.get("maplink")) {
console.warn("Load map from URL");
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 a valid URL", maplink);
if (valid) {loadMapFromURL(maplink, 1); return;}
else showUploadErrorMessage("Map link is not a valid URL", maplink);
}
generateRandomMapOnLoad();
// if there is a seed (user of MFCG provided), generate map for it
if (params.get("seed")) {
console.warn("Generate map for seed");
generateMapOnLoad();
return;
}
// open latest map if option is active and map is stored
if (onloadMap.value === "saved") {
ldb.get("lastMap", blob => {
if (blob) {
console.warn("Load last saved map");
try {
uploadFile(blob);
}
catch(error) {
console.error(error);
console.warn("Cannot load stored map, random map to be generated");
generateMapOnLoad();
}
} else {
console.error("No map stored, random map to be generated");
generateMapOnLoad();
}
});
return;
}
console.warn("Generate random map");
generateMapOnLoad();
}()
function loadMapFromURL(maplink, random) {
@ -137,25 +181,104 @@ function loadMapFromURL(maplink, random) {
}).then(blob => uploadFile(blob))
.catch(error => {
showUploadErrorMessage(error.message, URL, random);
if (random) generateRandomMapOnLoad();
if (random) generateMapOnLoad();
});
}
function showUploadErrorMessage(error, URL, random) {
console.log(error);
console.error(error);
alertMessage.innerHTML = `
Cannot load map from the <a href='${URL}' target='_blank'>link provided</a>.
${random?`A new random map is generated. `:''}
Please ensure the linked file is reachable`;
Please ensure the linked file is reachable and CORS is allowed on server side`;
$("#alert").dialog({title: "Loading error", width: 320, buttons: {OK: function() {$(this).dialog("close");}}});
}
function generateRandomMapOnLoad() {
function generateMapOnLoad() {
applyDefaultStyle(); // apply style
generate(); // generate map
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
applyPreset(); // apply saved layers preset
setTimeout(showWelcomeMessage, 7000); // show welcome message if required
}
// focus on coordinates, cell or burg provided in searchParams
function focusOn() {
const url = new URL(window.location.href);
const params = url.searchParams;
if (params.get("from") === "MFCG") {
if (params.get("seed").length === 13) {
// show back burg from MFCG
params.set("burg", params.get("seed").slice(-4));
} else {
// select burg for MFCG
findBurgForMFCG(params);
return;
}
}
const s = +params.get("scale") || 8;
let x = +params.get("x");
let y = +params.get("y");
const c = +params.get("cell");
if (c) {
x = pack.cells.p[c][0];
y = pack.cells.p[c][1];
}
const b = +params.get("burg");
if (b && pack.burgs[b]) {
x = pack.burgs[b].x;
y = pack.burgs[b].y;
}
if (x && y) zoomTo(x, y, s, 1600);
}
// find burg for MFCG and focus on it
function findBurgForMFCG(params) {
const cells = pack.cells, burgs = pack.burgs;
if (pack.burgs.length < 2) {console.error("Cannot select a burg for MFCG"); return;}
const size = +params.get("size");
const name = params.get("name");
let coast = +params.get("coast");
let port = +params.get("port");
let river = +params.get("river");
let selection = defineSelection(coast, port, river);
if (!selection.length) selection = defineSelection(coast, !port, !river);
if (!selection.length) selection = defineSelection(!coast, 0, !river);
if (!selection.length) selection = [burgs[1]]; // select first if nothing is found
function defineSelection(coast, port, river) {
if (port && river) return burgs.filter(b => b.port && cells.r[b.cell]);
if (!port && coast && river) return burgs.filter(b => !b.port && cells.t[b.cell] === 1 && cells.r[b.cell]);
if (!coast && !river) return burgs.filter(b => cells.t[b.cell] !== 1 && !cells.r[b.cell]);
if (!coast && river) return burgs.filter(b => cells.t[b.cell] !== 1 && cells.r[b.cell]);
if (coast && river) return burgs.filter(b => cells.t[b.cell] === 1 && cells.r[b.cell]);
return [];
}
// 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 b = selection[selected].i;
if (!b) {console.error("Cannot select a burg for MFCG"); return;}
if (size) burgs[b].population = size;
if (name) burgs[b].name = name;
const label = burgLabels.select("[data-id='" + b + "']");
if (label.size()) {
tip("Here stands the glorious city of " + burgs[b].name, true, "success", 12000);
label.text(burgs[b].name).classed("drag", true).on("mouseover", function() {
d3.select(this).classed("drag", false);
label.on("mouseover", null);
});
}
zoomTo(burgs[b].x, burgs[b].y, 8, 1600);
invokeActiveZooming();
}
function applyDefaultNamesData() {
@ -354,133 +477,45 @@ function applyDefaultStyle() {
fogging.attr("opacity", .8).attr("fill", "#000000").attr("stroke-width", 5);
}
// focus on coordinates, cell or burg provided in searchParams
function focusOn() {
const url = new URL(window.location.href);
const params = url.searchParams;
if (params.get("from") === "MFCG") {
if (params.get("seed").length === 13) {
// show back burg from MFCG
params.set("burg", params.get("seed").slice(-4));
} else {
// select burg for MFCG
findBurgForMFCG(params);
return;
}
}
const s = +params.get("scale") || 8;
let x = +params.get("x");
let y = +params.get("y");
const c = +params.get("cell");
if (c) {
x = pack.cells.p[c][0];
y = pack.cells.p[c][1];
}
const b = +params.get("burg");
if (b && pack.burgs[b]) {
x = pack.burgs[b].x;
y = pack.burgs[b].y;
}
if (x && y) zoomTo(x, y, s, 1600);
}
// find burg for MFCG and focus on it
function findBurgForMFCG(params) {
const cells = pack.cells, burgs = pack.burgs;
if (pack.burgs.length < 2) {console.error("Cannot select a burg for MFCG"); return;}
const size = +params.get("size");
const name = params.get("name");
let coast = +params.get("coast");
let port = +params.get("port");
let river = +params.get("river");
let selection = defineSelection(coast, port, river);
if (!selection.length) selection = defineSelection(coast, !port, !river);
if (!selection.length) selection = defineSelection(!coast, 0, !river);
if (!selection.length) selection = [burgs[1]]; // select first if nothing is found
function defineSelection(coast, port, river) {
if (port && river) return burgs.filter(b => b.port && cells.r[b.cell]);
if (!port && coast && river) return burgs.filter(b => !b.port && cells.t[b.cell] === 1 && cells.r[b.cell]);
if (!coast && !river) return burgs.filter(b => cells.t[b.cell] !== 1 && !cells.r[b.cell]);
if (!coast && river) return burgs.filter(b => cells.t[b.cell] !== 1 && cells.r[b.cell]);
if (coast && river) return burgs.filter(b => cells.t[b.cell] === 1 && cells.r[b.cell]);
return [];
}
// 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 b = selection[selected].i;
if (!b) {console.error("Cannot select a burg for MFCG"); return;}
if (size) burgs[b].population = size;
if (name) burgs[b].name = name;
const label = burgLabels.select("[data-id='" + b + "']");
if (label.size()) {
tip("Here stands the glorious city of " + burgs[b].name, true, "success", 12000);
label.text(burgs[b].name).classed("drag", true).on("mouseover", function() {
d3.select(this).classed("drag", false);
label.on("mouseover", null);
});
}
zoomTo(burgs[b].x, burgs[b].y, 8, 1600);
invokeActiveZooming();
}
function showWelcomeMessage() {
// Changelog dialog window
if (localStorage.getItem("version") != version) {
const link = 'https://www.reddit.com/r/FantasyMapGenerator/comments/cxu1c5/update_new_version_is_published_v_10'; // announcement on Reddit
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
const link = 'https://www.reddit.com/r/FantasyMapGenerator/comments/cxu1c5/update_new_version_is_published_v_10'; // announcement on Reddit
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
This version is compatible with versions 0.8b and 0.9b, but not with older .map files.
Please use an <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>archived version</a> to open old files.
This version is compatible with versions 0.8b and 0.9b, but not with older .map files.
Please use an <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>archived version</a> to open old files.
<ul><a href=${link} target='_blank'>Main changes:</a>
<li>Provinces and Provinces Editor</li>
<li>Religions Layer and Religions Editor</li>
<li>Full state names (state types)</li>
<li>Multi-lined labels</li>
<li>State relations (diplomacy)</li>
<li>Custom layers (zones)</li>
<li>Places of interest (auto-added markers)</li>
<li>New color picker and hatching fill</li>
<li>Legend boxes</li>
<li>World Configurator presets</li>
<li>Improved state labels placement</li>
<li>Relief icons sets</li>
<li>Fogging</li>
<li>Custom layer presets</li>
<li>Custom biomes</li>
<li>State, province and burg COAs</li>
<li>Desktop version (see <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A#is-there-a-desktop-version' target='_blank'>here)</a></li>
</ul>
<ul><a href=${link} target='_blank'>Main changes:</a>
<li>Provinces and Provinces Editor</li>
<li>Religions Layer and Religions Editor</li>
<li>Full state names (state types)</li>
<li>Multi-lined labels</li>
<li>State relations (diplomacy)</li>
<li>Custom layers (zones)</li>
<li>Places of interest (auto-added markers)</li>
<li>New color picker and hatching fill</li>
<li>Legend boxes</li>
<li>World Configurator presets</li>
<li>Improved state labels placement</li>
<li>Relief icons sets</li>
<li>Fogging</li>
<li>Custom layer presets</li>
<li>Custom biomes</li>
<li>State, province and burg COAs</li>
<li>Desktop version (see <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A#is-there-a-desktop-version' target='_blank'>here)</a></li>
</ul>
<p>Join our <a href='https://www.reddit.com/r/FantasyMapGenerator' target='_blank'>Reddit community</a> and
<a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a>
to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
<p>Join our <a href='https://www.reddit.com/r/FantasyMapGenerator' target='_blank'>Reddit community</a> and
<a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a>
to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
<p>Thanks for all supporters on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>!</i></p>`;
<p>Thanks for all supporters on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>!</i></p>`;
$("#alert").dialog(
{resizable: false, title: "Fantasy Map Generator update", width: 310,
buttons: {
OK: function() {
localStorage.clear();
localStorage.setItem("version", version);
$(this).dialog("close");
}
},
position: {my: "center", at: "center", of: "svg"}
});
}
$("#alert").dialog(
{resizable: false, title: "Fantasy Map Generator update", width: 310,
buttons: {OK: function() {$(this).dialog("close")}},
position: {my: "center", at: "center", of: "svg"},
close: () => localStorage.setItem("version", version)}
);
}
function zoomed() {
@ -601,6 +636,7 @@ void function addDragToUpload() {
}
// all good - show uploading text and load the map
$("#map-dragged > p").text("Uploading<span>.</span><span>.</span><span>.</span>");
closeDialogs();
uploadFile(file, function onUploadFinish() {
$("#map-dragged > p").text("Drop to upload");
});
@ -608,45 +644,65 @@ void function addDragToUpload() {
}()
function generate() {
const timeStart = performance.now();
console.time("TOTAL");
invokeActiveZooming();
generateSeed();
console.group("Map " + seed);
applyMapSize();
randomizeOptions();
placePoints();
calculateVoronoi(grid, grid.points);
drawScaleBar();
HeightmapGenerator.generate();
markFeatures();
openNearSeaLakes();
OceanLayers();
calculateMapCoordinates();
calculateTemperatures();
generatePrecipitation();
reGraph();
drawCoastline();
try {
const timeStart = performance.now();
invokeActiveZooming();
generateSeed();
console.group("Generated Map " + seed);
applyMapSize();
randomizeOptions();
placePoints();
calculateVoronoi(grid, grid.points);
drawScaleBar();
HeightmapGenerator.generate();
markFeatures();
openNearSeaLakes();
OceanLayers();
calculateMapCoordinates();
calculateTemperatures();
generatePrecipitation();
reGraph();
drawCoastline();
elevateLakes();
Rivers.generate();
defineBiomes();
rankCells();
Cultures.generate();
Cultures.expand();
BurgsAndStates.generate();
Religions.generate();
drawStates();
drawBorders();
BurgsAndStates.drawStateLabels();
addZone();
addMarkers();
elevateLakes();
Rivers.generate();
defineBiomes();
console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
showStatistics();
console.groupEnd("Generated Map " + seed);
}
catch(error) {
console.error(error);
clearMainTip();
rankCells();
Cultures.generate();
Cultures.expand();
BurgsAndStates.generate();
Religions.generate();
const regex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
const errorNoURL = error.stack.replace(regex, url => '<i>' + last(url.split("/")) + '</i>');
const errorParsed = errorNoURL.replace(/at /ig, "<br>&nbsp;&nbsp;at ");
drawStates();
drawBorders();
BurgsAndStates.drawStateLabels();
addZone();
addMarkers();
console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
showStatistics();
console.groupEnd("Map " + seed);
alertMessage.innerHTML = `An error is occured on map generation. Please retry.
<br>If error persists, clear the stored data and try again.
<p id="errorBox">${errorParsed}</p>`;
$("#alert").dialog({
resizable: false, title: "Generation error", maxWidth:500, buttons: {
"Clear data": function() {localStorage.clear(); localStorage.setItem("version", version);},
Regenerate: function() {regenerateMap(); $(this).dialog("close");}
}, position: {my: "center", at: "center", of: "svg"}
});
}
}
// generate map seed (string!) or get it from URL searchParams
@ -1004,13 +1060,16 @@ function drawCoastline() {
if (start === -1) continue; // cannot start here
const connectedVertices = connectVertices(start, type);
used[f] = 1;
const points = connectedVertices.map(v => vertices.p[v]);
let points = connectedVertices.map(v => vertices.p[v]);
const area = d3.polygonArea(points); // area with lakes/islands
if (area > 0 && features[f].type === "lake") points = points.reverse();
features[f].area = Math.abs(area);
const path = round(lineGen(points));
const id = features[f].group + features[f].i;
if (features[f].type === "lake") {
landMask.append("path").attr("d", path).attr("fill", "black");
//waterMask.append("path").attr("d", path).attr("fill", "white"); // uncomment to show over lakes
// waterMask.append("path").attr("d", path).attr("fill", "white"); // uncomment to show over lakes
lakes.select("#"+features[f].group).append("path").attr("d", path).attr("id", id); // draw the lake
} else {
landMask.append("path").attr("d", path).attr("fill", "white");
@ -1046,9 +1105,9 @@ function drawCoastline() {
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 (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]) {console.error("Next vertex is not found"); break;}
}
chain.push(chain[0]); // push first vertex as the last one
@ -1429,6 +1488,7 @@ function showStatistics() {
}
const regenerateMap = debounce(function() {
console.warn("Generate new random map");
closeDialogs("#worldConfigurator");
customization = 0;
undraw();

View file

@ -103,21 +103,22 @@
// place secondary settlements based on geo and economical evaluation
function placeTowns() {
console.time('placeTowns');
const score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // cell score for towns 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 score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // a bit randomized cell score for towns placement
const sorted = cells.i.filter(i => !cells.burg[i] && score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
const desiredNumber = manorsInput.value == 1000 ? rn(sorted.length / 8 / densityInput.value ** .8) : manorsInput.valueAsNumber;
const burgsNumber = Math.min(desiredNumber, sorted.length);
const burgsNumber = Math.min(desiredNumber, sorted.length); // towns to generate
let burgsAdded = 0;
const burgsTree = burgs[0];
let spacing = (graphWidth + graphHeight) / 150 / (burgsNumber ** .7 / 66); // min distance between towns
while (burgsAdded < burgsNumber) {
while (burgsAdded < burgsNumber && spacing > 1) {
for (let i=0; burgsAdded < burgsNumber && i < sorted.length; i++) {
if (cells.burg[sorted[i]]) continue;
const cell = sorted[i], 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
if (cells.burg[cell] || burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make placement not uniform
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
const burg = burgs.length;
const culture = cells.culture[cell];
const name = Names.getCulture(culture);
@ -133,11 +134,7 @@
console.error(`Cannot place all burgs. Requested ${desiredNumber}, placed ${burgsAdded}`);
}
//const min = d3.min(score.filter(s => s)), max = d3.max(score);
//terrs.selectAll("polygon").data(sorted).enter().append("polygon").attr("points", d => getPackPolygon(d)).attr("fill", d => color(1 - normalize(score[d], min, max)));
//labels.selectAll("text").data(sorted).enter().append("text").attr("x", d => cells.p[d][0]).attr("y", d => cells.p[d][1]).text(d => score[d]).attr("font-size", 2);
burgs[0] = {name:undefined};
burgs[0] = {name:undefined}; // do not store burgsTree anymore
console.timeEnd('placeTowns');
}
}
@ -823,7 +820,7 @@
cells.province = new Uint16Array(cells.i.length); // cell state
const percentage = +provincesInput.value;
if (states.length < 2 || !percentage) return; // no provinces
const max = gauss(400, 50, 300, 500) / percentage ** .5; // max growth in 300-30 range
const max = percentage == 100 ? 1000 : gauss(20, 5, 5, 100) * percentage ** .5; // max growth
const forms = {
Monarchy:{County:11, Earldom:3, Shire:1, Landgrave:1, Margrave:1, Barony:1},

View file

@ -152,8 +152,9 @@ function GFontToDataURI(url) {
}
// prepare map data for saving
function getMapURL() {
console.time("saveMap");
function getMapData() {
if (customization) return false;
console.time("createMapDataBlob");
return new Promise(resolve => {
const date = new Date();
@ -192,33 +193,33 @@ function getMapURL() {
pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl,
pack.cells.pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state,
pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces].join("\r\n");
const dataBlob = new Blob([data], {type: "text/plain"});
const URL = window.URL.createObjectURL(dataBlob);
console.timeEnd("saveMap");
resolve(URL);
const blob = new Blob([data], {type: "text/plain"});
console.timeEnd("createMapDataBlob");
resolve(blob);
});
}
// Save in .map format
// Download .map file
async function saveMap() {
if (customization) {tip("Map cannot be saved when is in edit mode, 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();
const URL = await getMapURL();
const blob = await getMapData();
const URL = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.download = "fantasy_map_" + Date.now() + ".map";
link.href = URL;
document.body.appendChild(link);
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check`, true, "success", 7000);
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
}
function uploadFile(file, callback) {
console.time("loadMap");
closeDialogs();
uploadFile.timeStart = performance.now();
const fileReader = new FileReader();
fileReader.onload = function(fileLoadedEvent) {
const dataLoaded = fileLoadedEvent.target.result;
@ -252,285 +253,386 @@ function uploadFile(file, callback) {
}
function parseLoadedData(data) {
closeDialogs();
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
void function parseParameters() {
const params = data[0].split("|");
if (params[3]) {seed = params[3]; optionsSeed.value = seed;}
if (params[4]) graphWidth = +params[4];
if (params[5]) graphHeight = +params[5];
}()
void function parseOptions() {
const options = data[1].split("|");
if (options[0]) applyOption(distanceUnitInput, options[0]);
if (options[1]) distanceScaleInput.value = distanceScaleOutput.value = options[1];
if (options[2]) areaUnit.value = options[2];
if (options[3]) applyOption(heightUnit, options[3]);
if (options[4]) heightExponentInput.value = heightExponentOutput.value = options[4];
if (options[5]) temperatureScale.value = options[5];
if (options[6]) barSize.value = barSizeOutput.value = options[6];
if (options[7] !== undefined) barLabel.value = options[7];
if (options[8] !== undefined) barBackOpacity.value = options[8];
if (options[9]) barBackColor.value = options[9];
if (options[10]) barPosX.value = options[10];
if (options[11]) barPosY.value = options[11];
if (options[12]) populationRate.value = populationRateOutput.value = options[12];
if (options[13]) urbanization.value = urbanizationOutput.value = options[13];
if (options[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(options[14], 100), 1);
if (options[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(options[15], 100), 0);
if (options[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = options[16];
if (options[17]) temperaturePoleInput.value = temperaturePoleOutput.value = options[17];
if (options[18]) precInput.value = precOutput.value = options[18];
if (options[19]) winds = JSON.parse(options[19]);
}()
void function parseConfiguration() {
if (data[2]) mapCoordinates = JSON.parse(data[2]);
if (data[4]) notes = JSON.parse(data[4]);
const biomes = data[3].split("|");
biomesData = applyDefaultBiomesSystem();
biomesData.color = biomes[0].split(",");
biomesData.habitability = biomes[1].split(",").map(h => +h);
biomesData.name = biomes[2].split(",");
// push custom biomes if any
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() {
svg.remove();
document.body.insertAdjacentHTML("afterbegin", data[5]);
}()
void function redefineElements() {
svg = d3.select("#map");
defs = svg.select("#deftemp");
viewbox = svg.select("#viewbox");
scaleBar = svg.select("#scaleBar");
legend = svg.select("#legend");
ocean = viewbox.select("#ocean");
oceanLayers = ocean.select("#oceanLayers");
oceanPattern = ocean.select("#oceanPattern");
lakes = viewbox.select("#lakes");
landmass = viewbox.select("#landmass");
texture = viewbox.select("#texture");
terrs = viewbox.select("#terrs");
biomes = viewbox.select("#biomes");
cells = viewbox.select("#cells");
gridOverlay = viewbox.select("#gridOverlay");
coordinates = viewbox.select("#coordinates");
compass = viewbox.select("#compass");
rivers = viewbox.select("#rivers");
terrain = viewbox.select("#terrain");
relig = viewbox.select("#relig");
cults = viewbox.select("#cults");
regions = viewbox.select("#regions");
statesBody = regions.select("#statesBody");
statesHalo = regions.select("#statesHalo");
provs = viewbox.select("#provs");
zones = viewbox.select("#zones");
borders = viewbox.select("#borders");
stateBorders = borders.select("#stateBorders");
provinceBorders = borders.select("#provinceBorders");
routes = viewbox.select("#routes");
roads = routes.select("#roads");
trails = routes.select("#trails");
searoutes = routes.select("#searoutes");
temperature = viewbox.select("#temperature");
coastline = viewbox.select("#coastline");
prec = viewbox.select("#prec");
population = viewbox.select("#population");
labels = viewbox.select("#labels");
icons = viewbox.select("#icons");
burgIcons = icons.select("#burgIcons");
anchors = icons.select("#anchors");
markers = viewbox.select("#markers");
ruler = viewbox.select("#ruler");
fogging = viewbox.select("#fogging");
debug = viewbox.select("#debug");
freshwater = lakes.select("#freshwater");
salt = lakes.select("#salt");
burgLabels = labels.select("#burgLabels");
}()
void function parseGridData() {
grid = JSON.parse(data[6]);
calculateVoronoi(grid, grid.points);
grid.cells.h = Uint8Array.from(data[7].split(","));
grid.cells.prec = Uint8Array.from(data[8].split(","));
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() {
pack = {};
reGraph();
reMarkFeatures();
pack.features = JSON.parse(data[12]);
pack.cultures = JSON.parse(data[13]);
pack.states = JSON.parse(data[14]);
pack.burgs = JSON.parse(data[15]);
pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: "No religion"}];
pack.provinces = data[30] ? JSON.parse(data[30]) : [0];
const cells = pack.cells;
cells.biome = Uint8Array.from(data[16].split(","));
cells.burg = Uint16Array.from(data[17].split(","));
cells.conf = Uint8Array.from(data[18].split(","));
cells.culture = Uint16Array.from(data[19].split(","));
cells.fl = Uint16Array.from(data[20].split(","));
cells.pop = Uint16Array.from(data[21].split(","));
cells.r = Uint16Array.from(data[22].split(","));
cells.road = Uint16Array.from(data[23].split(","));
cells.s = Uint16Array.from(data[24].split(","));
cells.state = Uint16Array.from(data[25].split(","));
cells.religion = data[26] ? Uint16Array.from(data[26].split(",")) : new Uint16Array(cells.i.length);
cells.province = data[27] ? Uint16Array.from(data[27].split(",")) : new Uint16Array(cells.i.length);
cells.crossroad = data[28] ? Uint16Array.from(data[28].split(",")) : new Uint16Array(cells.i.length);
}()
void function restoreLayersState() {
if (texture.style("display") !== "none" && texture.select("image").size()) turnButtonOn("toggleTexture"); else turnButtonOff("toggleTexture");
if (terrs.selectAll("*").size()) turnButtonOn("toggleHeight"); else turnButtonOff("toggleHeight");
if (biomes.selectAll("*").size()) turnButtonOn("toggleBiomes"); else turnButtonOff("toggleBiomes");
if (cells.selectAll("*").size()) turnButtonOn("toggleCells"); else turnButtonOff("toggleCells");
if (gridOverlay.selectAll("*").size()) turnButtonOn("toggleGrid"); else turnButtonOff("toggleGrid");
if (coordinates.selectAll("*").size()) turnButtonOn("toggleCoordinates"); else turnButtonOff("toggleCoordinates");
if (compass.style("display") !== "none" && compass.select("use").size()) turnButtonOn("toggleCompass"); else turnButtonOff("toggleCompass");
if (rivers.style("display") !== "none") turnButtonOn("toggleRivers"); else turnButtonOff("toggleRivers");
if (terrain.style("display") !== "none" && terrain.selectAll("*").size()) turnButtonOn("toggleRelief"); else turnButtonOff("toggleRelief");
if (relig.selectAll("*").size()) turnButtonOn("toggleReligions"); else turnButtonOff("toggleReligions");
if (cults.selectAll("*").size()) turnButtonOn("toggleCultures"); else turnButtonOff("toggleCultures");
if (statesBody.selectAll("*").size()) turnButtonOn("toggleStates"); else turnButtonOff("toggleStates");
if (provs.selectAll("*").size()) turnButtonOn("toggleProvinces"); else turnButtonOff("toggleProvinces");
if (zones.selectAll("*").size() && zones.style("display") !== "none") turnButtonOn("toggleZones"); else turnButtonOff("toggleZones");
if (borders.style("display") !== "none") turnButtonOn("toggleBorders"); else turnButtonOff("toggleBorders");
if (routes.style("display") !== "none" && routes.selectAll("path").size()) turnButtonOn("toggleRoutes"); else turnButtonOff("toggleRoutes");
if (temperature.selectAll("*").size()) turnButtonOn("toggleTemp"); else turnButtonOff("toggleTemp");
if (prec.selectAll("circle").size()) turnButtonOn("togglePrec"); else turnButtonOff("togglePrec");
if (labels.style("display") !== "none") turnButtonOn("toggleLabels"); else turnButtonOff("toggleLabels");
if (icons.style("display") !== "none") turnButtonOn("toggleIcons"); else turnButtonOff("toggleIcons");
if (markers.selectAll("*").size() && markers.style("display") !== "none") turnButtonOn("toggleMarkers"); else turnButtonOff("toggleMarkers");
if (ruler.style("display") !== "none") turnButtonOn("toggleRulers"); else turnButtonOff("toggleRulers");
if (scaleBar.style("display") !== "none") turnButtonOn("toggleScaleBar"); else turnButtonOff("toggleScaleBar");
// special case for population bars
const populationIsOn = population.selectAll("line").size();
if (populationIsOn) drawPopulation();
if (populationIsOn) turnButtonOn("togglePopulation"); else turnButtonOff("togglePopulation");
getCurrentPreset();
}()
void function restoreEvents() {
ruler.selectAll("g").call(d3.drag().on("start", dragRuler));
ruler.selectAll("text").on("click", removeParent);
ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge));
ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge));
ruler.selectAll("g.ruler rect").call(d3.drag().on("start", rulerCenterDrag));
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
scaleBar.on("mousemove", () => tip("Click to open Units Editor"));
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
}()
void function resolveVersionConflicts() {
const version = parseFloat(data[0].split("|")[0]);
if (version == 0.8) {
// 0.9 has additional relief icons to be included into older maps
document.getElementById("defs-relief").innerHTML = reliefIcons;
}
if (version < 1) {
// 1.0 adds a new religions layer
relig = viewbox.insert("g", "#terrain").attr("id", "relig");
Religions.generate();
// 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");
// 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");
// 1.0 adds state relations, provinces, forms and full names
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", .6);
BurgsAndStates.collectStatistics();
BurgsAndStates.generateDiplomacy();
BurgsAndStates.defineStateForms();
drawStates();
BurgsAndStates.generateProvinces();
drawBorders();
if (!layerIsOn("toggleBorders")) $('#borders').fadeOut();
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
// 1.0 adds hatching
document.getElementsByTagName("defs")[0].appendChild(hatching);
// 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");
addZone();
if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");}
// 1.0 add fogging layer (state focus)
let fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)")
.append("g").attr("id", "fogging").attr("display", "none");
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
defs.append("mask").attr("id", "fog").append("rect").attr("x", 0).attr("y", 0).attr("width", "100%")
.attr("height", "100%").attr("fill", "white");
// 1.0 changes states opacity bask to regions level
if (statesBody.attr("opacity")) {
regions.attr("opacity", statesBody.attr("opacity"));
statesBody.attr("opacity", null);
try {
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
void function parseParameters() {
const params = data[0].split("|");
if (params[3]) {seed = params[3]; optionsSeed.value = seed;}
if (params[4]) graphWidth = +params[4];
if (params[5]) graphHeight = +params[5];
}()
console.group("Loaded Map " + seed);
void function parseOptions() {
const options = data[1].split("|");
if (options[0]) applyOption(distanceUnitInput, options[0]);
if (options[1]) distanceScaleInput.value = distanceScaleOutput.value = options[1];
if (options[2]) areaUnit.value = options[2];
if (options[3]) applyOption(heightUnit, options[3]);
if (options[4]) heightExponentInput.value = heightExponentOutput.value = options[4];
if (options[5]) temperatureScale.value = options[5];
if (options[6]) barSize.value = barSizeOutput.value = options[6];
if (options[7] !== undefined) barLabel.value = options[7];
if (options[8] !== undefined) barBackOpacity.value = options[8];
if (options[9]) barBackColor.value = options[9];
if (options[10]) barPosX.value = options[10];
if (options[11]) barPosY.value = options[11];
if (options[12]) populationRate.value = populationRateOutput.value = options[12];
if (options[13]) urbanization.value = urbanizationOutput.value = options[13];
if (options[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(options[14], 100), 1);
if (options[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(options[15], 100), 0);
if (options[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = options[16];
if (options[17]) temperaturePoleInput.value = temperaturePoleOutput.value = options[17];
if (options[18]) precInput.value = precOutput.value = options[18];
if (options[19]) winds = JSON.parse(options[19]);
}()
void function parseConfiguration() {
if (data[2]) mapCoordinates = JSON.parse(data[2]);
if (data[4]) notes = JSON.parse(data[4]);
const biomes = data[3].split("|");
biomesData = applyDefaultBiomesSystem();
biomesData.color = biomes[0].split(",");
biomesData.habitability = biomes[1].split(",").map(h => +h);
biomesData.name = biomes[2].split(",");
// push custom biomes if any
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);
}
// 1.0 changed labels to multi-lined
labels.selectAll("textPath").each(function() {
const text = this.textContent;
const shift = this.getComputedTextLength() / -1.5;
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`;
});
// 1.0 added new biome - Wetland
biomesData.name.push("Wetland");
biomesData.color.push("#0b9131");
biomesData.habitability.push(12);
}
if (version == 1) {
// v 1.0 initial code had a bug with religion layer id
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
// v 1.0 initially has Sympathy status then relaced with Friendly
for (const s of pack.states) {
s.diplomacy = s.diplomacy.map(r => r === "Sympathy" ? "Friendly" : r);
}()
void function replaceSVG() {
svg.remove();
document.body.insertAdjacentHTML("afterbegin", data[5]);
}()
void function redefineElements() {
svg = d3.select("#map");
defs = svg.select("#deftemp");
viewbox = svg.select("#viewbox");
scaleBar = svg.select("#scaleBar");
legend = svg.select("#legend");
ocean = viewbox.select("#ocean");
oceanLayers = ocean.select("#oceanLayers");
oceanPattern = ocean.select("#oceanPattern");
lakes = viewbox.select("#lakes");
landmass = viewbox.select("#landmass");
texture = viewbox.select("#texture");
terrs = viewbox.select("#terrs");
biomes = viewbox.select("#biomes");
cells = viewbox.select("#cells");
gridOverlay = viewbox.select("#gridOverlay");
coordinates = viewbox.select("#coordinates");
compass = viewbox.select("#compass");
rivers = viewbox.select("#rivers");
terrain = viewbox.select("#terrain");
relig = viewbox.select("#relig");
cults = viewbox.select("#cults");
regions = viewbox.select("#regions");
statesBody = regions.select("#statesBody");
statesHalo = regions.select("#statesHalo");
provs = viewbox.select("#provs");
zones = viewbox.select("#zones");
borders = viewbox.select("#borders");
stateBorders = borders.select("#stateBorders");
provinceBorders = borders.select("#provinceBorders");
routes = viewbox.select("#routes");
roads = routes.select("#roads");
trails = routes.select("#trails");
searoutes = routes.select("#searoutes");
temperature = viewbox.select("#temperature");
coastline = viewbox.select("#coastline");
prec = viewbox.select("#prec");
population = viewbox.select("#population");
labels = viewbox.select("#labels");
icons = viewbox.select("#icons");
burgIcons = icons.select("#burgIcons");
anchors = icons.select("#anchors");
markers = viewbox.select("#markers");
ruler = viewbox.select("#ruler");
fogging = viewbox.select("#fogging");
debug = viewbox.select("#debug");
freshwater = lakes.select("#freshwater");
salt = lakes.select("#salt");
burgLabels = labels.select("#burgLabels");
}()
void function parseGridData() {
grid = JSON.parse(data[6]);
calculateVoronoi(grid, grid.points);
grid.cells.h = Uint8Array.from(data[7].split(","));
grid.cells.prec = Uint8Array.from(data[8].split(","));
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() {
pack = {};
reGraph();
reMarkFeatures();
pack.features = JSON.parse(data[12]);
pack.cultures = JSON.parse(data[13]);
pack.states = JSON.parse(data[14]);
pack.burgs = JSON.parse(data[15]);
pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: "No religion"}];
pack.provinces = data[30] ? JSON.parse(data[30]) : [0];
const cells = pack.cells;
cells.biome = Uint8Array.from(data[16].split(","));
cells.burg = Uint16Array.from(data[17].split(","));
cells.conf = Uint8Array.from(data[18].split(","));
cells.culture = Uint16Array.from(data[19].split(","));
cells.fl = Uint16Array.from(data[20].split(","));
cells.pop = Uint16Array.from(data[21].split(","));
cells.r = Uint16Array.from(data[22].split(","));
cells.road = Uint16Array.from(data[23].split(","));
cells.s = Uint16Array.from(data[24].split(","));
cells.state = Uint16Array.from(data[25].split(","));
cells.religion = data[26] ? Uint16Array.from(data[26].split(",")) : new Uint16Array(cells.i.length);
cells.province = data[27] ? Uint16Array.from(data[27].split(",")) : new Uint16Array(cells.i.length);
cells.crossroad = data[28] ? Uint16Array.from(data[28].split(",")) : new Uint16Array(cells.i.length);
}()
void function restoreLayersState() {
if (texture.style("display") !== "none" && texture.select("image").size()) turnButtonOn("toggleTexture"); else turnButtonOff("toggleTexture");
if (terrs.selectAll("*").size()) turnButtonOn("toggleHeight"); else turnButtonOff("toggleHeight");
if (biomes.selectAll("*").size()) turnButtonOn("toggleBiomes"); else turnButtonOff("toggleBiomes");
if (cells.selectAll("*").size()) turnButtonOn("toggleCells"); else turnButtonOff("toggleCells");
if (gridOverlay.selectAll("*").size()) turnButtonOn("toggleGrid"); else turnButtonOff("toggleGrid");
if (coordinates.selectAll("*").size()) turnButtonOn("toggleCoordinates"); else turnButtonOff("toggleCoordinates");
if (compass.style("display") !== "none" && compass.select("use").size()) turnButtonOn("toggleCompass"); else turnButtonOff("toggleCompass");
if (rivers.style("display") !== "none") turnButtonOn("toggleRivers"); else turnButtonOff("toggleRivers");
if (terrain.style("display") !== "none" && terrain.selectAll("*").size()) turnButtonOn("toggleRelief"); else turnButtonOff("toggleRelief");
if (relig.selectAll("*").size()) turnButtonOn("toggleReligions"); else turnButtonOff("toggleReligions");
if (cults.selectAll("*").size()) turnButtonOn("toggleCultures"); else turnButtonOff("toggleCultures");
if (statesBody.selectAll("*").size()) turnButtonOn("toggleStates"); else turnButtonOff("toggleStates");
if (provs.selectAll("*").size()) turnButtonOn("toggleProvinces"); else turnButtonOff("toggleProvinces");
if (zones.selectAll("*").size() && zones.style("display") !== "none") turnButtonOn("toggleZones"); else turnButtonOff("toggleZones");
if (borders.style("display") !== "none") turnButtonOn("toggleBorders"); else turnButtonOff("toggleBorders");
if (routes.style("display") !== "none" && routes.selectAll("path").size()) turnButtonOn("toggleRoutes"); else turnButtonOff("toggleRoutes");
if (temperature.selectAll("*").size()) turnButtonOn("toggleTemp"); else turnButtonOff("toggleTemp");
if (prec.selectAll("circle").size()) turnButtonOn("togglePrec"); else turnButtonOff("togglePrec");
if (labels.style("display") !== "none") turnButtonOn("toggleLabels"); else turnButtonOff("toggleLabels");
if (icons.style("display") !== "none") turnButtonOn("toggleIcons"); else turnButtonOff("toggleIcons");
if (markers.selectAll("*").size() && markers.style("display") !== "none") turnButtonOn("toggleMarkers"); else turnButtonOff("toggleMarkers");
if (ruler.style("display") !== "none") turnButtonOn("toggleRulers"); else turnButtonOff("toggleRulers");
if (scaleBar.style("display") !== "none") turnButtonOn("toggleScaleBar"); else turnButtonOff("toggleScaleBar");
// special case for population bars
const populationIsOn = population.selectAll("line").size();
if (populationIsOn) drawPopulation();
if (populationIsOn) turnButtonOn("togglePopulation"); else turnButtonOff("togglePopulation");
getCurrentPreset();
}()
void function restoreEvents() {
ruler.selectAll("g").call(d3.drag().on("start", dragRuler));
ruler.selectAll("text").on("click", removeParent);
ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge));
ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge));
ruler.selectAll("g.ruler rect").call(d3.drag().on("start", rulerCenterDrag));
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
scaleBar.on("mousemove", () => tip("Click to open Units Editor"));
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
}()
void function resolveVersionConflicts() {
const version = parseFloat(data[0].split("|")[0]);
if (version == 0.8) {
// 0.9 has additional relief icons to be included into older maps
document.getElementById("defs-relief").innerHTML = reliefIcons;
}
}
}()
if (version < 1) {
// 1.0 adds a new religions layer
relig = viewbox.insert("g", "#terrain").attr("id", "relig");
Religions.generate();
// 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");
// 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");
// 1.0 adds state relations, provinces, forms and full names
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", .6);
BurgsAndStates.collectStatistics();
BurgsAndStates.generateDiplomacy();
BurgsAndStates.defineStateForms();
drawStates();
BurgsAndStates.generateProvinces();
drawBorders();
if (!layerIsOn("toggleBorders")) $('#borders').fadeOut();
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
// 1.0 adds hatching
document.getElementsByTagName("defs")[0].appendChild(hatching);
// 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");
addZone();
if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");}
// 1.0 add fogging layer (state focus)
let fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)")
.append("g").attr("id", "fogging").attr("display", "none");
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
defs.append("mask").attr("id", "fog").append("rect").attr("x", 0).attr("y", 0).attr("width", "100%")
.attr("height", "100%").attr("fill", "white");
// 1.0 changes states opacity bask to regions level
if (statesBody.attr("opacity")) {
regions.attr("opacity", statesBody.attr("opacity"));
statesBody.attr("opacity", null);
}
// 1.0 changed labels to multi-lined
labels.selectAll("textPath").each(function() {
const text = this.textContent;
const shift = this.getComputedTextLength() / -1.5;
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`;
});
// 1.0 added new biome - Wetland
biomesData.name.push("Wetland");
biomesData.color.push("#0b9131");
biomesData.habitability.push(12);
}
if (version == 1) {
// v 1.0 initial code had a bug with religion layer id
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
// v 1.0 initially has Sympathy status then relaced with Friendly
for (const s of pack.states) {
s.diplomacy = s.diplomacy.map(r => r === "Sympathy" ? "Friendly" : r);
}
}
}()
changeMapSize();
if (window.restoreDefaultEvents) restoreDefaultEvents();
invokeActiveZooming();
console.warn(`TOTAL: ${rn((performance.now()-uploadFile.timeStart)/1000,2)}s`);
showStatistics();
console.groupEnd("Loaded Map " + seed);
tip("Map is successfully loaded", true, "success", 7000);
}
catch(error) {
console.error(error);
clearMainTip();
const regex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
const errorNoURL = error.stack.replace(regex, url => '<i>' + last(url.split("/")) + '</i>');
const errorParsed = errorNoURL.replace(/ at /ig, "<br>&nbsp;&nbsp;at ");
alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load,
<br>generate a new random map or cancel the loading
<p id="errorBox">${errorParsed}</p>`;
$("#alert").dialog({
resizable: false, title: "Loading error", maxWidth:500, 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"}
});
}
changeMapSize();
restoreDefaultEvents();
invokeActiveZooming();
tip("Map is successfully loaded", true, "success", 7000);
console.timeEnd("loadMap");
showStatistics();
}
async function quickSave() {
const blob = await getMapData();
if (blob) ldb.set("lastMap", blob); // auto-save map
tip("Map is saved to browser memory", true, "success", 2000);
}
function quickLoad() {
ldb.get("lastMap", blob => {
if (blob) {
loadMapPrompt(blob);
} else {
tip("No map stored. Save map to storage first", true, "error", 2000);
console.error("No map stored");
}
});
}
function loadMapPrompt(blob) {
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
if (workingTime < 10) {loadLastSavedMap(); return;}
alertMessage.innerHTML = `Are you sure you want to load saved map?<br>
All unsaved changes made to the current map will be lost`;
$("#alert").dialog({resizable: false, title: "Load saved map",
buttons: {
Cancel: function() {$(this).dialog("close");},
Load: function() {loadLastSavedMap(); $(this).dialog("close");}
}
});
function loadLastSavedMap() {
console.warn("Load last saved map");
closeDialogs();
try {
uploadFile(blob);
}
catch(error) {
console.error(error);
tip("Cannot load last saved map", true, "error", 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 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();
}
}

View file

@ -148,7 +148,7 @@ function editBurgs() {
function changeBurgPopulation() {
const burg = +this.parentNode.dataset.id;
if (this.value == "" || isNaN(+this.value)) {
tip("Please provide an integer number", false, "error");
tip("Please provide an integer number (like 10000, not 10K)", false, "error");
this.value = si(pack.burgs[burg].population * populationRate.value * urbanization.value);
return;
}

View file

@ -187,22 +187,43 @@ function editDiplomacy() {
const chronicle = pack.states[0].diplomacy;
if (!chronicle.length) {tip("Relations history is blank", false, "error"); return;}
let message = `<div>`;
chronicle.forEach(e => {
message += `<div style="margin: 0.5em 0">`;
e.forEach((l, i) => message += `<div${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
message += `</div>`;
let message = `<div autocorrect="off" spellcheck="false">`;
chronicle.forEach((e, d) => {
message += `<div>`;
e.forEach((l, i) => message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
message += `&#8205;</div>`;
});
alertMessage.innerHTML = message + `</div>`;
alertMessage.innerHTML = message + `</div><i id="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</i>`;
alertMessage.querySelectorAll("div[contenteditable='true']").forEach(el => el.addEventListener("input", changeReliationsHistory));
$("#alert").dialog({title: "Relations history", position: {my: "center", at: "center", of: "svg"},
buttons: {
Save: function() {
const text = this.querySelector("div").innerText.split("\n").join("\r\n");
const dataBlob = new Blob([text], {type: "text/plain"});
const url = window.URL.createObjectURL(dataBlob);
const link = document.createElement("a");
document.body.appendChild(link);
link.download = "state_relations_history" + Date.now() + ".txt";
link.href = url;
link.click();
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
},
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");},
Close: function() {$(this).dialog("close");}
}
});
}
function changeReliationsHistory() {
const i = this.dataset.id.split("-");
const group = pack.states[0].diplomacy[i[0]];
if (this.innerHTML === "") {
group.splice(i[1], 1);
this.remove();
} else group[i[1]] = this.innerHTML;
}
function showRelationsMatrix() {
const states = pack.states.filter(s => s.i && !s.removed);
const valid = states.map(s => s.i);

View file

@ -1,9 +1,6 @@
// Module to store general UI functions
"use strict";
// ask before closing the window
window.onbeforeunload = () => "Are you sure you want to navigate away?";
// fit full-screen map if window is resized
$(window).resize(function(e) {
mapWidthInput.value = window.innerWidth;
@ -11,6 +8,8 @@ $(window).resize(function(e) {
changeMapSize();
});
window.onbeforeunload = () => "Are you sure you want to navigate away?";
// Tooltips
const tooltip = document.getElementById("tooltip");
@ -230,16 +229,22 @@ function applyOption(select, option) {
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
document.addEventListener("keydown", function(event) {
const active = document.activeElement.tagName;
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return; // don't trigger if user inputs a text
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return; // don't trigger if user inputs a text
if (active === "DIV" && document.activeElement.contentEditable === "true") return; // don't trigger if user inputs a text
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey;
if (key === 118) regeneratePrompt(); // "F7" for new map
else if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
else if (key === 9) {toggleOptions(event); event.preventDefault();} // Tab to toggle options
else if (key === 113) regeneratePrompt(); // "F2" for new map
else if (key === 117) quickSave(); // "F6" for quick save
else if (key === 120) quickLoad(); // "F9" for quick load
else if (ctrl && key === 80) saveAsImage("png"); // Ctrl + "P" to save as PNG
else if (ctrl && key === 83) saveAsImage("svg"); // Ctrl + "S" to save as SVG
else if (ctrl && key === 77) saveMap(); // Ctrl + "M" to save MAP file
else if (ctrl && key === 85) mapToLoad.click(); // Ctrl + "U" to load MAP from URL
else if (ctrl && key === 76) mapToLoad.click(); // Ctrl + "L" to load MAP from local file
else if (ctrl && key === 81) toggleSaveReminder(); // Ctrl + "Q" to toggle save reminder
else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element
else if (shift && key === 192) console.log(pack.cells); // Shift + "`" to log cells data

View file

@ -22,22 +22,25 @@ function restoreLayers() {
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
}
// layers to be turned on; changable by user
let presets = {
"political": ["toggleBorders", "toggleIcons", "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", "toggleRivers", "toggleScaleBar"],
"heightmap": ["toggleHeight", "toggleRivers", "toggleScaleBar"],
"poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"landmass": ["toggleScaleBar"]
}
restoreLayers(); // run on-load
let presets = {}; // global object
restoreCustomPresets(); // run on-load
function getDefaultPresets() {
return {
"political": ["toggleBorders", "toggleIcons", "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", "toggleRivers", "toggleScaleBar"],
"heightmap": ["toggleHeight", "toggleRivers", "toggleScaleBar"],
"poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"landmass": ["toggleScaleBar"]
}
}
function restoreCustomPresets() {
presets = getDefaultPresets();
const storedPresets = JSON.parse(localStorage.getItem("presets"));
if (!storedPresets) return;
@ -45,6 +48,7 @@ function restoreCustomPresets() {
if (presets[preset]) continue;
layersPreset.add(new Option(preset, preset));
}
presets = storedPresets;
}
@ -62,34 +66,50 @@ function changePreset(preset) {
});
layersPreset.value = preset;
localStorage.setItem("preset", preset);
const isDefault = getDefaultPresets()[preset];
removePresetButton.style.display = isDefault ? "none" : "inline-block";
savePresetButton.style.display = "none";
}
function savePreset() {
// don't allow if layers should already esist as a preset
if (layersPreset.value !== "custom") {
tip(`Current layers are already saved as a "${layersPreset.selectedOptions[0].label}" preset`, false, "error");
return;
}
// add new preset
const preset = prompt("Please provide a preset name"); // preset name
if (!preset) return;
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);
removePresetButton.style.display = "inline-block";
savePresetButton.style.display = "none";
}
function removePreset() {
const preset = layersPreset.value;
delete presets[preset];
const index = Array.from(layersPreset.options).findIndex(o => o.value === preset);
layersPreset.options.remove(index);
layersPreset.value = "custom";
removePresetButton.style.display = "none";
savePresetButton.style.display = "inline-block";
localStorage.setItem("presets", JSON.stringify(presets));
localStorage.removeItem("preset");
}
function getCurrentPreset() {
const layers = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort();
const defaultPresets = getDefaultPresets();
for (const preset in presets) {
if (JSON.stringify(presets[preset]) !== JSON.stringify(layers)) continue;
layersPreset.value = preset;
removePresetButton.style.display = defaultPresets[preset] ? "none" : "inline-block";
return;
}
layersPreset.value = "custom";
removePresetButton.style.display = "none";
savePresetButton.style.display = "inline-block";
}
function toggleHeight() {

View file

@ -4,12 +4,6 @@
$("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"});
$("#mapLayers").disableSelection();
// show control elements and remove loading screen on map load
d3.select("#loading").transition().duration(5000).style("opacity", 0).remove();
d3.select("#initial").transition().duration(5000).attr("opacity", 0).remove();
d3.select("#optionsContainer").transition().duration(5000).style("opacity", 1);
d3.select("#tooltip").transition().duration(5000).style("opacity", 1);
// remove glow if tip is aknowledged
if (localStorage.getItem("disable_click_arrow_tooltip")) {
clearMainTip();
@ -723,7 +717,7 @@ function changeMapSize() {
oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
fitScaleBar();
fitLegendBox();
if (window.fitLegendBox) fitLegendBox();
}
// just apply map size that was already set, apply graph size!
@ -994,28 +988,29 @@ document.getElementById("sticked").addEventListener("click", function(event) {
else if (id === "saveButton") toggleSavePane();
else if (id === "loadButton") toggleLoadPane();
else if (id === "zoomReset") resetZoom(1000);
else if (id === "quickSave") quickSave();
else if (id === "saveMap") saveMap();
else if (id === "saveSVG") saveAsImage("svg");
else if (id === "savePNG") saveAsImage("png");
else if (id === "saveDropbox") saveDropbox();
if (id === "saveMap" || id === "saveSVG" || id === "savePNG" || id === "saveDropbox") toggleSavePane();
if (id === "loadMap") mapToLoad.click()
if (id === "loadURL") loadURL();
if (id === "loadDropbox") loadDropbox();
if (id === "loadURL" || id === "loadMap" || id === "loadDropbox") toggleLoadPane();
if (id === "quickSave" || id === "saveMap" || id === "saveSVG" || id === "savePNG" || id === "saveDropbox") toggleSavePane();
if (id === "loadMap") mapToLoad.click();
else if (id === "quickLoad") quickLoad();
else if (id === "loadURL") loadURL();
else if (id === "loadDropbox") loadDropbox();
if (id === "quickLoad" || id === "loadURL" || id === "loadMap" || id === "loadDropbox") toggleLoadPane();
});
function regeneratePrompt() {
if (customization) {tip("Please exit the customization mode first", false, "warning"); return;}
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
if (workingTime < 15) {regenerateMap(); return;}
if (workingTime < 10) {regenerateMap(); return;}
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
All unsaved changes made to the current map will be lost`;
$("#alert").dialog({resizable: false, title: "Generate new map",
buttons: {
Cancel: function() {$(this).dialog("close");},
Generate: regenerateMap
Generate: function() {closeDialogs(); regenerateMap();}
}
});
}
@ -1052,7 +1047,8 @@ function toggleSavePane() {
// };
// // working file: "https://dl.dropbox.com/s/llg93mwyonyzdmu/test.map?dl=1";
// const URL = await getMapURL();
// const dataBlob = await getMapData();
// const URL = window.URL.createObjectURL(dataBlob);
// Dropbox.save(URL, filename, options);
// }
@ -1072,6 +1068,7 @@ function loadURL() {
Load: function() {
const value = mapURL.value;
if (!pattern.test(value)) {tip("Please provide a valid URL", false, "error"); return;}
closeDialogs();
loadMapFromURL(value);
$(this).dialog("close");
},
@ -1104,5 +1101,6 @@ function loadURL() {
document.getElementById("mapToLoad").addEventListener("change", function() {
const fileToLoad = this.files[0];
this.value = "";
closeDialogs();
uploadFile(fileToLoad);
});

File diff suppressed because one or more lines are too long