Merge remote-tracking branch 'origin/master' into localization-dev

This commit is contained in:
mosuzi 2024-03-26 17:48:55 +08:00
commit 60bbf2e487
292 changed files with 11868 additions and 7469 deletions

View file

@ -60,7 +60,7 @@ window.Cloud = (function () {
async save(fileName, contents) {
const resp = await this.call("filesUpload", {path: "/" + fileName, contents});
DEBUG && console.log("Dropbox response:", resp);
DEBUG && console.info("Dropbox response:", resp);
return true;
},
@ -104,7 +104,7 @@ window.Cloud = (function () {
// Callback function for auth window
async setDropBoxToken(token) {
DEBUG && console.log("Access token:", token);
DEBUG && console.info("Access token:", token);
setToken(this.name, token);
await this.connect(token);
this.authWindow.close();
@ -118,9 +118,9 @@ window.Cloud = (function () {
},
async getLink(path) {
// return existitng shared link
// return existing shared link
const sharedLinks = await this.call("sharingListSharedLinks", {path});
if (sharedLinks.result.links.length) return resp.result.links[0].url;
if (sharedLinks.result.links.length) return sharedLinks.result.links[0].url;
// create new shared link
const settings = {
@ -131,7 +131,7 @@ window.Cloud = (function () {
allow_download: true
};
const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings});
DEBUG && console.log("Dropbox link object:", resp.result);
DEBUG && console.info("Dropbox link object:", resp.result);
return resp.result.url;
}
};

View file

@ -1,27 +1,21 @@
"use strict";
// Functions to export map to image or data files
// download map as SVG
async function saveSVG() {
TIME && console.time("saveSVG");
async function exportToSvg() {
TIME && console.time("exportToSvg");
const url = await getMapURL("svg", {fullMap: true});
const link = document.createElement("a");
link.download = getFileName() + ".svg";
link.href = url;
link.click();
tip(
`${link.download} is saved. Open "Downloads" screen (ctrl + J) to check. You can set image scale in options`,
true,
"success",
5000
);
TIME && console.timeEnd("saveSVG");
const message = `${link.download} is saved. Open 'Downloads' screen (ctrl + J) to check`;
tip(message, true, "success", 5000);
TIME && console.timeEnd("exportToSvg");
}
// download map as PNG
async function savePNG() {
TIME && console.time("savePNG");
async function exportToPng() {
TIME && console.time("exportToPng");
const url = await getMapURL("png");
const link = document.createElement("a");
@ -41,22 +35,18 @@ async function savePNG() {
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
);
const message = `${link.download} is saved. Open 'Downloads' screen (ctrl + J) to check. You can set image scale in options`;
tip(message, true, "success", 5000);
}, 1000);
});
};
TIME && console.timeEnd("savePNG");
TIME && console.timeEnd("exportToPng");
}
// download map as JPEG
async function saveJPEG() {
TIME && console.time("saveJPEG");
async function exportToJpeg() {
TIME && console.time("exportToJpeg");
const url = await getMapURL("png");
const canvas = document.createElement("canvas");
@ -77,103 +67,122 @@ async function saveJPEG() {
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
};
TIME && console.timeEnd("saveJPEG");
TIME && console.timeEnd("exportToJpeg");
}
// download map as png tiles
async function saveTiles() {
return new Promise(async (resolve, reject) => {
// download schema
const urlSchema = await getMapURL("tiles", {debug: true, fullMap: true});
await import("../../libs/jszip.min.js");
const zip = new window.JSZip();
async function exportToPngTiles() {
const status = byId("tileStatus");
status.innerHTML = "Preparing files...";
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = graphWidth;
canvas.height = graphHeight;
const urlSchema = await getMapURL("tiles", {debug: true, fullMap: true});
await import("../../libs/jszip.min.js");
const zip = new window.JSZip();
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));
};
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = graphWidth;
canvas.height = graphHeight;
// download tiles
const url = await getMapURL("tiles", {fullMap: true});
const tilesX = +document.getElementById("tileColsInput").value;
const tilesY = +document.getElementById("tileRowsInput").value;
const scale = +document.getElementById("tileScaleInput").value;
const imgSchema = new Image();
imgSchema.src = urlSchema;
await loadImage(imgSchema);
const tileW = (graphWidth / tilesX) | 0;
const tileH = (graphHeight / tilesY) | 0;
const tolesTotal = tilesX * tilesY;
status.innerHTML = "Drawing schema...";
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
const blob = await canvasToBlob(canvas, "image/png");
ctx.clearRect(0, 0, canvas.width, canvas.height);
zip.file("schema.png", blob);
const width = graphWidth * scale;
const height = width * (tileH / tileW);
canvas.width = width;
canvas.height = height;
// download tiles
const url = await getMapURL("tiles", {fullMap: true});
const tilesX = +byId("tileColsInput").value;
const tilesY = +byId("tileRowsInput").value;
const scale = +byId("tileScaleInput").value;
const tolesTotal = tilesX * tilesY;
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();
});
}
}
};
const tileW = (graphWidth / tilesX) | 0;
const tileH = (graphHeight / tilesY) | 0;
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();
const width = graphWidth * scale;
const height = width * (tileH / tileW);
canvas.width = width;
canvas.height = height;
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
resolve(true);
});
const img = new Image();
img.src = url;
await loadImage(img);
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (let y = 0, row = 0, id = 1; y + tileH <= graphHeight; y += tileH, row++) {
const rowName = alphabet[row % alphabet.length];
for (let x = 0, cell = 1; x + tileW <= graphWidth; x += tileW, cell++, id++) {
status.innerHTML = `Drawing tile ${rowName}${cell} (${id} of ${tolesTotal})...`;
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
const blob = await canvasToBlob(canvas, "image/png");
ctx.clearRect(0, 0, canvas.width, canvas.height);
zip.file(`${rowName}${cell}.png`, blob);
}
}
status.innerHTML = "Zipping files...";
zip.generateAsync({type: "blob"}).then(blob => {
status.innerHTML = "Downloading the archive...";
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = getFileName() + ".zip";
link.click();
link.remove();
status.innerHTML = 'Done. Check .zip file in "Downloads" (crtl + J)';
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
});
// promisified img.onload
function loadImage(img) {
return new Promise((resolve, reject) => {
img.onload = () => resolve();
img.onerror = err => reject(err);
});
}
// promisified canvas.toBlob
function canvasToBlob(canvas, mimeType, qualityArgument = 1) {
return new Promise((resolve, reject) => {
canvas.toBlob(
blob => {
if (blob) resolve(blob);
else reject(new Error("Canvas toBlob() error"));
},
mimeType,
qualityArgument
);
});
}
}
// parse map svg to object url
async function getMapURL(type, options = {}) {
async function getMapURL(type, options) {
const {
debug = false,
globe = false,
noLabels = false,
noWater = false,
noScaleBar = false,
noIce = false,
fullMap = false
} = options;
} = options || {};
if (fullMap) drawScaleBar(1);
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
const cloneEl = byId("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 svgDefs = byId("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();
@ -183,14 +192,18 @@ async function getMapURL(type, options = {}) {
clone.select("#oceanBase").attr("opacity", 0);
clone.select("#oceanPattern").attr("opacity", 0);
}
if (noScaleBar) clone.select("#scaleBar")?.remove();
if (noIce) clone.select("#ice")?.remove();
if (fullMap) {
// reset transform to show the whole map
clone.attr("width", graphWidth).attr("height", graphHeight);
clone.select("#viewbox").attr("transform", null);
drawScaleBar(scale);
if (!noScaleBar) {
drawScaleBar(clone.select("#scaleBar"), 1);
fitScaleBar(clone.select("#scaleBar"), graphWidth, graphHeight);
}
}
if (noScaleBar) clone.select("#scaleBar")?.remove();
if (type === "svg") removeUnusedElements(clone);
if (customization && type === "mesh") updateMeshCells(clone);
@ -229,35 +242,35 @@ async function getMapURL(type, options = {}) {
.forEach(el => {
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
if (!href) return;
const emblem = document.getElementById(href.slice(1));
const emblem = byId(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) {
const el = cloneEl.getElementById("oceanicPattern");
const url = el?.getAttribute("href");
if (url) {
{
// replace ocean pattern href to base64
const image = cloneEl.getElementById("oceanicPattern");
const href = image?.getAttribute("href");
if (href) {
await new Promise(resolve => {
getBase64(url, base64 => {
el.setAttribute("href", base64);
getBase64(href, base64 => {
image.setAttribute("href", base64);
resolve();
});
});
}
}
// replace texture href to base64
if (location.hostname) {
const el = cloneEl.getElementById("textureImage");
const url = el?.getAttribute("href");
if (url) {
{
// replace texture href to base64
const image = cloneEl.querySelector("#texture > image");
const href = image?.getAttribute("href");
if (href) {
await new Promise(resolve => {
getBase64(url, base64 => {
el.setAttribute("href", base64);
getBase64(href, base64 => {
image.setAttribute("href", base64);
resolve();
});
});
@ -375,7 +388,7 @@ function removeUnusedElements(clone) {
function updateMeshCells(clone) {
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
const scheme = getColorScheme(terrs.attr("scheme"));
const scheme = getColorScheme(terrs.select("#landHeights").attr("scheme"));
clone.select("#heights").attr("filter", "url(#blur1)");
clone
.select("#heights")
@ -400,12 +413,6 @@ function inlineStyle(clone) {
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;

View file

@ -1,24 +0,0 @@
"use strict";
window.Formats = (function () {
async function csvParser(file, separator = ",") {
const txt = await file.text();
const rows = txt.split("\n");
const headers = rows
.shift()
.split(separator)
.map(x => x.toLowerCase());
const data = rows.filter(a => a.trim() !== "").map(r => r.split(separator));
return {
headers,
data,
iterator: function* (sortf) {
const dataset = sortf ? this.data.sort(sortf) : this.data;
for (const d of dataset) yield Object.fromEntries(d.map((a, i) => [this.headers[i], a]));
}
};
}
return {csvParser};
})();

View file

@ -10,33 +10,30 @@ async function quickLoad() {
}
async function loadFromDropbox() {
const mapPath = document.getElementById("loadFromDropboxSelect")?.value;
const mapPath = byId("loadFromDropboxSelect")?.value;
DEBUG && console.log("Loading map from Dropbox:", mapPath);
DEBUG && console.info("Loading map from Dropbox:", mapPath);
const blob = await Cloud.providers.dropbox.load(mapPath);
uploadMap(blob);
}
async function createSharableDropboxLink() {
const mapFile = document.querySelector("#loadFromDropbox select").value;
const sharableLink = document.getElementById("sharableLink");
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
let url;
const sharableLink = byId("sharableLink");
const sharableLinkContainer = byId("sharableLinkContainer");
try {
url = await Cloud.providers.dropbox.getLink(mapFile);
} catch {
const previewLink = await Cloud.providers.dropbox.getLink(mapFile);
const directLink = previewLink.replace("www.dropbox.com", "dl.dropboxusercontent.com"); // DL allows CORS
const finalLink = `${location.origin}${location.pathname}?maplink=${directLink}`;
sharableLink.innerText = finalLink.slice(0, 45) + "...";
sharableLink.setAttribute("href", finalLink);
sharableLinkContainer.style.display = "block";
} catch (error) {
ERROR && console.error(error);
return tip("Dropbox API error. Can not create link.", true, "error", 2000);
}
const fmg = window.location.href.split("?")[0];
const reallink = `${fmg}?maplink=${url}`;
// voodoo magic required by the yellow god of CORS
const link = reallink.replace("www.dropbox.com/s/", "dl.dropboxusercontent.com/1/view/");
const shortLink = link.slice(0, 50) + "...";
sharableLinkContainer.style.display = "block";
sharableLink.innerText = shortLink;
sharableLink.setAttribute("href", link);
}
function loadMapPrompt(blob) {
@ -113,7 +110,7 @@ function uploadMap(file, callback) {
const fileReader = new FileReader();
fileReader.onloadend = async function (fileLoadedEvent) {
if (callback) callback();
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
byId("coas").innerHTML = ""; // remove auto-generated emblems
const result = fileLoadedEvent.target.result;
const [mapData, mapVersion] = await parseLoadedResult(result);
@ -186,22 +183,22 @@ function showUploadMessage(type, mapData, mapVersion) {
title = "Newer file";
canBeLoaded = false;
} else if (type === "outdated") {
message = `The map version (${mapVersion}) does not match the Generator version (${version}).<br>That is fine, click OK to the get map <b style="color: #005000">auto-updated</b>.<br>In case of issues please keep using an ${archive} of the Generator`;
title = "Outdated file";
canBeLoaded = true;
INFO && console.info(`Loading map. Auto-update from ${mapVersion} to ${version}`);
parseLoadedData(mapData, mapVersion);
return;
}
alertMessage.innerHTML = message;
const buttons = {
OK: function () {
$(this).dialog("close");
if (canBeLoaded) parseLoadedData(mapData);
if (canBeLoaded) parseLoadedData(mapData, mapVersion);
}
};
$("#alert").dialog({title, buttons});
}
async function parseLoadedData(data) {
async function parseLoadedData(data, mapVersion) {
try {
// exit customization
if (window.closeDialogs) closeDialogs();
@ -221,6 +218,7 @@ async function parseLoadedData(data) {
INFO && console.group("Loaded Map " + seed);
// TODO: move all to options object
void (function parseSettings() {
const settings = data[1].split("|");
if (settings[0]) applyOption(distanceUnitInput, settings[0]);
@ -229,23 +227,16 @@ async function parseLoadedData(data) {
if (settings[3]) applyOption(heightUnit, settings[3]);
if (settings[4]) heightExponentInput.value = heightExponentOutput.value = settings[4];
if (settings[5]) temperatureScale.value = settings[5];
if (settings[6]) barSizeInput.value = barSizeOutput.value = settings[6];
if (settings[7] !== undefined) barLabel.value = settings[7];
if (settings[8] !== undefined) barBackOpacity.value = settings[8];
if (settings[9]) barBackColor.value = settings[9];
if (settings[10]) barPosX.value = settings[10];
if (settings[11]) barPosY.value = settings[11];
// setting 6-11 (scaleBar) are part of style now, kept as "" in newer versions for compatibility
if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12];
if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13];
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100);
if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100);
if (settings[18]) precInput.value = precOutput.value = settings[18];
if (settings[19]) options = JSON.parse(settings[19]);
// setting 16 and 17 (temperature) are part of options now, kept as "" in newer versions for compatibility
if (settings[16]) options.temperatureEquator = +settings[16];
if (settings[17]) options.temperatureNorthPole = options.temperatureSouthPole = +settings[17];
if (settings[20]) mapName.value = settings[20];
if (settings[21]) hideLabels.checked = +settings[21];
if (settings[22]) stylePreset.value = settings[22];
@ -351,6 +342,15 @@ async function parseLoadedData(data) {
statePaths?.remove();
})();
void (function addMissingElements() {
if (!texture.size()) {
texture = viewbox
.insert("g", "#landmass")
.attr("id", "texture")
.attr("data-href", "./images/textures/plaster.jpg");
}
})();
void (function parseGridData() {
grid = JSON.parse(data[6]);
@ -404,46 +404,46 @@ async function parseLoadedData(data) {
})();
void (function restoreLayersState() {
// helper functions
const notHidden = selection => selection.node() && selection.style("display") !== "none";
const isVisible = selection => selection.node() && selection.style("display") !== "none";
const isVisibleNode = node => node && node.style.display !== "none";
const hasChildren = selection => selection.node()?.hasChildNodes();
const hasChild = (selection, selector) => selection.node()?.querySelector(selector);
const turnOn = el => document.getElementById(el).classList.remove("buttonoff");
const turnOn = el => byId(el).classList.remove("buttonoff");
// turn all layers off
document
.getElementById("mapLayers")
byId("mapLayers")
.querySelectorAll("li")
.forEach(el => el.classList.add("buttonoff"));
// turn on active layers
if (notHidden(texture) && hasChild(texture, "image")) turnOn("toggleTexture");
if (hasChild(texture, "image")) turnOn("toggleTexture");
if (hasChildren(terrs)) turnOn("toggleHeight");
if (hasChildren(biomes)) turnOn("toggleBiomes");
if (hasChildren(cells)) turnOn("toggleCells");
if (hasChildren(gridOverlay)) turnOn("toggleGrid");
if (hasChildren(coordinates)) turnOn("toggleCoordinates");
if (notHidden(compass) && hasChild(compass, "use")) turnOn("toggleCompass");
if (isVisible(compass) && hasChild(compass, "use")) turnOn("toggleCompass");
if (hasChildren(rivers)) turnOn("toggleRivers");
if (notHidden(terrain) && hasChildren(terrain)) turnOn("toggleRelief");
if (isVisible(terrain) && hasChildren(terrain)) turnOn("toggleRelief");
if (hasChildren(relig)) turnOn("toggleReligions");
if (hasChildren(cults)) turnOn("toggleCultures");
if (hasChildren(statesBody)) turnOn("toggleStates");
if (hasChildren(provs)) turnOn("toggleProvinces");
if (hasChildren(zones) && notHidden(zones)) turnOn("toggleZones");
if (notHidden(borders) && hasChild(borders, "path")) turnOn("toggleBorders");
if (notHidden(routes) && hasChild(routes, "path")) turnOn("toggleRoutes");
if (hasChildren(zones) && isVisible(zones)) turnOn("toggleZones");
if (isVisible(borders) && hasChild(borders, "path")) turnOn("toggleBorders");
if (isVisible(routes) && hasChild(routes, "path")) turnOn("toggleRoutes");
if (hasChildren(temperature)) turnOn("toggleTemp");
if (hasChild(population, "line")) turnOn("togglePopulation");
if (hasChildren(ice)) turnOn("toggleIce");
if (hasChild(prec, "circle")) turnOn("togglePrec");
if (notHidden(emblems) && hasChild(emblems, "use")) turnOn("toggleEmblems");
if (notHidden(labels)) turnOn("toggleLabels");
if (notHidden(icons)) turnOn("toggleIcons");
if (hasChildren(armies) && notHidden(armies)) turnOn("toggleMilitary");
if (isVisible(emblems) && hasChild(emblems, "use")) turnOn("toggleEmblems");
if (isVisible(labels)) turnOn("toggleLabels");
if (isVisible(icons)) turnOn("toggleIcons");
if (hasChildren(armies) && isVisible(armies)) turnOn("toggleMilitary");
if (hasChildren(markers)) turnOn("toggleMarkers");
if (notHidden(ruler)) turnOn("toggleRulers");
if (notHidden(scaleBar)) turnOn("toggleScaleBar");
if (isVisible(ruler)) turnOn("toggleRulers");
if (isVisible(scaleBar)) turnOn("toggleScaleBar");
if (isVisibleNode(byId("vignette"))) turnOn("toggleVignette");
getCurrentPreset();
})();
@ -456,17 +456,31 @@ async function parseLoadedData(data) {
})();
{
// dynamically import and run auto-udpdate script
// dynamically import and run auto-update script
const versionNumber = parseFloat(params[0]);
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.93.00");
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.97.04");
resolveVersionConflicts(versionNumber);
}
// add custom heightmap color scheme if any
if (heightmapColorSchemes) {
const oceanScheme = byId("oceanHeights")?.getAttribute("scheme");
if (oceanScheme && !(oceanScheme in heightmapColorSchemes)) addCustomColorScheme(oceanScheme);
const landScheme = byId("#landHeights")?.getAttribute("scheme");
if (landScheme && !(landScheme in heightmapColorSchemes)) addCustomColorScheme(landScheme);
}
{
// add custom texture if any
const textureHref = texture.attr("data-href");
if (textureHref) updateTextureSelectValue(textureHref);
}
void (function checkDataIntegrity() {
const cells = pack.cells;
if (pack.cells.i.length !== pack.cells.state.length) {
const message = "Data Integrity Check. Striping issue detected. To fix edit the heightmap in erase mode";
const message = "Data integrity check. Striping issue detected. To fix edit the heightmap in ERASE mode";
ERROR && console.error(message);
}
@ -474,7 +488,7 @@ async function parseLoadedData(data) {
invalidStates.forEach(s => {
const invalidCells = cells.i.filter(i => cells.state[i] === s);
invalidCells.forEach(i => (cells.state[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells);
ERROR && console.error("Data integrity check. Invalid state", s, "is assigned to cells", invalidCells);
});
const invalidProvinces = [...new Set(cells.province)].filter(
@ -483,14 +497,14 @@ async function parseLoadedData(data) {
invalidProvinces.forEach(p => {
const invalidCells = cells.i.filter(i => cells.province[i] === p);
invalidCells.forEach(i => (cells.province[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells);
ERROR && console.error("Data integrity check. Invalid province", p, "is assigned to cells", invalidCells);
});
const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed);
invalidCultures.forEach(c => {
const invalidCells = cells.i.filter(i => cells.culture[i] === c);
invalidCells.forEach(i => (cells.province[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells);
ERROR && console.error("Data integrity check. Invalid culture", c, "is assigned to cells", invalidCells);
});
const invalidReligions = [...new Set(cells.religion)].filter(
@ -499,14 +513,14 @@ async function parseLoadedData(data) {
invalidReligions.forEach(r => {
const invalidCells = cells.i.filter(i => cells.religion[i] === r);
invalidCells.forEach(i => (cells.religion[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid religion", r, "is assigned to cells", invalidCells);
ERROR && console.error("Data integrity check. Invalid religion", r, "is assigned to cells", invalidCells);
});
const invalidFeatures = [...new Set(cells.f)].filter(f => f && !pack.features[f]);
invalidFeatures.forEach(f => {
const invalidCells = cells.i.filter(i => cells.f[i] === f);
// No fix as for now
ERROR && console.error("Data Integrity Check. Invalid feature", f, "is assigned to cells", invalidCells);
ERROR && console.error("Data integrity check. Invalid feature", f, "is assigned to cells", invalidCells);
});
const invalidBurgs = [...new Set(cells.burg)].filter(
@ -515,7 +529,7 @@ async function parseLoadedData(data) {
invalidBurgs.forEach(burgId => {
const invalidCells = cells.i.filter(i => cells.burg[i] === burgId);
invalidCells.forEach(i => (cells.burg[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid burg", burgId, "is assigned to cells", invalidCells);
ERROR && console.error("Data integrity check. Invalid burg", burgId, "is assigned to cells", invalidCells);
});
const invalidRivers = [...new Set(cells.r)].filter(r => r && !pack.rivers.find(river => river.i === r));
@ -523,60 +537,112 @@ async function parseLoadedData(data) {
const invalidCells = cells.i.filter(i => cells.r[i] === r);
invalidCells.forEach(i => (cells.r[i] = 0));
rivers.select("river" + r).remove();
ERROR && console.error("Data Integrity Check. Invalid river", r, "is assigned to cells", invalidCells);
ERROR && console.error("Data integrity check. Invalid river", r, "is assigned to cells", invalidCells);
});
pack.burgs.forEach(burg => {
if ((!burg.i || burg.removed) && burg.lock) {
if (typeof burg.capital === "boolean") burg.capital = Number(burg.capital);
if (!burg.i && burg.lock) {
ERROR && console.error(`Data integrity check. Burg 0 is marked as locked, removing the status`);
delete burg.lock;
return;
}
if (burg.removed && burg.lock) {
ERROR &&
console.error(
`Data Integrity Check. Burg ${burg.i || "0"} is removed or invalid but still locked. Unlocking the burg`
);
console.error(`Data integrity check. Removed burg ${burg.i} is marked as locked. Unlocking the burg`);
delete burg.lock;
return;
}
if (!burg.i || burg.removed) return;
if (burg.cell === undefined || burg.x === undefined || burg.y === undefined) {
ERROR &&
console.error(
`Data Integrity Check. Burg ${burg.i} is missing cell info or coordinates. Removing the burg`
`Data integrity check. Burg ${burg.i} is missing cell info or coordinates. Removing the burg`
);
burg.removed = true;
}
if (burg.port < 0) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "has invalid port value", burg.port);
ERROR && console.error("Data integrity check. Burg", burg.i, "has invalid port value", burg.port);
burg.port = 0;
}
if (burg.cell >= cells.i.length) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to invalid cell", burg.cell);
ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to invalid cell", burg.cell);
burg.cell = findCell(burg.x, burg.y);
cells.i.filter(i => cells.burg[i] === burg.i).forEach(i => (cells.burg[i] = 0));
cells.burg[burg.cell] = burg.i;
}
if (burg.state && !pack.states[burg.state]) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to invalid state", burg.state);
ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to invalid state", burg.state);
burg.state = 0;
}
if (burg.state && pack.states[burg.state].removed) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to removed state", burg.state);
ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to removed state", burg.state);
burg.state = 0;
}
if (burg.state === undefined) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "has no state data");
ERROR && console.error("Data integrity check. Burg", burg.i, "has no state data");
burg.state = 0;
}
});
pack.states.forEach(state => {
if (state.removed) return;
const stateBurgs = pack.burgs.filter(b => b.state === state.i && !b.removed);
const capitalBurgs = stateBurgs.filter(b => b.capital);
if (!state.i && capitalBurgs.length) {
ERROR &&
console.error(
`Data integrity check. Neutral burgs (${capitalBurgs
.map(b => b.i)
.join(", ")}) marked as capitals. Moving them to towns`
);
capitalBurgs.forEach(burg => {
burg.capital = 0;
moveBurgToGroup(burg.i, "towns");
});
return;
}
if (capitalBurgs.length > 1) {
const message = `Data integrity check. State ${state.i} has multiple capitals (${capitalBurgs
.map(b => b.i)
.join(", ")}) assigned. Keeping the first as capital and moving others to towns`;
ERROR && console.error(message);
capitalBurgs.forEach((burg, i) => {
if (!i) return;
burg.capital = 0;
moveBurgToGroup(burg.i, "towns");
});
return;
}
if (state.i && stateBurgs.length && !capitalBurgs.length) {
ERROR &&
console.error(`Data integrity check. State ${state.i} has no capital. Assigning the first burg as capital`);
stateBurgs[0].capital = 1;
moveBurgToGroup(stateBurgs[0].i, "cities");
}
});
pack.provinces.forEach(p => {
if (!p.i || p.removed) return;
if (pack.states[p.state] && !pack.states[p.state].removed) return;
ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state);
ERROR && console.error("Data integrity check. Province", p.i, "is linked to removed state", p.state);
p.removed = true; // remove incorrect province
});
@ -586,7 +652,7 @@ async function parseLoadedData(data) {
pack.markers.forEach(marker => {
if (markerIds[marker.i]) {
ERROR && console.error("Data Integrity Check. Marker", marker.i, "has non-unique id. Changing to", nextId);
ERROR && console.error("Data integrity check. Marker", marker.i, "has non-unique id. Changing to", nextId);
const domElements = document.querySelectorAll("#marker" + marker.i);
if (domElements[1]) domElements[1].id = "marker" + nextId; // rename 2nd dom element
@ -606,7 +672,7 @@ async function parseLoadedData(data) {
}
})();
changeMapSize();
fitMapToScreen();
// remove href from emblems, to trigger rendering on load
emblems.selectAll("use").attr("href", null);
@ -627,7 +693,7 @@ async function parseLoadedData(data) {
ERROR && console.error(error);
clearMainTip();
alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br />generate a new random map or cancel the loading
alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br>generate a new random map or cancel the loading.<br>Map version: ${mapVersion}. Generator version: ${version}.
<p id="errorBox">${parseError(error)}</p>`;
$("#alert").dialog({

View file

@ -49,12 +49,12 @@ function prepareMapData() {
heightUnit.value,
heightExponentInput.value,
temperatureScale.value,
barSizeInput.value,
barLabel.value,
barBackOpacity.value,
barBackColor.value,
barPosX.value,
barPosY.value,
"", // previously used for barSize.value
"", // previously used for barLabel.value
"", // previously used for barBackColor.value
"", // previously used for barBackColor.value
"", // previously used for barPosX.value
"", // previously used for barPosY.value
populationRate,
urbanization,
mapSizeOutput.value,