This commit is contained in:
Azgaar 2019-09-03 22:34:52 +03:00
parent 52e8f28cd0
commit 92a50d9810
9 changed files with 161 additions and 75 deletions

View file

@ -996,14 +996,26 @@ div.slider .ui-slider-handle {
top: 100%;
border: 1px solid #5e4fa2;
background-color: #a4879b;
width: 44px;
width: 3.6em;
}
#loadDropdown {
display: none;
position: absolute;
left: 53%;
top: 100%;
border: 1px solid #5e4fa2;
background-color: #a4879b;
width: 6em;
}
#loadDropdown>div,
#saveDropdown>div {
padding: 2px 4px;
cursor: pointer;
}
#loadDropdown>div:hover,
#saveDropdown>div:hover {
color: white;
}

View file

@ -1814,13 +1814,17 @@
<div id="sticked">
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F7">New Map</button>
<button id="saveButton" data-tip="Select file format to save map">Save as</button>
<button id="saveButton" data-tip="Select file format to save map">Save</button>
<div id="saveDropdown">
<div id="saveMap" data-tip="Save as fully functional map in .map format. Shortcut: Ctrl + M">.map</div>
<div id="saveSVG" data-tip="Download the map as .svg image (open in browser or Inkscape). Shortcut: Ctrl + S">.svg</div>
<div id="savePNG" data-tip="Download visible part of the map as image. Texture will not be shown. Shortcut: Ctrl + P">.png</div>
</div>
<button id="loadMap" data-tip="Load fully functional map in a .map format. Shortcut: Ctrl + L">Load</button>
<button id="loadButton" data-tip="Load fully functional map in a .map format">Load</button>
<div id="loadDropdown">
<div id="loadURL" data-tip="Load .map file from URL: Ctrl + U">from URL</div>
<div id="loadMap" data-tip="Load .map file from local disk. Shortcut: Ctrl + L">from file</div>
</div>
<button id="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
</div>
@ -2945,6 +2949,7 @@
<script defer src="modules/ui/general.js"></script>
<script defer src="modules/ui/options.js"></script>
<script defer src="modules/ui/measurers.js"></script>
<script defer src="modules/save-and-load.js"></script>
<script defer src="main.js?version=1.0"></script>
<script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/tools.js"></script>
@ -2969,7 +2974,6 @@
<script defer src="modules/ui/diplomacy-editor.js"></script>
<script defer src="modules/ui/zones-editor.js"></script>
<script defer src="modules/ui/editors.js"></script>
<script defer src="modules/save-and-load.js"></script>
<script defer src="libs/quantize.min.js"></script>
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
</body>

152
main.js
View file

