diff --git a/modules/save-and-load.js b/modules/save-and-load.js deleted file mode 100644 index 44488bbc..00000000 --- a/modules/save-and-load.js +++ /dev/null @@ -1,1458 +0,0 @@ -// Functions to save and load the map -'use strict'; - -// 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'); -} - -// parse map svg to object url -async function getMapURL(type, subtype) { - const cloneEl = document.getElementById('map').cloneNode(true); // clone svg - cloneEl.id = 'fantasyMap'; - document.body.appendChild(cloneEl); - const clone = d3.select(cloneEl); - 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 (subtype === 'globe') clone.select('#scaleBar').remove(); - if (subtype === '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(); - } - - // add displayed resource icons TODO - - // replace ocean pattern href to base64 - if (PRODUCTION && 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('hatching').children.length) cloneEl.getElementById('hatching').remove(); //remove unused hatching group - 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', - '' - ); - - const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts - if (fontStyle) clone.select('defs').append('style').text(fontStyle.join('\n')); // add font to style - clone.remove(); - - const serialized = `` + 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(); - if (markers.style('display') === 'none') clone.select('#defs-markers').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(); -} - -// get non-standard fonts used for labels to fetch them from web -function getFontsToLoad(clone) { - const webSafe = ['Georgia', 'Times+New+Roman', 'Comic+Sans+MS', 'Lucida+Sans+Unicode', 'Courier+New', 'Verdana', 'Arial', 'Impact']; // fonts to not fetch - - const fontsInUse = new Set(); // to store fonts currently in use - clone.selectAll('#labels > g').each(function () { - if (!this.hasChildNodes()) return; - const font = this.dataset.font; - if (!font || webSafe.includes(font)) return; - fontsInUse.add(font); - }); - const legendFont = legend.attr('data-font'); - if (legend.node().hasChildNodes() && !webSafe.includes(legendFont)) fontsInUse.add(legendFont); - const fonts = [...fontsInUse]; - return fonts.length ? 'https://fonts.googleapis.com/css?family=' + fonts.join('|') : null; -} - -// code from Kaiido's answer https://stackoverflow.com/questions/42402584/how-to-use-google-fonts-in-canvas-when-drawing-dom-objects-in-svg -function GFontToDataURI(url) { - if (!url) return Promise.resolve(); - return fetch(url) // first fecth the embed stylesheet page - .then((resp) => resp.text()) // we only need the text of it - .then((text) => { - let s = document.createElement('style'); - s.innerHTML = text; - document.head.appendChild(s); - const styleSheet = Array.prototype.filter.call(document.styleSheets, (sS) => sS.ownerNode === s)[0]; - - const FontRule = (rule) => { - const src = rule.style.getPropertyValue('src'); - const url = src ? src.split('url(')[1].split(')')[0] : ''; - return {rule, src, url: url.substring(url.length - 1, 1)}; - }; - const fontProms = []; - - for (const r of styleSheet.cssRules) { - let fR = FontRule(r); - if (!fR.url) continue; - - fontProms.push( - fetch(fR.url) // fetch the actual font-file (.woff) - .then((resp) => resp.blob()) - .then((blob) => { - return new Promise((resolve) => { - let f = new FileReader(); - f.onload = (e) => resolve(f.result); - f.readAsDataURL(blob); - }); - }) - .then((dataURL) => fR.rule.cssText.replace(fR.url, dataURL)) - ); - } - document.head.removeChild(s); // clean up - return Promise.all(fontProms); // wait for all this has been done - }); -} - -// prepare map data for saving -function getMapData() { - TIME && console.time('createMapDataBlob'); - - return new Promise((resolve) => { - 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, - barSize.value, - barLabel.value, - barBackOpacity.value, - barBackColor.value, - barPosX.value, - barPosY.value, - populationRate.value, - urbanization.value, - mapSizeOutput.value, - latitudeOutput.value, - temperatureEquatorOutput.value, - temperaturePoleOutput.value, - precOutput.value, - JSON.stringify(options), - mapName.value - ].join('|'); - const coords = JSON.stringify(mapCoordinates); - const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join('|'); - const notesData = JSON.stringify(notes); - const rulersString = rulers.toString(); - - // clone svg - const cloneEl = document.getElementById('map').cloneNode(true); - - // set transform values to default - cloneEl.setAttribute('width', graphWidth); - cloneEl.setAttribute('height', graphHeight); - cloneEl.querySelector('#viewbox').removeAttribute('transform'); - - // always remove rulers - cloneEl.querySelector('#ruler').innerHTML = ''; - - const svg_xml = new XMLSerializer().serializeToString(cloneEl); - - const gridGeneral = JSON.stringify({spacing: grid.spacing, cellsX: grid.cellsX, cellsY: grid.cellsY, boundary: grid.boundary, points: grid.points, features: grid.features}); - const features = 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 resources = JSON.stringify(pack.resources); - - // store name array only if it is 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 resources - const pop = Array.from(pack.cells.pop).map((p) => rn(p, 4)); - - // data format as below - const data = [ - params, - settings, - coords, - biomes, - notesData, - svg_xml, - gridGeneral, - grid.cells.h, - grid.cells.prec, - grid.cells.f, - grid.cells.t, - grid.cells.temp, - features, - 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, - pack.cells.resource, - resources - ].join('\r\n'); - - const blob = new Blob([data], {type: 'text/plain'}); - - TIME && console.timeEnd('createMapDataBlob'); - resolve(blob); - }); -} - -// Download .map file -async function saveMap() { - if (customization) { - tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error'); - return; - } - closeDialogs('#alert'); - - const blob = await getMapData(); - 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); -} - -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 json = {type: 'FeatureCollection', features: []}; - - markers.selectAll('use').each(function () { - const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y); - const id = this.id; - const type = this.dataset.id.substring(1); - const icon = document.getElementById(type).textContent; - const note = notes.length ? notes.find((note) => note.id === this.id) : null; - const name = note ? note.name : ''; - const legend = note ? note.legend : ''; - - const feature = {type: 'Feature', geometry: {type: 'Point', coordinates}, properties: {id, type, icon, name, legend}}; - json.features.push(feature); - }); - - const name = getFileName('Markers') + '.geojson'; - downloadFile(JSON.stringify(json), name, 'application/json'); -} - -function getCellCoordinates(vertices) { - const p = pack.vertices.p; - const coordinates = vertices.map((n) => getQGIScoordinates(p[n][0], p[n][1])); - 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(getQGIScoordinates(p.x, p.y)); - } - 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] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); - points.push([x, y]); - } - return points; -} - -async function quickSave() { - if (customization) { - tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error'); - return; - } - const blob = await getMapData(); - 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); -} - -function quickLoad() { - ldb.get('lastMap', (blob) => { - if (blob) { - loadMapPrompt(blob); - } else { - tip('No map stored. Save map to storage first', true, 'error', 2000); - ERROR && console.error('No map stored'); - } - }); -} - -function loadMapPrompt(blob) { - const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes - if (workingTime < 5) return loadLastSavedMap(); - - const message = 'Are you sure you want to load saved map?
All unsaved changes made to the current map will be lost'; - confirmationDialog({title: 'Load saved map', message, confirm: 'Load', onConfirm: loadLastSavedMap}); - - function loadLastSavedMap() { - WARN && console.warn('Load last saved map'); - try { - uploadMap(blob); - } catch (error) { - ERROR && console.error(error); - tip('Cannot load last saved map', true, 'error', 2000); - } - } -} - -const saveReminder = function () { - if (localStorage.getItem('noReminder')) return; - const message = [ - "Please don't forget to save your work as a .map file", - 'Please remember to save work as a .map file', - "Saving in .map format will ensure your data won't be lost in case of issues", - 'Safety is number one priority. Please save the map', - "Don't forget to save your map on a regular basis!", - 'Just a gentle reminder for you to save the map', - "Please 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" - ]; - - saveReminder.reminder = setInterval(() => { - if (customization) return; - tip(ra(message), true, 'warn', 2500); - }, 1e6); - saveReminder.status = 1; -}; - -saveReminder(); - -function toggleSaveReminder() { - if (saveReminder.status) { - tip('Save reminder is turned off. Press CTRL+Q again to re-initiate', true, 'warn', 2000); - clearInterval(saveReminder.reminder); - localStorage.setItem('noReminder', true); - saveReminder.status = 0; - } else { - tip('Save reminder is turned on. Press CTRL+Q to turn off', true, 'warn', 2000); - localStorage.removeItem('noReminder'); - saveReminder(); - } -} - -function uploadMap(file, callback) { - uploadMap.timeStart = performance.now(); - - const fileReader = new FileReader(); - fileReader.onload = function (fileLoadedEvent) { - if (callback) callback(); - document.getElementById('coas').innerHTML = ''; // remove auto-generated emblems - - const dataLoaded = fileLoadedEvent.target.result; - const data = dataLoaded.split('\r\n'); - - const mapVersion = data[0].split('|')[0] || data[0]; - if (mapVersion === version) { - parseLoadedData(data); - return; - } - - const archive = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog', 'archived version'); - const parsed = parseFloat(mapVersion); - let message = '', - load = false; - if (isNaN(parsed) || data.length < 26 || !data[5]) { - message = `The file you are trying to load is outdated or not a valid .map file. -
Please try to open it using an ${archive}`; - } else if (parsed < 0.7) { - message = `The map version you are trying to load (${mapVersion}) is too old and cannot be updated to the current version. -
Please keep using an ${archive}`; - } else { - load = true; - message = `The map version (${mapVersion}) does not match the Generator version (${version}). -
Click OK to get map auto-updated. In case of issues please keep using an ${archive} of the Generator`; - } - alertMessage.innerHTML = message; - $('#alert').dialog({ - title: 'Version conflict', - width: '38em', - buttons: { - OK: function () { - $(this).dialog('close'); - if (load) parseLoadedData(data); - } - } - }); - }; - - fileReader.readAsText(file, 'UTF-8'); -} - -function parseLoadedData(data) { - try { - // exit customization - if (window.closeDialogs) closeDialogs(); - customization = 0; - if (customizationMenu.offsetParent) styleTab.click(); - - const reliefIcons = document.getElementById('defs-relief').innerHTML; // save relief icons - const hatching = document.getElementById('hatching').cloneNode(true); // save hatching - - void (function parseParameters() { - const params = data[0].split('|'); - if (params[3]) { - seed = params[3]; - optionsSeed.value = seed; - } - if (params[4]) graphWidth = +params[4]; - if (params[5]) graphHeight = +params[5]; - mapId = params[6] ? +params[6] : Date.now(); - })(); - - INFO && console.group('Loaded Map ' + seed); - - void (function parseSettings() { - const settings = data[1].split('|'); - if (settings[0]) applyOption(distanceUnitInput, settings[0]); - if (settings[1]) distanceScaleInput.value = distanceScaleOutput.value = settings[1]; - if (settings[2]) areaUnit.value = settings[2]; - 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]) barSize.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]; - if (settings[12]) populationRate.value = populationRateOutput.value = settings[12]; - if (settings[13]) urbanization.value = urbanizationOutput.value = settings[13]; - if (settings[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(settings[14], 100), 1); - if (settings[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(settings[15], 100), 0); - if (settings[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = settings[16]; - if (settings[17]) temperaturePoleInput.value = temperaturePoleOutput.value = settings[17]; - if (settings[18]) precInput.value = precOutput.value = settings[18]; - if (settings[19]) options = JSON.parse(settings[19]); - if (settings[20]) mapName.value = settings[20]; - })(); - - void (function parseConfiguration() { - if (data[2]) mapCoordinates = JSON.parse(data[2]); - if (data[4]) notes = JSON.parse(data[4]); - if (data[33]) rulers.fromString(data[33]); - - const biomes = data[3].split('|'); - biomesData = applyDefaultBiomesSystem(); - biomesData.color = biomes[0].split(','); - biomesData.habitability = biomes[1].split(',').map((h) => +h); - biomesData.name = biomes[2].split(','); - - // push custom biomes if any - for (let i = biomesData.i.length; i < biomesData.name.length; i++) { - biomesData.i.push(biomesData.i.length); - biomesData.iconsDensity.push(0); - biomesData.icons.push([]); - biomesData.cost.push(50); - } - })(); - - void (function replaceSVG() { - svg.remove(); - document.body.insertAdjacentHTML('afterbegin', data[5]); - })(); - - void (function redefineElements() { - svg = d3.select('#map'); - defs = svg.select('#deftemp'); - viewbox = svg.select('#viewbox'); - scaleBar = svg.select('#scaleBar'); - legend = svg.select('#legend'); - ocean = viewbox.select('#ocean'); - oceanLayers = ocean.select('#oceanLayers'); - oceanPattern = ocean.select('#oceanPattern'); - lakes = viewbox.select('#lakes'); - landmass = viewbox.select('#landmass'); - texture = viewbox.select('#texture'); - terrs = viewbox.select('#terrs'); - biomes = viewbox.select('#biomes'); - ice = viewbox.select('#ice'); - cells = viewbox.select('#cells'); - gridOverlay = viewbox.select('#gridOverlay'); - coordinates = viewbox.select('#coordinates'); - compass = viewbox.select('#compass'); - rivers = viewbox.select('#rivers'); - terrain = viewbox.select('#terrain'); - relig = viewbox.select('#relig'); - cults = viewbox.select('#cults'); - regions = viewbox.select('#regions'); - statesBody = regions.select('#statesBody'); - statesHalo = regions.select('#statesHalo'); - provs = viewbox.select('#provs'); - zones = viewbox.select('#zones'); - borders = viewbox.select('#borders'); - stateBorders = borders.select('#stateBorders'); - provinceBorders = borders.select('#provinceBorders'); - routes = viewbox.select('#routes'); - roads = routes.select('#roads'); - trails = routes.select('#trails'); - searoutes = routes.select('#searoutes'); - temperature = viewbox.select('#temperature'); - coastline = viewbox.select('#coastline'); - prec = viewbox.select('#prec'); - population = viewbox.select('#population'); - goods = viewbox.select('#goods'); - emblems = viewbox.select('#emblems'); - labels = viewbox.select('#labels'); - icons = viewbox.select('#icons'); - burgIcons = icons.select('#burgIcons'); - anchors = icons.select('#anchors'); - armies = viewbox.select('#armies'); - markers = viewbox.select('#markers'); - ruler = viewbox.select('#ruler'); - fogging = viewbox.select('#fogging'); - debug = viewbox.select('#debug'); - burgLabels = labels.select('#burgLabels'); - })(); - - void (function parseGridData() { - grid = JSON.parse(data[6]); - calculateVoronoi(grid, grid.points); - grid.cells.h = Uint8Array.from(data[7].split(',')); - grid.cells.prec = Uint8Array.from(data[8].split(',')); - grid.cells.f = Uint16Array.from(data[9].split(',')); - grid.cells.t = Int8Array.from(data[10].split(',')); - grid.cells.temp = Int8Array.from(data[11].split(',')); - })(); - - void (function parsePackData() { - pack = {}; - reGraph(); - reMarkFeatures(); - pack.features = JSON.parse(data[12]); - pack.cultures = JSON.parse(data[13]); - pack.states = JSON.parse(data[14]); - pack.burgs = JSON.parse(data[15]); - pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: 'No religion'}]; - pack.provinces = data[30] ? JSON.parse(data[30]) : [0]; - pack.rivers = data[32] ? JSON.parse(data[32]) : []; - pack.resources = data[35] ? JSON.parse(data[35]) : []; - - const cells = pack.cells; - cells.biome = Uint8Array.from(data[16].split(',')); - cells.burg = Uint16Array.from(data[17].split(',')); - cells.conf = Uint8Array.from(data[18].split(',')); - cells.culture = Uint16Array.from(data[19].split(',')); - cells.fl = Uint16Array.from(data[20].split(',')); - cells.pop = Float32Array.from(data[21].split(',')); - cells.r = Uint16Array.from(data[22].split(',')); - cells.road = Uint16Array.from(data[23].split(',')); - cells.s = Uint16Array.from(data[24].split(',')); - cells.state = Uint16Array.from(data[25].split(',')); - cells.religion = data[26] ? Uint16Array.from(data[26].split(',')) : new Uint16Array(cells.i.length); - cells.province = data[27] ? Uint16Array.from(data[27].split(',')) : new Uint16Array(cells.i.length); - cells.crossroad = data[28] ? Uint16Array.from(data[28].split(',')) : new Uint16Array(cells.i.length); - cells.resource = data[34] ? Uint8Array.from(data[34].split(',')) : new Uint8Array(cells.i.length); - - if (data[31]) { - const namesDL = data[31].split('/'); - namesDL.forEach((d, i) => { - const e = d.split('|'); - if (!e.length) return; - const b = e[5].split(',').length > 2 || !nameBases[i] ? e[5] : nameBases[i].b; - nameBases[i] = {name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b}; - }); - } - })(); - - const notHidden = (selection) => selection.node() && selection.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'); - - void (function restoreLayersState() { - // turn all layers off - document - .getElementById('mapLayers') - .querySelectorAll('li') - .forEach((el) => el.classList.add('buttonoff')); - - // turn on active layers - if (notHidden(texture) && 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 (notHidden(rivers)) turnOn('toggleRivers'); - if (notHidden(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(compass, 'use')) turnOn('toggleBorders'); - if (notHidden(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 (hasChildren(goods)) turnOn('toggleResources'); - 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 (hasChildren(markers) && notHidden(markers)) turnOn('toggleMarkers'); - if (notHidden(ruler)) turnOn('toggleRulers'); - if (notHidden(scaleBar)) turnOn('toggleScaleBar'); - - getCurrentPreset(); - })(); - - void (function restoreEvents() { - scaleBar.on('mousemove', () => tip('Click to open Units Editor')).on('click', () => editUnits()); - legend.on('mousemove', () => tip('Drag to change the position. Click to hide the legend')).on('click', () => clearLegend()); - })(); - - void (function resolveVersionConflicts() { - const version = parseFloat(data[0].split('|')[0]); - if (version < 0.9) { - // 0.9 has additional relief icons to be included into older maps - document.getElementById('defs-relief').innerHTML = reliefIcons; - } - - if (version < 1) { - // 1.0 adds a new religions layer - relig = viewbox.insert('g', '#terrain').attr('id', 'relig'); - Religions.generate(); - - // 1.0 adds a legend box - legend = svg.append('g').attr('id', 'legend'); - legend - .attr('font-family', 'Almendra SC') - .attr('data-font', 'Almendra+SC') - .attr('font-size', 13) - .attr('data-size', 13) - .attr('data-x', 99) - .attr('data-y', 93) - .attr('stroke-width', 2.5) - .attr('stroke', '#812929') - .attr('stroke-dasharray', '0 4 10 4') - .attr('stroke-linecap', 'round'); - - // 1.0 separated drawBorders fron drawStates() - stateBorders = borders.append('g').attr('id', 'stateBorders'); - provinceBorders = borders.append('g').attr('id', 'provinceBorders'); - borders.attr('opacity', null).attr('stroke', null).attr('stroke-width', null).attr('stroke-dasharray', null).attr('stroke-linecap', null).attr('filter', null); - stateBorders.attr('opacity', 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'); - - // 1.0 adds 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(); - - // 1.0 adds hatching - document.getElementsByTagName('defs')[0].appendChild(hatching); - - // 1.0 adds zones layer - zones = viewbox.insert('g', '#borders').attr('id', 'zones').attr('display', 'none'); - zones.attr('opacity', 0.6).attr('stroke', null).attr('stroke-width', 0).attr('stroke-dasharray', null).attr('stroke-linecap', 'butt'); - addZones(); - if (!markers.selectAll('*').size()) { - addMarkers(); - turnButtonOn('toggleMarkers'); - } - - // 1.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'); - - // 1.0 changes states opacity bask to regions level - if (statesBody.attr('opacity')) { - regions.attr('opacity', statesBody.attr('opacity')); - statesBody.attr('opacity', null); - } - - // 1.0 changed labels to multi-lined - labels.selectAll('textPath').each(function () { - const text = this.textContent; - const shift = this.getComputedTextLength() / -1.5; - this.innerHTML = `${text}`; - }); - - // 1.0 added new biome - Wetland - biomesData.name.push('Wetland'); - biomesData.color.push('#0b9131'); - biomesData.habitability.push(12); - } - - if (version < 1.1) { - // v 1.0 initial code had a bug with religion layer id - if (!relig.size()) relig = viewbox.insert('g', '#terrain').attr('id', 'relig'); - - // v 1.0 initially has Sympathy status then relaced with Friendly - for (const s of pack.states) { - 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); - - // v 1.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); - } - - // v 1.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); - } - - // v 1.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) { - // v 1.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); - - // v 1.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); - }); - } - - // v 1.11 had an issue with fogging being displayed on load - unfog(); - - // v 1.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) { - // v 1.11 replaced "display" attribute by "display" style - viewbox.selectAll('g').each(function () { - if (this.hasAttribute('display')) { - this.removeAttribute('display'); - this.style.display = 'none'; - } - }); - - // v 1.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) { - // v 1.22 changed state neighbors from Set object to array - BurgsAndStates.collectStatistics(); - } - - if (version < 1.3) { - // v 1.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}; - - // v 1.3 added campaings data for all states - BurgsAndStates.generateCampaigns(); - - // v 1.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) { - // v 1.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); - } - - // v 1.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(); - - // v 1.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 '⚔️'; - } - - // 1.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'); - - // v 1.5 cultures has shield attribute - pack.cultures.forEach((culture) => { - if (culture.removed) return; - culture.shield = Cultures.getRandomShield(); - }); - - // v 1.5 added burg type value - pack.burgs.forEach((burg) => { - if (!burg.i || burg.removed) return; - burg.type = BurgsAndStates.getType(burg.cell, burg.port); - }); - - // v 1.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(); - - // v 1.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) { - // v 1.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); - } - } - - // v 1.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) { - // v 1.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 = ``; - document.getElementById('oceanPattern').setAttribute('opacity', 0.2); - } - - if (version < 1.62) { - // v 1.62 changed grid data - gridOverlay.attr('size', null); - } - - if (version < 1.7) { - // v 1.7 added resources layer - goods = viewbox.append('g').attr('id', 'goods'); - defs.append('g').attr('id', 'defs-icons'); - Resources.generate(); - } - })(); - - void (function checkDataIntegrity() { - const cells = pack.cells; - - if (pack.cells.i.length !== pack.cells.state.length) { - ERROR && console.error('Striping issue. Map data is corrupted. The only solution is to edit the heightmap in erase mode'); - } - - const invalidStates = [...new Set(cells.state)].filter((s) => !pack.states[s] || pack.states[s].removed); - 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); - }); - - const invalidProvinces = [...new Set(cells.province)].filter((p) => p && (!pack.provinces[p] || pack.provinces[p].removed)); - 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); - }); - - 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); - }); - - const invalidReligions = [...new Set(cells.religion)].filter((r) => !pack.religions[r] || pack.religions[r].removed); - 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', c, '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); - }); - - const invalidBurgs = [...new Set(cells.burg)].filter((b) => b && (!pack.burgs[b] || pack.burgs[b].removed)); - invalidBurgs.forEach((b) => { - const invalidCells = cells.i.filter((i) => cells.burg[i] === b); - invalidCells.forEach((i) => (cells.burg[i] = 0)); - ERROR && console.error('Data Integrity Check. Invalid burg', b, 'is assigned to cells', invalidCells); - }); - - const invalidRivers = [...new Set(cells.r)].filter((r) => r && !pack.rivers.find((river) => river.i === r)); - invalidRivers.forEach((r) => { - 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); - }); - - pack.burgs.forEach((b) => { - if (!b.i || b.removed) return; - if (b.port < 0) { - ERROR && console.error('Data Integrity Check. Burg', b.i, 'has invalid port value', b.port); - b.port = 0; - } - - if (b.cell >= cells.i.length) { - ERROR && console.error('Data Integrity Check. Burg', b.i, 'is linked to invalid cell', b.cell); - b.cell = findCell(b.x, b.y); - cells.i.filter((i) => cells.burg[i] === b.i).forEach((i) => (cells.burg[i] = 0)); - cells.burg[b.cell] = b.i; - } - - if (b.state && !pack.states[b.state]) { - ERROR && console.error('Data Integrity Check. Burg', b.i, 'is linked to invalid state', b.state); - b.state = 0; - } - }); - - 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); - p.removed = true; // remove incorrect province - }); - })(); - - changeMapSize(); - - // remove href from emblems, to trigger rendering on load - emblems.selectAll('use').attr('href', null); - - // draw data layers (no kept in svg) - if (rulers && layerIsOn('toggleRulers')) rulers.draw(); - if (layerIsOn('toggleGrid')) drawGrid(); - - // set options - yearInput.value = options.year; - eraInput.value = options.era; - - if (window.restoreDefaultEvents) restoreDefaultEvents(); - focusOn(); // based on searchParams focus on point, cell or burg - invokeActiveZooming(); - - WARN && console.warn(`TOTAL: ${rn((performance.now() - uploadMap.timeStart) / 1000, 2)}s`); - showStatistics(); - INFO && console.groupEnd('Loaded Map ' + seed); - tip('Map is successfully loaded', true, 'success', 7000); - } catch (error) { - ERROR && console.error(error); - clearMainTip(); - - alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load, -
generate a new random map or cancel the loading -

${parseError(error)}

`; - $('#alert').dialog({ - resizable: false, - title: 'Loading error', - maxWidth: '50em', - buttons: { - 'Select file': function () { - $(this).dialog('close'); - mapToLoad.click(); - }, - 'New map': function () { - $(this).dialog('close'); - regenerateMap(); - }, - Cancel: function () { - $(this).dialog('close'); - } - }, - position: {my: 'center', at: 'center', of: 'svg'} - }); - } -}