import {findCell, findGridCell} from "/src/utils/graphUtils"; import {rn} from "/src/utils/numberUtils"; import {link} from "/src/utils/linkUtils"; import {getCoordinates, toDMS} from "/src/utils/coordinateUtils"; import {si} from "/src/utils/unitUtils"; // fit full-screen map if window is resized window.addEventListener("resize", function (e) { if (stored("mapWidth") && stored("mapHeight")) return; mapWidthInput.value = window.innerWidth; mapHeightInput.value = window.innerHeight; changeMapSize(); }); if (location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1") { window.onbeforeunload = () => "Are you sure you want to navigate away?"; } function highlightEditorLine(editor, id, timeout = 10000) { Array.from(editor.getElementsByClassName("states hovered")).forEach(el => el.classList.remove("hovered")); // clear all hovered const hovered = Array.from(editor.querySelectorAll("div")).find(el => el.dataset.id == id); if (hovered) hovered.classList.add("hovered"); // add hovered class if (timeout) setTimeout(() => { hovered && hovered.classList.remove("hovered"); }, timeout); } // get cell info on mouse move function updateCellInfo(point, i, g) { const cells = pack.cells; const x = (infoX.innerHTML = rn(point[0])); const y = (infoY.innerHTML = rn(point[1])); const f = cells.f[i]; const [lon, lat] = getCoordinates(x, y, 4); infoLat.innerHTML = toDMS(lat, "lat"); infoLon.innerHTML = toDMS(lon, "lon"); infoCell.innerHTML = i; infoArea.innerHTML = cells.area[i] ? si(getArea(cells.area[i])) + " " + getAreaUnit() : "n/a"; infoEvelation.innerHTML = getElevation(pack.features[f], pack.cells.h[i]); infoDepth.innerHTML = getDepth(pack.features[f], point); infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]); infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a"; infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no"; infoState.innerHTML = cells.h[i] >= 20 ? cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : "neutral lands (0)" : "no"; infoProvince.innerHTML = cells.province[i] ? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})` : "no"; infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : "no"; infoReligion.innerHTML = cells.religion[i] ? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})` : "no"; infoPopulation.innerHTML = getFriendlyPopulation(i); infoBurg.innerHTML = cells.burg[i] ? pack.burgs[cells.burg[i]].name + " (" + cells.burg[i] + ")" : "no"; infoFeature.innerHTML = f ? pack.features[f].group + " (" + f + ")" : "n/a"; infoBiome.innerHTML = biomesData.name[cells.biome[i]]; } // get surface elevation function getElevation(f, h) { if (f.land) return getHeight(h) + " (" + h + ")"; // land: usual height if (f.border) return "0 " + heightUnit.value; // ocean: 0 if (f.type === "lake") return getHeight(f.height) + " (" + f.height + ")"; // lake: defined on river generation } // get water depth function getDepth(f, p) { if (f.land) return "0 " + heightUnit.value; // land: 0 // lake: difference between surface and bottom const gridH = grid.cells.h[findGridCell(p[0], p[1], grid)]; if (f.type === "lake") { const depth = gridH === 19 ? f.height / 2 : gridH; return getHeight(depth, "abs"); } return getHeight(gridH, "abs"); // ocean: grid height } // get user-friendly (real-world) height value from map data export function getFriendlyHeight([x, y]) { const packH = pack.cells.h[findCell(x, y)]; const gridH = grid.cells.h[findGridCell(x, y, grid)]; const h = packH < 20 ? gridH : packH; return getHeight(h); } function getHeight(h, abs) { const unit = heightUnit.value; let unitRatio = 3.281; // default calculations are in feet if (unit === "m") unitRatio = 1; // if meter else if (unit === "f") unitRatio = 0.5468; // if fathom let height = -990; if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value); else if (h < 20 && h > 0) height = ((h - 20) / h) * 50; if (abs) height = Math.abs(height); return rn(height * unitRatio) + " " + unit; } function getPrecipitation(prec) { return prec * 100 + " mm"; } // get user-friendly (real-world) precipitation value from map data function getFriendlyPrecipitation(i) { const prec = grid.cells.prec[pack.cells.g[i]]; return getPrecipitation(prec); } function getRiverInfo(id) { const r = pack.rivers.find(r => r.i == id); return r ? `${r.name} ${r.type} (${id})` : "n/a"; } function getCellPopulation(i) { const rural = pack.cells.pop[i] * populationRate; const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate * urbanization : 0; return [rural, urban]; } // get user-friendly (real-world) population value from map data function getFriendlyPopulation(i) { const [rural, urban] = getCellPopulation(i); return `${si(rural + urban)} (${si(rural)} rural, urban ${si(urban)})`; } function getPopulationTip(i) { const [rural, urban] = getCellPopulation(i); return `Cell population: ${si(rural + urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`; } function highlightEmblemElement(type, el) { const i = el.i, cells = pack.cells; const animation = d3.transition().duration(1000).ease(d3.easeSinIn); if (type === "burg") { const {x, y} = el; debug .append("circle") .attr("cx", x) .attr("cy", y) .attr("r", 0) .attr("fill", "none") .attr("stroke", "#d0240f") .attr("stroke-width", 1) .attr("opacity", 1) .transition(animation) .attr("r", 20) .attr("opacity", 0.1) .attr("stroke-width", 0) .remove(); return; } const [x, y] = el.pole || pack.cells.p[el.center]; const obj = type === "state" ? cells.state : cells.province; const borderCells = cells.i.filter(id => obj[id] === i && cells.c[id].some(n => obj[n] !== i)); const data = Array.from(borderCells) .filter((c, i) => !(i % 2)) .map(i => cells.p[i]) .map(i => [i[0], i[1], Math.hypot(i[0] - x, i[1] - y)]); debug .selectAll("line") .data(data) .enter() .append("line") .attr("x1", x) .attr("y1", y) .attr("x2", d => d[0]) .attr("y2", d => d[1]) .attr("stroke", "#d0240f") .attr("stroke-width", 0.5) .attr("opacity", 0.2) .attr("stroke-dashoffset", d => d[2]) .attr("stroke-dasharray", d => d[2]) .transition(animation) .attr("stroke-dashoffset", 0) .attr("opacity", 1) .transition(animation) .delay(1000) .attr("stroke-dashoffset", d => d[2]) .attr("opacity", 0) .remove(); } // assign skeaker behaviour Array.from(document.getElementsByClassName("speaker")).forEach(el => { const input = el.previousElementSibling; el.addEventListener("click", () => speak(input.value)); }); function speak(text) { const speaker = new SpeechSynthesisUtterance(text); const voices = speechSynthesis.getVoices(); if (voices.length) { const voiceId = +document.getElementById("speakerVoice").value; speaker.voice = voices[voiceId]; } speechSynthesis.speak(speaker); } // apply drop-down menu option. If the value is not in options, add it export function applyOption($select, value, name = value) { const isExisting = Array.from($select.options).some(o => o.value === value); if (!isExisting) $select.options.add(new Option(name, value)); $select.value = value; } // show info about the generator in a popup function showInfo() { const Discord = link("https://discordapp.com/invite/X7E84HU", "Discord"); const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit"); const Patreon = link("https://www.patreon.com/azgaar", "Patreon"); const Armoria = link("https://azgaar.github.io/Armoria", "Armoria"); const QuickStart = link( "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial", "Quick start tutorial" ); const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page"); const VideoTutorial = link("https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C", "Video tutorial"); alertMessage.innerHTML = /* html */ `Fantasy Map Generator (FMG) is a free open-source application. It means that you own all created maps and can use them as you wish.

The development is community-backed, you can donate on ${Patreon}. You can also help creating overviews, tutorials and spreding the word about the Generator.

The best way to get help is to contact the community on ${Discord} and ${Reddit}. Before asking questions, please check out the ${QuickStart}, the ${QAA}, and ${VideoTutorial}.

Check out our another project: ${Armoria} — heraldry generator and editor.

`; $("#alert").dialog({ resizable: false, title: document.title, width: "28em", buttons: { OK: function () { $(this).dialog("close"); } }, position: {my: "center", at: "center", of: "svg"} }); }