@ -81,9 +81,8 @@ population.append("g").attr("id", "urban");
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
// assign events separately as not a viewbox child
scaleBar.on("mousemove", function() {tip("Click to open Units Editor")});
legend.on("mousemove", function() {tip("Drag to change the position. Click to hide the legend")})
.on("click", () => clearLegend());
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());
// main data variables
let grid = {}; // initial grapg based on jittered square grid and data
@ -111,61 +110,52 @@ landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr
oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
applyDefaultNamesData(); // apply default namesbase on load
applyDefaultStyle(); // apply style on load
generate(); // generate map on load
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
applyPreset(); // apply saved layers preset on load
applyDefaultNamesData(); // always apply default namesbase load as namesdata is not stored in .map file
// show message on load if required
setTimeout(showWelcomeMessage, 7000);
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>.
// load linked map from url or generate a random map
void function checkLoadParameters() {
const url = new URL(window.location.href);
const maplink = url.searchParams.get("maplink");
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>
<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>`;
$("#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"}
});
// check if URL is valid
if (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);
}
generateRandomMapOnLoad();
}()
function loadMapFromURL(maplink, random) {
const URL = decodeURIComponent(maplink);
fetch(URL, {method: 'GET', mode: 'cors'})
.then(response => {
if(response.ok) return response.blob();
throw new Error("Cannot load map from URL");
}).then(blob => uploadFile(blob))
.catch(error => {
showUploadErrorMessage(error.message, URL, random);
if (random) generateRandomMapOnLoad();
});
}
function showUploadErrorMessage(error, URL, random) {
console.log(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`;
$("#alert").dialog({title: "Loading error", width: 320, buttons: {OK: function() {$(this).dialog("close");}}});
}
function generateRandomMapOnLoad() {
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
}
function applyDefaultNamesData() {
@ -431,11 +421,10 @@ function findBurgForMFCG(params) {
const label = burgLabels.select("[data-id='" + b + "']");
if (label.size()) {
tip("Here stands the glorious city of " + burgs[b].name, true, "error");
tip("Here stands the glorious city of " + burgs[b].name, true, "success", 10000);
label.classed("drag", true).on("mouseover", function() {
d3.select(this).classed("drag", false);
label.on("mouseover", null);
tip("", true);
});
}
@ -443,6 +432,55 @@ function findBurgForMFCG(params) {
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>.
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>
<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>`;
$("#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"}
});
}
}
function zoomed() {
const transform = d3.event.transform;
const scaleDiff = scale - transform.k;

View file

@ -265,6 +265,8 @@
const type = states[s].type;
cells.c[n].forEach(function(e) {
if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells
const cultureCost = states[s].culture === cells.culture[e] ? -9 : 700;
const biomeCost = getBiomeCost(cells.road[e], b, cells.biome[e], type);
const heightCost = getHeightCost(pack.features[cells.f[e]], cells.h[e], type);
@ -278,7 +280,6 @@
if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell
cost[e] = totalCost;
queue.queue({e, p:totalCost, s, b});
//const points = [cells.p[n][0], cells.p[n][1], (cells.p[n][0]+cells.p[e][0])/2, (cells.p[n][1]+cells.p[e][1])/2, cells.p[e][0], cells.p[e][1]];
//debug.append("text").attr("x", (cells.p[n][0]+cells.p[e][0])/2 - 1).attr("y", (cells.p[n][1]+cells.p[e][1])/2 - 1).text(rn(totalCost-p)).attr("font-size", .8);
//debug.append("polyline").attr("points", points).attr("marker-mid", "url(#arrow)").attr("opacity", .6);

View file

@ -210,6 +210,7 @@ function saveMap() {
function uploadFile(file, callback) {
console.time("loadMap");
closeDialogs();
const fileReader = new FileReader();
fileReader.onload = function(fileLoadedEvent) {
const dataLoaded = fileLoadedEvent.target.result;
@ -423,7 +424,7 @@ function parseLoadedData(data) {
getCurrentPreset();
}()
void function restoreRulersEvents() {
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));
@ -431,6 +432,9 @@ function parseLoadedData(data) {
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() {
@ -518,6 +522,7 @@ function parseLoadedData(data) {
changeMapSize();
restoreDefaultEvents();
invokeActiveZooming();
tip("Map is loaded");
tip("Map is successfully loaded", true, "success", 7000);
console.timeEnd("loadMap");
showStatistics();
}

View file

@ -94,7 +94,7 @@ function editBurg() {
}
function createNewGroup() {
if (!this.value) {tip("Please provide a valid group name"); return;}
if (!this.value) {tip("Please provide a valid group name", false, "error"); return;}
let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
if (Number.isFinite(+group.charAt(0))) group = "g" + group;

View file

@ -186,7 +186,7 @@ function editBurgs() {
if (!pack.burgs[burg].port) {
const haven = pack.cells.haven[pack.burgs[burg].cell];
const port = haven ? pack.cells.f[haven] : -1;
if (!haven) tip("Port haven is not found, system won't be able to make a searoute", false, "warning");
if (!haven) tip("Port haven is not found, system won't be able to make a searoute", false, "warn");
pack.burgs[burg].port = port;
const g = pack.burgs[burg].capital ? "cities" : "towns";

View file

@ -18,16 +18,18 @@ const tooltip = document.getElementById("tooltip");
document.getElementById("dialogs").addEventListener("mousemove", showDataTip);
document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip);
function tip(tip = "Tip is undefined", main = false, error = false) {
const reg = "linear-gradient(0.1turn, #ffffff00, #5e5c5c66, #ffffff00)";
const red = "linear-gradient(0.1turn, #ffffff00, #e3141499, #ffffff00)";
function tip(tip = "Tip is undefined", main, error, time) {
tooltip.innerHTML = tip;
tooltip.style.background = error ? red : reg;
if (main) tooltip.dataset.main = tip;
tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)";
if (error === "error") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)"; else
if (error === "warn") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)"; else
if (error === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)";
if (main) tooltip.dataset.main = tip; // set main tip
if (time) setTimeout(tooltip.dataset.main = "", time); // clear main in some time
}
function showMainTip() {
tooltip.style.background = "linear-gradient(0.1turn, #aaffff00, #3a26264d, #ffffff00)";
tooltip.innerHTML = tooltip.dataset.main;
}
@ -236,7 +238,8 @@ document.addEventListener("keydown", function(event) {
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 === 76) mapToLoad.click(); // Ctrl + "L" to load MAP
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 (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

@ -677,6 +677,7 @@ optionsContent.addEventListener("input", function(event) {
else if (id === "provincesInput") provincesOutput.value = value;
else if (id === "provincesOutput") provincesOutput.value = value;
else if (id === "provincesOutput") powerOutput.value = value;
else if (id === "powerInput") powerOutput.value = value;
else if (id === "powerOutput") powerInput.value = value;
else if (id === "neutralInput") neutralOutput.value = value;
else if (id === "neutralOutput") neutralInput.value = value;
@ -991,12 +992,14 @@ document.getElementById("sticked").addEventListener("click", function(event) {
const id = event.target.id;
if (id === "newMapButton") regeneratePrompt();
else if (id === "saveButton") toggleSavePane();
else if (id === "loadMap") mapToLoad.click();
else if (id === "loadButton") toggleLoadPane();
else if (id === "zoomReset") resetZoom(1000);
else if (id === "saveMap") saveMap();
else if (id === "saveSVG") saveAsImage("svg");
else if (id === "savePNG") saveAsImage("png");
if (id === "saveMap" || id === "saveSVG" || id === "savePNG") toggleSavePane();
if (id === "loadURL") {loadURL(); toggleLoadPane()}
if (id === "loadMap") {mapToLoad.click(); toggleLoadPane()}
});
function regeneratePrompt() {
@ -1033,12 +1036,32 @@ function toggleSavePane() {
}
});
}
}
function toggleLoadPane() {
if (loadDropdown.style.display === "block") {loadDropdown.style.display = "none"; return;}
loadDropdown.style.display = "block";
}
function loadURL() {
const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
const inner = `Provide URL to a .map file: <input id="mapURL" type="url" style="width: 254px" placeholder="http://www.e-cloud.com/fantasy_map.map"">`;
alertMessage.innerHTML = inner;
$("#alert").dialog({resizable: false, title: "Load map from URL", width: 280,
buttons: {
Load: function() {
const value = mapURL.value;
if (!pattern.test(value)) {tip("Please provide a valid URL", false, "error"); return;}
loadMapFromURL(value);
$(this).dialog("close");
},
Cancel: function() {$(this).dialog("close");}
}
});
}
// load map
document.getElementById("mapToLoad").addEventListener("change", function() {
closeDialogs();
const fileToLoad = this.files[0];
this.value = "";
uploadFile(fileToLoad);