mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 20:11:24 +01:00
load auto-update sctipt dynamically
This commit is contained in:
parent
08a9d75bc9
commit
d49ecd209f
7 changed files with 759 additions and 1887 deletions
141
modules/cloud.js
141
modules/cloud.js
|
|
@ -1,141 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/*
|
|
||||||
Cloud provider implementations (Dropbox only as now)
|
|
||||||
|
|
||||||
provider Interface:
|
|
||||||
|
|
||||||
name: name of the provider
|
|
||||||
async auth(): authenticate and get access tokens from provider
|
|
||||||
async save(filename): save map file to provider as filename
|
|
||||||
async load(filename): load filename from provider
|
|
||||||
async list(): list available filenames at provider
|
|
||||||
async getLink(filePath): get shareable link for file
|
|
||||||
restore(): restore access tokens from storage if possible
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.Cloud = (function () {
|
|
||||||
|
|
||||||
// helpers to use in providers for token handling
|
|
||||||
const lSKey = x => `auth-${x}`
|
|
||||||
const setToken = (prov, key) => localStorage.setItem(lSKey(prov), key)
|
|
||||||
const getToken = prov => localStorage.getItem(lSKey(prov))
|
|
||||||
|
|
||||||
/**********************************************************/
|
|
||||||
/* Dropbox provider */
|
|
||||||
/**********************************************************/
|
|
||||||
|
|
||||||
const DBP = {
|
|
||||||
name: 'dropbox',
|
|
||||||
clientId: 'sp7tzwm27u2w5ns',
|
|
||||||
authWindow: undefined,
|
|
||||||
token: null, // Access token
|
|
||||||
api: null,
|
|
||||||
|
|
||||||
restore() {
|
|
||||||
this.token = getToken(this.name)
|
|
||||||
if (this.token) this.connect(this.token)
|
|
||||||
},
|
|
||||||
|
|
||||||
async call(name, param) {
|
|
||||||
try {
|
|
||||||
return await this.api[name](param)
|
|
||||||
} catch (e) {
|
|
||||||
if (e.name !== "DropboxResponseError") throw(e)
|
|
||||||
// retry with auth
|
|
||||||
await this.auth()
|
|
||||||
return await this.api[name](param)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
connect(token) {
|
|
||||||
const clientId = this.clientId
|
|
||||||
const auth = new Dropbox.DropboxAuth({ clientId })
|
|
||||||
auth.setAccessToken(token)
|
|
||||||
this.api = new Dropbox.Dropbox({ auth })
|
|
||||||
},
|
|
||||||
|
|
||||||
async save(fileName, contents) {
|
|
||||||
if (!this.api) await this.auth()
|
|
||||||
const resp = this.call('filesUpload', { path: '/' + fileName, contents })
|
|
||||||
console.log("Dropbox response:", resp)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
async load(path) {
|
|
||||||
if (!this.api) await this.auth()
|
|
||||||
const resp = await this.call('filesDownload', { path })
|
|
||||||
const blob = resp.result.fileBlob
|
|
||||||
if (!blob) throw(new Error('Invalid response from dropbox.'))
|
|
||||||
return blob
|
|
||||||
},
|
|
||||||
|
|
||||||
async list() {
|
|
||||||
if (!this.api) return null
|
|
||||||
const resp = await this.call('filesListFolder', { path: '' })
|
|
||||||
return resp.result.entries.map(e => ({ name: e.name, path: e.path_lower }))
|
|
||||||
},
|
|
||||||
|
|
||||||
auth() {
|
|
||||||
const url = window.location.origin + window.location.pathname + 'dropbox.html'
|
|
||||||
this.authWindow = window.open(url, 'auth', 'width=640,height=480')
|
|
||||||
// child window expected to call
|
|
||||||
// window.opener.Cloud.providers.dropbox.setDropBoxToken (see below)
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const watchDog = () => {
|
|
||||||
this.authWindow.close()
|
|
||||||
reject(new Error("Timeout. No auth for dropbox."))
|
|
||||||
}
|
|
||||||
setTimeout(watchDog, 120*1000)
|
|
||||||
window.addEventListener('dropboxauth', e => {
|
|
||||||
clearTimeout(watchDog)
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// Callback function for auth window.
|
|
||||||
setDropBoxToken(token) {
|
|
||||||
console.log('Access token got:', token)
|
|
||||||
setToken(this.name, token)
|
|
||||||
this.connect(token)
|
|
||||||
this.authWindow.close()
|
|
||||||
window.dispatchEvent(new Event('dropboxauth'))
|
|
||||||
},
|
|
||||||
|
|
||||||
async getLink(path) {
|
|
||||||
if (!this.api) await this.auth()
|
|
||||||
let resp
|
|
||||||
|
|
||||||
// already exists?
|
|
||||||
resp = await this.call('sharingListSharedLinks', { path })
|
|
||||||
if (resp.result.links.length)
|
|
||||||
return resp.result.links[0].url
|
|
||||||
|
|
||||||
// create new
|
|
||||||
resp = await this.call('sharingCreateSharedLinkWithSettings', {
|
|
||||||
path,
|
|
||||||
settings: {
|
|
||||||
require_password: false,
|
|
||||||
audience: 'public',
|
|
||||||
access: 'viewer',
|
|
||||||
requested_visibility: 'public',
|
|
||||||
allow_download: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
console.log("dropbox link object:", resp.result)
|
|
||||||
return resp.result.url
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// register providers here:
|
|
||||||
const providers = {
|
|
||||||
dropbox: DBP,
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore all providers at startup
|
|
||||||
for (const p of Object.values(providers)) p.restore()
|
|
||||||
|
|
||||||
return { providers }
|
|
||||||
})()
|
|
||||||
|
|
@ -1,500 +0,0 @@
|
||||||
"use strict";
|
|
||||||
// Functions to export map to image or data files
|
|
||||||
|
|
||||||
// download map as SVG
|
|
||||||
async function saveSVG() {
|
|
||||||
TIME && console.time("saveSVG");
|
|
||||||
const url = await getMapURL("svg");
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.download = getFileName() + ".svg";
|
|
||||||
link.href = url;
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
|
|
||||||
TIME && console.timeEnd("saveSVG");
|
|
||||||
}
|
|
||||||
|
|
||||||
// download map as PNG
|
|
||||||
async function savePNG() {
|
|
||||||
TIME && console.time("savePNG");
|
|
||||||
const url = await getMapURL("png");
|
|
||||||
|
|
||||||
const link = document.createElement("a");
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
canvas.width = svgWidth * pngResolutionInput.value;
|
|
||||||
canvas.height = svgHeight * pngResolutionInput.value;
|
|
||||||
const img = new Image();
|
|
||||||
img.src = url;
|
|
||||||
|
|
||||||
img.onload = function () {
|
|
||||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
||||||
link.download = getFileName() + ".png";
|
|
||||||
canvas.toBlob(function (blob) {
|
|
||||||
link.href = window.URL.createObjectURL(blob);
|
|
||||||
link.click();
|
|
||||||
window.setTimeout(function () {
|
|
||||||
canvas.remove();
|
|
||||||
window.URL.revokeObjectURL(link.href);
|
|
||||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
TIME && console.timeEnd("savePNG");
|
|
||||||
}
|
|
||||||
|
|
||||||
// download map as JPEG
|
|
||||||
async function saveJPEG() {
|
|
||||||
TIME && console.time("saveJPEG");
|
|
||||||
const url = await getMapURL("png");
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
canvas.width = svgWidth * pngResolutionInput.value;
|
|
||||||
canvas.height = svgHeight * pngResolutionInput.value;
|
|
||||||
const img = new Image();
|
|
||||||
img.src = url;
|
|
||||||
|
|
||||||
img.onload = async function () {
|
|
||||||
canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
||||||
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
|
|
||||||
const URL = await canvas.toDataURL("image/jpeg", quality);
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.download = getFileName() + ".jpeg";
|
|
||||||
link.href = URL;
|
|
||||||
link.click();
|
|
||||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
|
||||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
|
||||||
};
|
|
||||||
|
|
||||||
TIME && console.timeEnd("saveJPEG");
|
|
||||||
}
|
|
||||||
|
|
||||||
// download map as png tiles
|
|
||||||
async function saveTiles() {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
// download schema
|
|
||||||
const urlSchema = await getMapURL("tiles", {debug: true});
|
|
||||||
const zip = new JSZip();
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
canvas.width = graphWidth;
|
|
||||||
canvas.height = graphHeight;
|
|
||||||
|
|
||||||
const imgSchema = new Image();
|
|
||||||
imgSchema.src = urlSchema;
|
|
||||||
imgSchema.onload = function () {
|
|
||||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
|
||||||
canvas.toBlob(blob => zip.file(`fmg_tile_schema.png`, blob));
|
|
||||||
};
|
|
||||||
|
|
||||||
// download tiles
|
|
||||||
const url = await getMapURL("tiles");
|
|
||||||
const tilesX = +document.getElementById("tileColsInput").value;
|
|
||||||
const tilesY = +document.getElementById("tileRowsInput").value;
|
|
||||||
const scale = +document.getElementById("tileScaleInput").value;
|
|
||||||
|
|
||||||
const tileW = (graphWidth / tilesX) | 0;
|
|
||||||
const tileH = (graphHeight / tilesY) | 0;
|
|
||||||
const tolesTotal = tilesX * tilesY;
|
|
||||||
|
|
||||||
const width = graphWidth * scale;
|
|
||||||
const height = width * (tileH / tileW);
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
|
|
||||||
let loaded = 0;
|
|
||||||
const img = new Image();
|
|
||||||
img.src = url;
|
|
||||||
img.onload = function () {
|
|
||||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
|
||||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
|
||||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
|
||||||
const name = `fmg_tile_${i}.png`;
|
|
||||||
canvas.toBlob(blob => {
|
|
||||||
zip.file(name, blob);
|
|
||||||
loaded += 1;
|
|
||||||
if (loaded === tolesTotal) return downloadZip();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function downloadZip() {
|
|
||||||
const name = `${getFileName()}.zip`;
|
|
||||||
zip.generateAsync({type: "blob"}).then(blob => {
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
link.download = name;
|
|
||||||
link.click();
|
|
||||||
link.remove();
|
|
||||||
|
|
||||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse map svg to object url
|
|
||||||
async function getMapURL(type, options = {}) {
|
|
||||||
const {debug = false, globe = false, noLabels = false, noWater = false} = options;
|
|
||||||
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
|
|
||||||
cloneEl.id = "fantasyMap";
|
|
||||||
document.body.appendChild(cloneEl);
|
|
||||||
const clone = d3.select(cloneEl);
|
|
||||||
if (!debug) clone.select("#debug")?.remove();
|
|
||||||
|
|
||||||
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
|
||||||
const svgDefs = document.getElementById("defElements");
|
|
||||||
|
|
||||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
|
||||||
if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove();
|
|
||||||
if (globe) clone.select("#scaleBar")?.remove();
|
|
||||||
if (noLabels) {
|
|
||||||
clone.select("#labels #states")?.remove();
|
|
||||||
clone.select("#labels #burgLabels")?.remove();
|
|
||||||
clone.select("#icons #burgIcons")?.remove();
|
|
||||||
}
|
|
||||||
if (noWater) {
|
|
||||||
clone.select("#oceanBase").attr("opacity", 0);
|
|
||||||
clone.select("#oceanPattern").attr("opacity", 0);
|
|
||||||
}
|
|
||||||
if (type !== "png") {
|
|
||||||
// reset transform to show the whole map
|
|
||||||
clone.attr("width", graphWidth).attr("height", graphHeight);
|
|
||||||
clone.select("#viewbox").attr("transform", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "svg") removeUnusedElements(clone);
|
|
||||||
if (customization && type === "mesh") updateMeshCells(clone);
|
|
||||||
inlineStyle(clone);
|
|
||||||
|
|
||||||
// remove unused filters
|
|
||||||
const filters = cloneEl.querySelectorAll("filter");
|
|
||||||
for (let i = 0; i < filters.length; i++) {
|
|
||||||
const id = filters[i].id;
|
|
||||||
if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue;
|
|
||||||
if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue;
|
|
||||||
filters[i].remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove unused patterns
|
|
||||||
const patterns = cloneEl.querySelectorAll("pattern");
|
|
||||||
for (let i = 0; i < patterns.length; i++) {
|
|
||||||
const id = patterns[i].id;
|
|
||||||
if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue;
|
|
||||||
patterns[i].remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove unused symbols
|
|
||||||
const symbols = cloneEl.querySelectorAll("symbol");
|
|
||||||
for (let i = 0; i < symbols.length; i++) {
|
|
||||||
const id = symbols[i].id;
|
|
||||||
if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue;
|
|
||||||
symbols[i].remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// add displayed emblems
|
|
||||||
if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) {
|
|
||||||
cloneEl
|
|
||||||
.getElementById("emblems")
|
|
||||||
?.querySelectorAll("use")
|
|
||||||
.forEach(el => {
|
|
||||||
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
|
|
||||||
if (!href) return;
|
|
||||||
const emblem = document.getElementById(href.slice(1));
|
|
||||||
if (emblem) cloneDefs.append(emblem.cloneNode(true));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cloneDefs.querySelector("#defs-emblems")?.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace ocean pattern href to base64
|
|
||||||
if (location.hostname && cloneEl.getElementById("oceanicPattern")) {
|
|
||||||
const el = cloneEl.getElementById("oceanicPattern");
|
|
||||||
const url = el.getAttribute("href");
|
|
||||||
await new Promise(resolve => {
|
|
||||||
getBase64(url, base64 => {
|
|
||||||
el.setAttribute("href", base64);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add relief icons
|
|
||||||
if (cloneEl.getElementById("terrain")) {
|
|
||||||
const uniqueElements = new Set();
|
|
||||||
const terrainNodes = cloneEl.getElementById("terrain").childNodes;
|
|
||||||
for (let i = 0; i < terrainNodes.length; i++) {
|
|
||||||
const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href");
|
|
||||||
uniqueElements.add(href);
|
|
||||||
}
|
|
||||||
|
|
||||||
const defsRelief = svgDefs.getElementById("defs-relief");
|
|
||||||
for (const terrain of [...uniqueElements]) {
|
|
||||||
const element = defsRelief.querySelector(terrain);
|
|
||||||
if (element) cloneDefs.appendChild(element.cloneNode(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add wind rose
|
|
||||||
if (cloneEl.getElementById("compass")) {
|
|
||||||
const rose = svgDefs.getElementById("rose");
|
|
||||||
if (rose) cloneDefs.appendChild(rose.cloneNode(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// add port icon
|
|
||||||
if (cloneEl.getElementById("anchors")) {
|
|
||||||
const anchor = svgDefs.getElementById("icon-anchor");
|
|
||||||
if (anchor) cloneDefs.appendChild(anchor.cloneNode(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// add grid pattern
|
|
||||||
if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) {
|
|
||||||
const type = cloneEl.getElementById("gridOverlay").getAttribute("type");
|
|
||||||
const pattern = svgDefs.getElementById("pattern_" + type);
|
|
||||||
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
|
|
||||||
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
|
|
||||||
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
|
|
||||||
|
|
||||||
// add armies style
|
|
||||||
if (cloneEl.getElementById("armies")) {
|
|
||||||
cloneEl.insertAdjacentHTML(
|
|
||||||
"afterbegin",
|
|
||||||
"<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add xlink: for href to support svg 1.1
|
|
||||||
if (type === "svg") {
|
|
||||||
cloneEl.querySelectorAll("[href]").forEach(el => {
|
|
||||||
const href = el.getAttribute("href");
|
|
||||||
el.removeAttribute("href");
|
|
||||||
el.setAttribute("xlink:href", href);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add hatchings
|
|
||||||
const hatchingUsers = cloneEl.querySelectorAll(`[fill^='url(#hatch']`);
|
|
||||||
const hatchingFills = unique(Array.from(hatchingUsers).map(el => el.getAttribute("fill")));
|
|
||||||
const hatchingIds = hatchingFills.map(fill => fill.slice(5, -1));
|
|
||||||
for (const hatchingId of hatchingIds) {
|
|
||||||
const hatching = svgDefs.getElementById(hatchingId);
|
|
||||||
if (hatching) cloneDefs.appendChild(hatching.cloneNode(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// load fonts
|
|
||||||
const usedFonts = getUsedFonts(cloneEl);
|
|
||||||
const fontsToLoad = usedFonts.filter(font => font.src);
|
|
||||||
if (fontsToLoad.length) {
|
|
||||||
const dataURLfonts = await loadFontsAsDataURI(fontsToLoad);
|
|
||||||
|
|
||||||
const fontFaces = dataURLfonts
|
|
||||||
.map(({family, src, unicodeRange = "", variant = "normal"}) => {
|
|
||||||
return `@font-face {font-family: "${family}"; src: ${src}; unicode-range: ${unicodeRange}; font-variant: ${variant};}`;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.setAttribute("type", "text/css");
|
|
||||||
style.innerHTML = fontFaces;
|
|
||||||
cloneEl.querySelector("defs").appendChild(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
clone.remove();
|
|
||||||
|
|
||||||
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + new XMLSerializer().serializeToString(cloneEl);
|
|
||||||
const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"});
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
|
||||||
function removeUnusedElements(clone) {
|
|
||||||
if (!terrain.selectAll("use").size()) clone.select("#defs-relief")?.remove();
|
|
||||||
|
|
||||||
for (let empty = 1; empty; ) {
|
|
||||||
empty = 0;
|
|
||||||
clone.selectAll("g").each(function () {
|
|
||||||
if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) {
|
|
||||||
empty++;
|
|
||||||
this.remove();
|
|
||||||
}
|
|
||||||
if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMeshCells(clone) {
|
|
||||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
|
||||||
const scheme = getColorScheme();
|
|
||||||
clone.select("#heights").attr("filter", "url(#blur1)");
|
|
||||||
clone
|
|
||||||
.select("#heights")
|
|
||||||
.selectAll("polygon")
|
|
||||||
.data(data)
|
|
||||||
.join("polygon")
|
|
||||||
.attr("points", d => getGridPolygon(d))
|
|
||||||
.attr("id", d => "cell" + d)
|
|
||||||
.attr("stroke", d => getColor(grid.cells.h[d], scheme));
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each g element get inline style
|
|
||||||
function inlineStyle(clone) {
|
|
||||||
const emptyG = clone.append("g").node();
|
|
||||||
const defaultStyles = window.getComputedStyle(emptyG);
|
|
||||||
|
|
||||||
clone.selectAll("g, #ruler *, #scaleBar > text").each(function () {
|
|
||||||
const compStyle = window.getComputedStyle(this);
|
|
||||||
let style = "";
|
|
||||||
|
|
||||||
for (let i = 0; i < compStyle.length; i++) {
|
|
||||||
const key = compStyle[i];
|
|
||||||
const value = compStyle.getPropertyValue(key);
|
|
||||||
|
|
||||||
// Firefox mask hack
|
|
||||||
if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) {
|
|
||||||
style += "mask-image: url('#land');";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === "cursor") continue; // cursor should be default
|
|
||||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
|
||||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
|
||||||
style += key + ":" + value + ";";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in compStyle) {
|
|
||||||
const value = compStyle.getPropertyValue(key);
|
|
||||||
|
|
||||||
if (key === "cursor") continue; // cursor should be default
|
|
||||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
|
||||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
|
||||||
style += key + ":" + value + ";";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style != "") this.setAttribute("style", style);
|
|
||||||
});
|
|
||||||
|
|
||||||
emptyG.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveGeoJSON_Cells() {
|
|
||||||
const json = {type: "FeatureCollection", features: []};
|
|
||||||
const cells = pack.cells;
|
|
||||||
const getPopulation = i => {
|
|
||||||
const [r, u] = getCellPopulation(i);
|
|
||||||
return rn(r + u);
|
|
||||||
};
|
|
||||||
const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
|
|
||||||
|
|
||||||
cells.i.forEach(i => {
|
|
||||||
const coordinates = getCellCoordinates(cells.v[i]);
|
|
||||||
const height = getHeight(i);
|
|
||||||
const biome = cells.biome[i];
|
|
||||||
const type = pack.features[cells.f[i]].type;
|
|
||||||
const population = getPopulation(i);
|
|
||||||
const state = cells.state[i];
|
|
||||||
const province = cells.province[i];
|
|
||||||
const culture = cells.culture[i];
|
|
||||||
const religion = cells.religion[i];
|
|
||||||
const neighbors = cells.c[i];
|
|
||||||
|
|
||||||
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors};
|
|
||||||
const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties};
|
|
||||||
json.features.push(feature);
|
|
||||||
});
|
|
||||||
|
|
||||||
const name = getFileName("Cells") + ".geojson";
|
|
||||||
downloadFile(JSON.stringify(json), name, "application/json");
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveGeoJSON_Routes() {
|
|
||||||
const json = {type: "FeatureCollection", features: []};
|
|
||||||
|
|
||||||
routes.selectAll("g > path").each(function () {
|
|
||||||
const coordinates = getRoutePoints(this);
|
|
||||||
const id = this.id;
|
|
||||||
const type = this.parentElement.id;
|
|
||||||
|
|
||||||
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}};
|
|
||||||
json.features.push(feature);
|
|
||||||
});
|
|
||||||
|
|
||||||
const name = getFileName("Routes") + ".geojson";
|
|
||||||
downloadFile(JSON.stringify(json), name, "application/json");
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveGeoJSON_Rivers() {
|
|
||||||
const json = {type: "FeatureCollection", features: []};
|
|
||||||
|
|
||||||
rivers.selectAll("path").each(function () {
|
|
||||||
const coordinates = getRiverPoints(this);
|
|
||||||
const id = this.id;
|
|
||||||
const width = +this.dataset.increment;
|
|
||||||
const increment = +this.dataset.increment;
|
|
||||||
const river = pack.rivers.find(r => r.i === +id.slice(5));
|
|
||||||
const name = river ? river.name : "";
|
|
||||||
const type = river ? river.type : "";
|
|
||||||
const i = river ? river.i : "";
|
|
||||||
const basin = river ? river.basin : "";
|
|
||||||
|
|
||||||
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}};
|
|
||||||
json.features.push(feature);
|
|
||||||
});
|
|
||||||
|
|
||||||
const name = getFileName("Rivers") + ".geojson";
|
|
||||||
downloadFile(JSON.stringify(json), name, "application/json");
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveGeoJSON_Markers() {
|
|
||||||
const features = pack.markers.map(marker => {
|
|
||||||
const {i, type, icon, x, y, size, fill, stroke} = marker;
|
|
||||||
const coordinates = getQGIScoordinates(x, y);
|
|
||||||
const id = `marker${i}`;
|
|
||||||
const note = notes.find(note => note.id === id);
|
|
||||||
const properties = {id, type, icon, ...note, size, fill, stroke};
|
|
||||||
return {type: "Feature", geometry: {type: "Point", coordinates}, properties};
|
|
||||||
});
|
|
||||||
|
|
||||||
const json = {type: "FeatureCollection", features};
|
|
||||||
|
|
||||||
const fileName = getFileName("Markers") + ".geojson";
|
|
||||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCellCoordinates(vertices) {
|
|
||||||
const p = pack.vertices.p;
|
|
||||||
const coordinates = vertices.map(n => getCoordinates(p[n][0], p[n][1], 2));
|
|
||||||
return [coordinates.concat([coordinates[0]])];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRoutePoints(node) {
|
|
||||||
let points = [];
|
|
||||||
const l = node.getTotalLength();
|
|
||||||
const increment = l / Math.ceil(l / 2);
|
|
||||||
for (let i = 0; i <= l; i += increment) {
|
|
||||||
const p = node.getPointAtLength(i);
|
|
||||||
points.push(getCoordinates(p.x, p.y, 4));
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRiverPoints(node) {
|
|
||||||
let points = [];
|
|
||||||
const l = node.getTotalLength() / 2; // half-length
|
|
||||||
const increment = 0.25; // defines density of points
|
|
||||||
for (let i = l, c = i; i >= 0; i -= increment, c += increment) {
|
|
||||||
const p1 = node.getPointAtLength(i);
|
|
||||||
const p2 = node.getPointAtLength(c);
|
|
||||||
const [x, y] = getCoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2, 4);
|
|
||||||
points.push([x, y]);
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
523
modules/io/auto-update.js
Normal file
523
modules/io/auto-update.js
Normal file
|
|
@ -0,0 +1,523 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// update old .map version to the current one
|
||||||
|
export function resolveVersionConflicts(version) {
|
||||||
|
if (version < 1) {
|
||||||
|
// v1.0 added a new religions layer
|
||||||
|
relig = viewbox.insert("g", "#terrain").attr("id", "relig");
|
||||||
|
Religions.generate();
|
||||||
|
|
||||||
|
// v1.0 added a legend box
|
||||||
|
legend = svg.append("g").attr("id", "legend");
|
||||||
|
legend
|
||||||
|
.attr("font-family", "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");
|
||||||
|
|
||||||
|
// v1.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", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt");
|
||||||
|
provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt");
|
||||||
|
|
||||||
|
// v1.0 added state relations, provinces, forms and full names
|
||||||
|
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6);
|
||||||
|
BurgsAndStates.collectStatistics();
|
||||||
|
BurgsAndStates.generateCampaigns();
|
||||||
|
BurgsAndStates.generateDiplomacy();
|
||||||
|
BurgsAndStates.defineStateForms();
|
||||||
|
drawStates();
|
||||||
|
BurgsAndStates.generateProvinces();
|
||||||
|
drawBorders();
|
||||||
|
if (!layerIsOn("toggleBorders")) $("#borders").fadeOut();
|
||||||
|
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
|
||||||
|
|
||||||
|
// v1.0 added zones layer
|
||||||
|
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
|
||||||
|
zones.attr("opacity", 0.6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
|
||||||
|
addZones();
|
||||||
|
if (!markers.selectAll("*").size()) {
|
||||||
|
Markers.generate();
|
||||||
|
turnButtonOn("toggleMarkers");
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.0 add fogging layer (state focus)
|
||||||
|
fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("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");
|
||||||
|
|
||||||
|
// v1.0 changes states opacity bask to regions level
|
||||||
|
if (statesBody.attr("opacity")) {
|
||||||
|
regions.attr("opacity", statesBody.attr("opacity"));
|
||||||
|
statesBody.attr("opacity", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.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>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// v1.0 added new biome - Wetland
|
||||||
|
biomesData.name.push("Wetland");
|
||||||
|
biomesData.color.push("#0b9131");
|
||||||
|
biomesData.habitability.push(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.1) {
|
||||||
|
// v1.0 initial code had a bug with religion layer id
|
||||||
|
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
|
||||||
|
|
||||||
|
// v1.0 initially has Sympathy status then relaced with Friendly
|
||||||
|
for (const s of pack.states) {
|
||||||
|
if (!s.diplomacy) continue;
|
||||||
|
s.diplomacy = s.diplomacy.map(r => (r === "Sympathy" ? "Friendly" : r));
|
||||||
|
}
|
||||||
|
|
||||||
|
// labels should be toggled via style attribute, so remove display attribute
|
||||||
|
labels.attr("display", null);
|
||||||
|
|
||||||
|
// v1.0 added religions heirarchy tree
|
||||||
|
if (pack.religions[1] && !pack.religions[1].code) {
|
||||||
|
pack.religions
|
||||||
|
.filter(r => r.i)
|
||||||
|
.forEach(r => {
|
||||||
|
r.origin = 0;
|
||||||
|
r.code = r.name.slice(0, 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.getElementById("freshwater")) {
|
||||||
|
lakes.append("g").attr("id", "freshwater");
|
||||||
|
lakes.select("#freshwater").attr("opacity", 0.5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", 0.7).attr("filter", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.getElementById("salt")) {
|
||||||
|
lakes.append("g").attr("id", "salt");
|
||||||
|
lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.1 added new lake and coast groups
|
||||||
|
if (!document.getElementById("sinkhole")) {
|
||||||
|
lakes.append("g").attr("id", "sinkhole");
|
||||||
|
lakes.append("g").attr("id", "frozen");
|
||||||
|
lakes.append("g").attr("id", "lava");
|
||||||
|
lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", 0.7).attr("filter", null);
|
||||||
|
lakes.select("#frozen").attr("opacity", 0.95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null);
|
||||||
|
lakes.select("#lava").attr("opacity", 0.7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)");
|
||||||
|
|
||||||
|
coastline.append("g").attr("id", "sea_island");
|
||||||
|
coastline.append("g").attr("id", "lake_island");
|
||||||
|
coastline.select("#sea_island").attr("opacity", 0.5).attr("stroke", "#1f3846").attr("stroke-width", 0.7).attr("filter", "url(#dropShadow)");
|
||||||
|
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.1 features stores more data
|
||||||
|
defs.select("#land").selectAll("path").remove();
|
||||||
|
defs.select("#water").selectAll("path").remove();
|
||||||
|
coastline.selectAll("path").remove();
|
||||||
|
lakes.selectAll("path").remove();
|
||||||
|
drawCoastline();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.11) {
|
||||||
|
// v1.11 added new attributes
|
||||||
|
terrs.attr("scheme", "bright").attr("terracing", 0).attr("skip", 5).attr("relax", 0).attr("curve", 0);
|
||||||
|
svg.select("#oceanic > *").attr("id", "oceanicPattern");
|
||||||
|
oceanLayers.attr("layers", "-6,-3,-1");
|
||||||
|
gridOverlay.attr("type", "pointyHex").attr("size", 10);
|
||||||
|
|
||||||
|
// v1.11 added cultures heirarchy tree
|
||||||
|
if (pack.cultures[1] && !pack.cultures[1].code) {
|
||||||
|
pack.cultures
|
||||||
|
.filter(c => c.i)
|
||||||
|
.forEach(c => {
|
||||||
|
c.origin = 0;
|
||||||
|
c.code = c.name.slice(0, 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.11 had an issue with fogging being displayed on load
|
||||||
|
unfog();
|
||||||
|
|
||||||
|
// v1.2 added new terrain attributes
|
||||||
|
if (!terrain.attr("set")) terrain.attr("set", "simple");
|
||||||
|
if (!terrain.attr("size")) terrain.attr("size", 1);
|
||||||
|
if (!terrain.attr("density")) terrain.attr("density", 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.21) {
|
||||||
|
// v1.11 replaced "display" attribute by "display" style
|
||||||
|
viewbox.selectAll("g").each(function () {
|
||||||
|
if (this.hasAttribute("display")) {
|
||||||
|
this.removeAttribute("display");
|
||||||
|
this.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// v1.21 added rivers data to pack
|
||||||
|
pack.rivers = []; // rivers data
|
||||||
|
rivers.selectAll("path").each(function () {
|
||||||
|
const i = +this.id.slice(5);
|
||||||
|
const length = this.getTotalLength() / 2;
|
||||||
|
const s = this.getPointAtLength(length),
|
||||||
|
e = this.getPointAtLength(0);
|
||||||
|
const source = findCell(s.x, s.y),
|
||||||
|
mouth = findCell(e.x, e.y);
|
||||||
|
const name = Rivers.getName(mouth);
|
||||||
|
const type = length < 25 ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : "River";
|
||||||
|
pack.rivers.push({i, parent: 0, length, source, mouth, basin: i, name, type});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.22) {
|
||||||
|
// v1.22 changed state neighbors from Set object to array
|
||||||
|
BurgsAndStates.collectStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.3) {
|
||||||
|
// v1.3 added global options object
|
||||||
|
const winds = options.slice(); // previostly wind was saved in settings[19]
|
||||||
|
const year = rand(100, 2000);
|
||||||
|
const era = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era";
|
||||||
|
const eraShort = era[0] + "E";
|
||||||
|
const military = Military.getDefaultOptions();
|
||||||
|
options = {winds, year, era, eraShort, military};
|
||||||
|
|
||||||
|
// v1.3 added campaings data for all states
|
||||||
|
BurgsAndStates.generateCampaigns();
|
||||||
|
|
||||||
|
// v1.3 added militry layer
|
||||||
|
armies = viewbox.insert("g", "#icons").attr("id", "armies");
|
||||||
|
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", 0.3);
|
||||||
|
turnButtonOn("toggleMilitary");
|
||||||
|
Military.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.4) {
|
||||||
|
// v1.35 added dry lakes
|
||||||
|
if (!lakes.select("#dry").size()) {
|
||||||
|
lakes.append("g").attr("id", "dry");
|
||||||
|
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.4 added ice layer
|
||||||
|
ice = viewbox.insert("g", "#coastline").attr("id", "ice").style("display", "none");
|
||||||
|
ice.attr("opacity", null).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)");
|
||||||
|
drawIce();
|
||||||
|
|
||||||
|
// v1.4 added icon and power attributes for units
|
||||||
|
for (const unit of options.military) {
|
||||||
|
if (!unit.icon) unit.icon = getUnitIcon(unit.type);
|
||||||
|
if (!unit.power) unit.power = unit.crew;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUnitIcon(type) {
|
||||||
|
if (type === "naval") return "🌊";
|
||||||
|
if (type === "ranged") return "🏹";
|
||||||
|
if (type === "mounted") return "🐴";
|
||||||
|
if (type === "machinery") return "💣";
|
||||||
|
if (type === "armored") return "🐢";
|
||||||
|
if (type === "aviation") return "🦅";
|
||||||
|
if (type === "magical") return "🔮";
|
||||||
|
else return "⚔️";
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.4 added state reference for regiments
|
||||||
|
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => (r.state = s.i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.5) {
|
||||||
|
// not need to store default styles from v 1.5
|
||||||
|
localStorage.removeItem("styleClean");
|
||||||
|
localStorage.removeItem("styleGloom");
|
||||||
|
localStorage.removeItem("styleAncient");
|
||||||
|
localStorage.removeItem("styleMonochrome");
|
||||||
|
|
||||||
|
// v1.5 cultures has shield attribute
|
||||||
|
pack.cultures.forEach(culture => {
|
||||||
|
if (culture.removed) return;
|
||||||
|
culture.shield = Cultures.getRandomShield();
|
||||||
|
});
|
||||||
|
|
||||||
|
// v1.5 added burg type value
|
||||||
|
pack.burgs.forEach(burg => {
|
||||||
|
if (!burg.i || burg.removed) return;
|
||||||
|
burg.type = BurgsAndStates.getType(burg.cell, burg.port);
|
||||||
|
});
|
||||||
|
|
||||||
|
// v1.5 added emblems
|
||||||
|
defs.append("g").attr("id", "defs-emblems");
|
||||||
|
emblems = viewbox.insert("g", "#population").attr("id", "emblems").style("display", "none");
|
||||||
|
emblems.append("g").attr("id", "burgEmblems");
|
||||||
|
emblems.append("g").attr("id", "provinceEmblems");
|
||||||
|
emblems.append("g").attr("id", "stateEmblems");
|
||||||
|
regenerateEmblems();
|
||||||
|
toggleEmblems();
|
||||||
|
|
||||||
|
// v1.5 changed releif icons data
|
||||||
|
terrain.selectAll("use").each(function () {
|
||||||
|
const type = this.getAttribute("data-type") || this.getAttribute("xlink:href");
|
||||||
|
this.removeAttribute("xlink:href");
|
||||||
|
this.removeAttribute("data-type");
|
||||||
|
this.removeAttribute("data-size");
|
||||||
|
this.setAttribute("href", type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.6) {
|
||||||
|
// v1.6 changed rivers data
|
||||||
|
for (const river of pack.rivers) {
|
||||||
|
const el = document.getElementById("river" + river.i);
|
||||||
|
if (el) {
|
||||||
|
river.widthFactor = +el.getAttribute("data-width");
|
||||||
|
el.removeAttribute("data-width");
|
||||||
|
el.removeAttribute("data-increment");
|
||||||
|
river.discharge = pack.cells.fl[river.mouth] || 1;
|
||||||
|
river.width = rn(river.length / 100, 2);
|
||||||
|
river.sourceWidth = 0.1;
|
||||||
|
} else {
|
||||||
|
Rivers.remove(river.i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1.6 changed lakes data
|
||||||
|
for (const f of pack.features) {
|
||||||
|
if (f.type !== "lake") continue;
|
||||||
|
if (f.evaporation) continue;
|
||||||
|
|
||||||
|
f.flux = f.flux || f.cells * 3;
|
||||||
|
f.temp = grid.cells.temp[pack.cells.g[f.firstCell]];
|
||||||
|
f.height = f.height || d3.min(pack.cells.c[f.firstCell].map(c => pack.cells.h[c]).filter(h => h >= 20));
|
||||||
|
const height = (f.height - 18) ** heightExponentInput.value;
|
||||||
|
const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp);
|
||||||
|
f.evaporation = rn(evaporation * f.cells);
|
||||||
|
f.name = f.name || Lakes.getName(f);
|
||||||
|
delete f.river;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.61) {
|
||||||
|
// v1.61 changed rulers data
|
||||||
|
ruler.style("display", null);
|
||||||
|
rulers = new Rulers();
|
||||||
|
|
||||||
|
ruler.selectAll(".ruler > .white").each(function () {
|
||||||
|
const x1 = +this.getAttribute("x1");
|
||||||
|
const y1 = +this.getAttribute("y1");
|
||||||
|
const x2 = +this.getAttribute("x2");
|
||||||
|
const y2 = +this.getAttribute("y2");
|
||||||
|
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) return;
|
||||||
|
const points = [
|
||||||
|
[x1, y1],
|
||||||
|
[x2, y2]
|
||||||
|
];
|
||||||
|
rulers.create(Ruler, points);
|
||||||
|
});
|
||||||
|
|
||||||
|
ruler.selectAll("g.opisometer").each(function () {
|
||||||
|
const pointsString = this.dataset.points;
|
||||||
|
if (!pointsString) return;
|
||||||
|
const points = JSON.parse(pointsString);
|
||||||
|
rulers.create(Opisometer, points);
|
||||||
|
});
|
||||||
|
|
||||||
|
ruler.selectAll("path.planimeter").each(function () {
|
||||||
|
const length = this.getTotalLength();
|
||||||
|
if (length < 30) return;
|
||||||
|
|
||||||
|
const step = length > 1000 ? 40 : length > 400 ? 20 : 10;
|
||||||
|
const increment = length / Math.ceil(length / step);
|
||||||
|
const points = [];
|
||||||
|
for (let i = 0; i <= length; i += increment) {
|
||||||
|
const point = this.getPointAtLength(i);
|
||||||
|
points.push([point.x | 0, point.y | 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rulers.create(Planimeter, points);
|
||||||
|
});
|
||||||
|
|
||||||
|
ruler.selectAll("*").remove();
|
||||||
|
|
||||||
|
if (rulers.data.length) {
|
||||||
|
turnButtonOn("toggleRulers");
|
||||||
|
rulers.draw();
|
||||||
|
} else turnButtonOff("toggleRulers");
|
||||||
|
|
||||||
|
// 1.61 changed oceanicPattern from rect to image
|
||||||
|
const pattern = document.getElementById("oceanic");
|
||||||
|
const filter = pattern.firstElementChild.getAttribute("filter");
|
||||||
|
const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : "";
|
||||||
|
pattern.innerHTML = `<image id="oceanicPattern" href=${href} width="100" height="100" opacity="0.2"></image>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.62) {
|
||||||
|
// v1.62 changed grid data
|
||||||
|
gridOverlay.attr("size", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.63) {
|
||||||
|
// v1.63 changed ocean pattern opacity element
|
||||||
|
const oceanPattern = document.getElementById("oceanPattern");
|
||||||
|
if (oceanPattern) oceanPattern.removeAttribute("opacity");
|
||||||
|
const oceanicPattern = document.getElementById("oceanicPattern");
|
||||||
|
if (!oceanicPattern.getAttribute("opacity")) oceanicPattern.setAttribute("opacity", 0.2);
|
||||||
|
|
||||||
|
// v 1.63 moved label text-shadow from css to editable inline style
|
||||||
|
burgLabels.select("#cities").style("text-shadow", "white 0 0 4px");
|
||||||
|
burgLabels.select("#towns").style("text-shadow", "white 0 0 4px");
|
||||||
|
labels.select("#states").style("text-shadow", "white 0 0 4px");
|
||||||
|
labels.select("#addedLabels").style("text-shadow", "white 0 0 4px");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.64) {
|
||||||
|
// v1.64 change states style
|
||||||
|
const opacity = regions.attr("opacity");
|
||||||
|
const filter = regions.attr("filter");
|
||||||
|
statesBody.attr("opacity", opacity).attr("filter", filter);
|
||||||
|
statesHalo.attr("opacity", opacity).attr("filter", "blur(5px)");
|
||||||
|
regions.attr("opacity", null).attr("filter", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.65) {
|
||||||
|
// v1.65 changed rivers data
|
||||||
|
d3.select("#rivers").attr("style", null); // remove style to unhide layer
|
||||||
|
const {cells, rivers} = pack;
|
||||||
|
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
|
||||||
|
|
||||||
|
for (const river of rivers) {
|
||||||
|
const node = document.getElementById("river" + river.i);
|
||||||
|
if (node && !river.cells) {
|
||||||
|
const riverCells = [];
|
||||||
|
const riverPoints = [];
|
||||||
|
|
||||||
|
const length = node.getTotalLength() / 2;
|
||||||
|
if (!length) continue;
|
||||||
|
const segments = Math.ceil(length / 6);
|
||||||
|
const increment = length / segments;
|
||||||
|
|
||||||
|
for (let i = 0; i <= segments; i++) {
|
||||||
|
const shift = increment * i;
|
||||||
|
const {x: x1, y: y1} = node.getPointAtLength(length + shift);
|
||||||
|
const {x: x2, y: y2} = node.getPointAtLength(length - shift);
|
||||||
|
const x = rn((x1 + x2) / 2, 1);
|
||||||
|
const y = rn((y1 + y2) / 2, 1);
|
||||||
|
|
||||||
|
const cell = findCell(x, y);
|
||||||
|
riverPoints.push([x, y]);
|
||||||
|
riverCells.push(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
river.cells = riverCells;
|
||||||
|
river.points = riverPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
river.widthFactor = defaultWidthFactor;
|
||||||
|
|
||||||
|
cells.i.forEach(i => {
|
||||||
|
const riverInWater = cells.r[i] && cells.h[i] < 20;
|
||||||
|
if (riverInWater) cells.r[i] = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.652) {
|
||||||
|
// remove style to unhide layers
|
||||||
|
rivers.attr("style", null);
|
||||||
|
borders.attr("style", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.7) {
|
||||||
|
// v1.7 changed markers data
|
||||||
|
const defs = document.getElementById("defs-markers");
|
||||||
|
const markersGroup = document.getElementById("markers");
|
||||||
|
|
||||||
|
if (defs && markersGroup) {
|
||||||
|
const markerElements = markersGroup.querySelectorAll("use");
|
||||||
|
const rescale = +markersGroup.getAttribute("rescale");
|
||||||
|
|
||||||
|
pack.markers = Array.from(markerElements).map((el, i) => {
|
||||||
|
const id = el.getAttribute("id");
|
||||||
|
const note = notes.find(note => note.id === id);
|
||||||
|
if (note) note.id = `marker${i}`;
|
||||||
|
|
||||||
|
let x = +el.dataset.x;
|
||||||
|
let y = +el.dataset.y;
|
||||||
|
|
||||||
|
const transform = el.getAttribute("transform");
|
||||||
|
if (transform) {
|
||||||
|
const [dx, dy] = parseTransform(transform);
|
||||||
|
if (dx) x += +dx;
|
||||||
|
if (dy) y += +dy;
|
||||||
|
}
|
||||||
|
const cell = findCell(x, y);
|
||||||
|
const size = rn(rescale ? el.dataset.size * 30 : el.getAttribute("width"), 1);
|
||||||
|
|
||||||
|
const href = el.href.baseVal;
|
||||||
|
const type = href.replace("#marker_", "");
|
||||||
|
const symbol = defs?.querySelector(`symbol${href}`);
|
||||||
|
const text = symbol?.querySelector("text");
|
||||||
|
const circle = symbol?.querySelector("circle");
|
||||||
|
|
||||||
|
const icon = text?.innerHTML;
|
||||||
|
const px = text && Number(text.getAttribute("font-size")?.replace("px", ""));
|
||||||
|
const dx = text && Number(text.getAttribute("x")?.replace("%", ""));
|
||||||
|
const dy = text && Number(text.getAttribute("y")?.replace("%", ""));
|
||||||
|
const fill = circle && circle.getAttribute("fill");
|
||||||
|
const stroke = circle && circle.getAttribute("stroke");
|
||||||
|
|
||||||
|
const marker = {i, icon, type, x, y, size, cell};
|
||||||
|
if (size && size !== 30) marker.size = size;
|
||||||
|
if (!isNaN(px) && px !== 12) marker.px = px;
|
||||||
|
if (!isNaN(dx) && dx !== 50) marker.dx = dx;
|
||||||
|
if (!isNaN(dy) && dy !== 50) marker.dy = dy;
|
||||||
|
if (fill && fill !== "#ffffff") marker.fill = fill;
|
||||||
|
if (stroke && stroke !== "#000000") marker.stroke = stroke;
|
||||||
|
if (circle?.getAttribute("opacity") === "0") marker.pin = "no";
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
});
|
||||||
|
|
||||||
|
markersGroup.style.display = null;
|
||||||
|
defs?.remove();
|
||||||
|
markerElements.forEach(el => el.remove());
|
||||||
|
if (layerIsOn("markers")) drawMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.72) {
|
||||||
|
// v1.72 renamed custom style presets
|
||||||
|
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith("style"));
|
||||||
|
storedStyles.forEach(styleName => {
|
||||||
|
const style = localStorage.getItem(styleName);
|
||||||
|
const newStyleName = styleName.replace(/^style/, customPresetPrefix);
|
||||||
|
localStorage.setItem(newStyleName, style);
|
||||||
|
localStorage.removeItem(styleName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 1.73) {
|
||||||
|
// v1.73 moved the hatching patterns out of the user's SVG
|
||||||
|
document.getElementById("hatching")?.remove();
|
||||||
|
|
||||||
|
// v1.73 added zone type to UI, ensure type is populated
|
||||||
|
const zones = Array.from(document.querySelectorAll("#zones > g"));
|
||||||
|
zones.forEach(zone => {
|
||||||
|
if (!zone.dataset.type) zone.dataset.type = "Unknown";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
235
modules/io/export-json.js
Normal file
235
modules/io/export-json.js
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
export function exportToJson(type) {
|
||||||
|
if (customization)
|
||||||
|
return tip("Data cannot be exported when edit mode is active, please exit the mode and retry", false, "error");
|
||||||
|
closeDialogs("#alert");
|
||||||
|
|
||||||
|
const typeMap = {
|
||||||
|
Full: getFullDataJson,
|
||||||
|
Minimal: getMinimalDataJson,
|
||||||
|
PackCells: getPackCellsDataJson,
|
||||||
|
GridCells: getGridCellsDataJson
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapData = typeMap[type]();
|
||||||
|
const blob = new Blob([mapData], {type: "application/json"});
|
||||||
|
const URL = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = getFileName(type) + ".json";
|
||||||
|
link.href = URL;
|
||||||
|
link.click();
|
||||||
|
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||||
|
window.URL.revokeObjectURL(URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFullDataJson() {
|
||||||
|
TIME && console.time("getFullDataJson");
|
||||||
|
|
||||||
|
const info = getMapInfo();
|
||||||
|
const settings = getSettings();
|
||||||
|
const cells = getPackCellsData();
|
||||||
|
const vertices = getPackVerticesData();
|
||||||
|
const exportData = {info, settings, coords: mapCoordinates, cells, vertices, biomes: biomesData, notes, nameBases};
|
||||||
|
|
||||||
|
TIME && console.timeEnd("getFullDataJson");
|
||||||
|
return JSON.stringify(exportData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMinimalDataJson() {
|
||||||
|
TIME && console.time("getMinimalDataJson");
|
||||||
|
|
||||||
|
const info = getMapInfo();
|
||||||
|
const settings = getSettings();
|
||||||
|
const packData = {
|
||||||
|
features: pack.features,
|
||||||
|
cultures: pack.cultures,
|
||||||
|
burgs: pack.burgs,
|
||||||
|
states: pack.states,
|
||||||
|
provinces: pack.provinces,
|
||||||
|
religions: pack.religions,
|
||||||
|
rivers: pack.rivers,
|
||||||
|
markers: pack.markers
|
||||||
|
};
|
||||||
|
const exportData = {info, settings, coords: mapCoordinates, pack: packData, biomes: biomesData, notes, nameBases};
|
||||||
|
|
||||||
|
TIME && console.timeEnd("getMinimalDataJson");
|
||||||
|
return JSON.stringify(exportData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackCellsDataJson() {
|
||||||
|
TIME && console.time("getCellsDataJson");
|
||||||
|
|
||||||
|
const info = getMapInfo();
|
||||||
|
const cells = getPackCellsData();
|
||||||
|
const exportData = {info, cells};
|
||||||
|
|
||||||
|
TIME && console.timeEnd("getCellsDataJson");
|
||||||
|
return JSON.stringify(exportData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGridCellsDataJson() {
|
||||||
|
TIME && console.time("getGridCellsDataJson");
|
||||||
|
|
||||||
|
const info = getMapInfo();
|
||||||
|
const gridCells = getGridCellsData();
|
||||||
|
const exportData = {info, gridCells};
|
||||||
|
|
||||||
|
TIME && console.log("getGridCellsDataJson");
|
||||||
|
return JSON.stringify(exportData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMapInfo() {
|
||||||
|
const info = {
|
||||||
|
version,
|
||||||
|
description: "Azgaar's Fantasy Map Generator output: azgaar.github.io/Fantasy-map-generator",
|
||||||
|
exportedAt: new Date().toISOString(),
|
||||||
|
mapName: mapName.value,
|
||||||
|
seed,
|
||||||
|
mapId
|
||||||
|
};
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSettings() {
|
||||||
|
const settings = {
|
||||||
|
distanceUnit: distanceUnitInput.value,
|
||||||
|
distanceScale: distanceScaleInput.value,
|
||||||
|
areaUnit: areaUnit.value,
|
||||||
|
heightUnit: heightUnit.value,
|
||||||
|
heightExponent: heightExponentInput.value,
|
||||||
|
temperatureScale: temperatureScale.value,
|
||||||
|
barSize: barSizeInput.value,
|
||||||
|
barLabel: barLabel.value,
|
||||||
|
barBackOpacity: barBackOpacity.value,
|
||||||
|
barBackColor: barBackColor.value,
|
||||||
|
barPosX: barPosX.value,
|
||||||
|
barPosY: barPosY.value,
|
||||||
|
populationRate: populationRate,
|
||||||
|
urbanization: urbanization,
|
||||||
|
mapSize: mapSizeOutput.value,
|
||||||
|
latitudeO: latitudeOutput.value,
|
||||||
|
temperatureEquator: temperatureEquatorOutput.value,
|
||||||
|
temperaturePole: temperaturePoleOutput.value,
|
||||||
|
prec: precOutput.value,
|
||||||
|
options: options,
|
||||||
|
mapName: mapName.value,
|
||||||
|
hideLabels: hideLabels.checked,
|
||||||
|
stylePreset: stylePreset.value,
|
||||||
|
rescaleLabels: rescaleLabels.checked,
|
||||||
|
urbanDensity: urbanDensity
|
||||||
|
};
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackCellsData() {
|
||||||
|
const cellConverted = {
|
||||||
|
i: Array.from(pack.cells.i),
|
||||||
|
v: pack.cells.v,
|
||||||
|
c: pack.cells.c,
|
||||||
|
p: pack.cells.p,
|
||||||
|
g: Array.from(pack.cells.g),
|
||||||
|
h: Array.from(pack.cells.h),
|
||||||
|
area: Array.from(pack.cells.area),
|
||||||
|
f: Array.from(pack.cells.f),
|
||||||
|
t: Array.from(pack.cells.t),
|
||||||
|
haven: Array.from(pack.cells.haven),
|
||||||
|
harbor: Array.from(pack.cells.harbor),
|
||||||
|
fl: Array.from(pack.cells.fl),
|
||||||
|
r: Array.from(pack.cells.r),
|
||||||
|
conf: Array.from(pack.cells.conf),
|
||||||
|
biome: Array.from(pack.cells.biome),
|
||||||
|
s: Array.from(pack.cells.s),
|
||||||
|
pop: Array.from(pack.cells.pop),
|
||||||
|
culture: Array.from(pack.cells.culture),
|
||||||
|
burg: Array.from(pack.cells.burg),
|
||||||
|
road: Array.from(pack.cells.road),
|
||||||
|
crossroad: Array.from(pack.cells.crossroad),
|
||||||
|
state: Array.from(pack.cells.state),
|
||||||
|
religion: Array.from(pack.cells.religion),
|
||||||
|
province: Array.from(pack.cells.province)
|
||||||
|
};
|
||||||
|
const cellObjArr = [];
|
||||||
|
{
|
||||||
|
cellConverted.i.forEach(value => {
|
||||||
|
const cellobj = {
|
||||||
|
i: value,
|
||||||
|
v: cellConverted.v[value],
|
||||||
|
c: cellConverted.c[value],
|
||||||
|
p: cellConverted.p[value],
|
||||||
|
g: cellConverted.g[value],
|
||||||
|
h: cellConverted.h[value],
|
||||||
|
area: cellConverted.area[value],
|
||||||
|
f: cellConverted.f[value],
|
||||||
|
t: cellConverted.t[value],
|
||||||
|
haven: cellConverted.haven[value],
|
||||||
|
harbor: cellConverted.harbor[value],
|
||||||
|
fl: cellConverted.fl[value],
|
||||||
|
r: cellConverted.r[value],
|
||||||
|
conf: cellConverted.conf[value],
|
||||||
|
biome: cellConverted.biome[value],
|
||||||
|
s: cellConverted.s[value],
|
||||||
|
pop: cellConverted.pop[value],
|
||||||
|
culture: cellConverted.culture[value],
|
||||||
|
burg: cellConverted.burg[value],
|
||||||
|
road: cellConverted.road[value],
|
||||||
|
crossroad: cellConverted.crossroad[value],
|
||||||
|
state: cellConverted.state[value],
|
||||||
|
religion: cellConverted.religion[value],
|
||||||
|
province: cellConverted.province[value]
|
||||||
|
};
|
||||||
|
cellObjArr.push(cellobj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const cellsData = {
|
||||||
|
cells: cellObjArr,
|
||||||
|
features: pack.features,
|
||||||
|
cultures: pack.cultures,
|
||||||
|
burgs: pack.burgs,
|
||||||
|
states: pack.states,
|
||||||
|
provinces: pack.provinces,
|
||||||
|
religions: pack.religions,
|
||||||
|
rivers: pack.rivers,
|
||||||
|
markers: pack.markers
|
||||||
|
};
|
||||||
|
|
||||||
|
return cellsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGridCellsData() {
|
||||||
|
const gridData = {
|
||||||
|
cellsDesired: grid.cellsDesired,
|
||||||
|
spacing: grid.spacing,
|
||||||
|
cellsY: grid.cellsY,
|
||||||
|
cellsX: grid.cellsX,
|
||||||
|
points: grid.points,
|
||||||
|
boundary: grid.boundary
|
||||||
|
};
|
||||||
|
return gridData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackVerticesData() {
|
||||||
|
const {vertices} = pack;
|
||||||
|
const verticesNumber = vertices.p.length;
|
||||||
|
const verticesArray = new Array(verticesNumber);
|
||||||
|
for (let i = 0; i < verticesNumber; i++) {
|
||||||
|
verticesArray[i] = {
|
||||||
|
p: vertices.p[i],
|
||||||
|
v: vertices.v[i],
|
||||||
|
c: vertices.c[i]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return verticesArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGridCellsDataJson() {
|
||||||
|
TIME && console.time("getGridCellsDataJson");
|
||||||
|
|
||||||
|
const info = getMapInfo();
|
||||||
|
const gridCells = getGridCellsData()
|
||||||
|
const exportData = {info,gridCells};
|
||||||
|
|
||||||
|
TIME && console.log("getGridCellsDataJson");
|
||||||
|
return JSON.stringify(exportData);
|
||||||
|
}
|
||||||
|
|
@ -435,6 +435,7 @@ async function parseLoadedData(data) {
|
||||||
resolveVersionConflicts(versionNumber);
|
resolveVersionConflicts(versionNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void (function checkDataIntegrity() {
|
void (function checkDataIntegrity() {
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
|
|
||||||
|
|
|
||||||
1052
modules/load.js
1052
modules/load.js
File diff suppressed because it is too large
Load diff
194
modules/save.js
194
modules/save.js
|
|
@ -1,194 +0,0 @@
|
||||||
"use strict";
|
|
||||||
// functions to save project as .map file
|
|
||||||
|
|
||||||
// prepare map data for saving
|
|
||||||
function getMapData() {
|
|
||||||
TIME && console.time("createMapData");
|
|
||||||
|
|
||||||
const date = new Date();
|
|
||||||
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
|
||||||
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
|
||||||
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|");
|
|
||||||
const settings = [
|
|
||||||
distanceUnitInput.value,
|
|
||||||
distanceScaleInput.value,
|
|
||||||
areaUnit.value,
|
|
||||||
heightUnit.value,
|
|
||||||
heightExponentInput.value,
|
|
||||||
temperatureScale.value,
|
|
||||||
barSizeInput.value,
|
|
||||||
barLabel.value,
|
|
||||||
barBackOpacity.value,
|
|
||||||
barBackColor.value,
|
|
||||||
barPosX.value,
|
|
||||||
barPosY.value,
|
|
||||||
populationRate,
|
|
||||||
urbanization,
|
|
||||||
mapSizeOutput.value,
|
|
||||||
latitudeOutput.value,
|
|
||||||
temperatureEquatorOutput.value,
|
|
||||||
temperaturePoleOutput.value,
|
|
||||||
precOutput.value,
|
|
||||||
JSON.stringify(options),
|
|
||||||
mapName.value,
|
|
||||||
+hideLabels.checked,
|
|
||||||
stylePreset.value,
|
|
||||||
+rescaleLabels.checked
|
|
||||||
].join("|");
|
|
||||||
const coords = JSON.stringify(mapCoordinates);
|
|
||||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
|
||||||
const notesData = JSON.stringify(notes);
|
|
||||||
const rulersString = rulers.toString();
|
|
||||||
const fonts = JSON.stringify(getUsedFonts(svg.node()));
|
|
||||||
|
|
||||||
// save svg
|
|
||||||
const cloneEl = document.getElementById("map").cloneNode(true);
|
|
||||||
|
|
||||||
// reset transform values to default
|
|
||||||
cloneEl.setAttribute("width", graphWidth);
|
|
||||||
cloneEl.setAttribute("height", graphHeight);
|
|
||||||
cloneEl.querySelector("#viewbox").removeAttribute("transform");
|
|
||||||
|
|
||||||
cloneEl.querySelector("#ruler").innerHTML = ""; // always remove rulers
|
|
||||||
|
|
||||||
const serializedSVG = new XMLSerializer().serializeToString(cloneEl);
|
|
||||||
|
|
||||||
const {spacing, cellsX, cellsY, boundary, points, features} = grid;
|
|
||||||
const gridGeneral = JSON.stringify({spacing, cellsX, cellsY, boundary, points, features});
|
|
||||||
const packFeatures = JSON.stringify(pack.features);
|
|
||||||
const cultures = JSON.stringify(pack.cultures);
|
|
||||||
const states = JSON.stringify(pack.states);
|
|
||||||
const burgs = JSON.stringify(pack.burgs);
|
|
||||||
const religions = JSON.stringify(pack.religions);
|
|
||||||
const provinces = JSON.stringify(pack.provinces);
|
|
||||||
const rivers = JSON.stringify(pack.rivers);
|
|
||||||
const markers = JSON.stringify(pack.markers);
|
|
||||||
|
|
||||||
// store name array only if not the same as default
|
|
||||||
const defaultNB = Names.getNameBases();
|
|
||||||
const namesData = nameBases
|
|
||||||
.map((b, i) => {
|
|
||||||
const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b;
|
|
||||||
return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
|
|
||||||
})
|
|
||||||
.join("/");
|
|
||||||
|
|
||||||
// round population to save space
|
|
||||||
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
|
|
||||||
|
|
||||||
// data format as below
|
|
||||||
const mapData = [
|
|
||||||
params,
|
|
||||||
settings,
|
|
||||||
coords,
|
|
||||||
biomes,
|
|
||||||
notesData,
|
|
||||||
serializedSVG,
|
|
||||||
gridGeneral,
|
|
||||||
grid.cells.h,
|
|
||||||
grid.cells.prec,
|
|
||||||
grid.cells.f,
|
|
||||||
grid.cells.t,
|
|
||||||
grid.cells.temp,
|
|
||||||
packFeatures,
|
|
||||||
cultures,
|
|
||||||
states,
|
|
||||||
burgs,
|
|
||||||
pack.cells.biome,
|
|
||||||
pack.cells.burg,
|
|
||||||
pack.cells.conf,
|
|
||||||
pack.cells.culture,
|
|
||||||
pack.cells.fl,
|
|
||||||
pop,
|
|
||||||
pack.cells.r,
|
|
||||||
pack.cells.road,
|
|
||||||
pack.cells.s,
|
|
||||||
pack.cells.state,
|
|
||||||
pack.cells.religion,
|
|
||||||
pack.cells.province,
|
|
||||||
pack.cells.crossroad,
|
|
||||||
religions,
|
|
||||||
provinces,
|
|
||||||
namesData,
|
|
||||||
rivers,
|
|
||||||
rulersString,
|
|
||||||
fonts,
|
|
||||||
markers
|
|
||||||
].join("\r\n");
|
|
||||||
TIME && console.timeEnd("createMapData");
|
|
||||||
return mapData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download .map file
|
|
||||||
function dowloadMap() {
|
|
||||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
|
||||||
closeDialogs("#alert");
|
|
||||||
|
|
||||||
const mapData = getMapData();
|
|
||||||
const blob = new Blob([mapData], {type: "text/plain"});
|
|
||||||
const URL = window.URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.download = getFileName() + '.map';
|
|
||||||
link.href = URL;
|
|
||||||
link.click();
|
|
||||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, 'success', 7000);
|
|
||||||
window.URL.revokeObjectURL(URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveToDropbox() {
|
|
||||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
|
||||||
closeDialogs("#alert");
|
|
||||||
const mapData = getMapData();
|
|
||||||
const filename = getFileName() + ".map";
|
|
||||||
try {
|
|
||||||
await Cloud.providers.dropbox.save(filename, mapData);
|
|
||||||
tip("Map is saved to your Dropbox", true, "success", 8000);
|
|
||||||
} catch (msg) {
|
|
||||||
ERROR && console.error(msg);
|
|
||||||
tip("Cannot save .map to your Dropbox", true, "error", 8000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function quickSave() {
|
|
||||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
|
||||||
|
|
||||||
const mapData = getMapData();
|
|
||||||
const blob = new Blob([mapData], {type: "text/plain"});
|
|
||||||
if (blob) ldb.set("lastMap", blob); // auto-save map
|
|
||||||
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 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 don't 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"
|
|
||||||
];
|
|
||||||
const interval = 15 * 60 * 1000; // remind every 15 minutes
|
|
||||||
|
|
||||||
saveReminder.reminder = setInterval(() => {
|
|
||||||
if (customization) return;
|
|
||||||
tip(ra(message), true, "warn", 2500);
|
|
||||||
}, interval);
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue