// Module to store general UI functions "use strict"; // fit full-screen map if window is resized $(window).resize(function(e) { mapWidthInput.value = window.innerWidth; mapHeightInput.value = window.innerHeight; changeMapSize(); }); window.onbeforeunload = () => "Are you sure you want to navigate away?"; // Tooltips const tooltip = document.getElementById("tooltip"); // show tip for non-svg elemets with data-tip document.getElementById("dialogs").addEventListener("mousemove", showDataTip); document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip); function tip(tip = "Tip is undefined", main, error, time) { tooltip.innerHTML = tip; tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)"; if (error === "error") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)"; else if (error === "warn") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)"; else if (error === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)"; if (main) tooltip.dataset.main = tip; // set main tip if (time) setTimeout(tooltip.dataset.main = "", time); // clear main in some time } function showMainTip() { tooltip.innerHTML = tooltip.dataset.main; } function clearMainTip() { tooltip.dataset.main = ""; tooltip.innerHTML = ""; } function showDataTip(e) { if (!e.target) return; if (e.target.dataset.tip) {tip(e.target.dataset.tip); return;}; if (e.target.parentNode.dataset.tip) tip(e.target.parentNode.dataset.tip); } function moved() { const point = d3.mouse(this); const i = findCell(point[0], point[1]); // pack cell id if (i === undefined) return; showNotes(d3.event, i); const g = findGridCell(point[0], point[1]); // grid cell id if (tooltip.dataset.main) showMainTip(); else showMapTooltip(point, d3.event, i, g); if (toolsContent.style.display === "block" && cellInfo.style.display === "block") updateCellInfo(point, i, g); } // show note box on hover (if any) function showNotes(e, i) { let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id; if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; else if (e.target.parentNode.parentNode.id === "burgIcons") id = "burg" + e.target.dataset.id; const note = notes.find(note => note.id === id); if (note !== undefined && note.legend !== "") { document.getElementById("notes").style.display = "block"; document.getElementById("notesHeader").innerHTML = note.name; document.getElementById("notesBody").innerHTML = note.legend; } else { document.getElementById("notes").style.display = "none"; document.getElementById("notesHeader").innerHTML = ""; document.getElementById("notesBody").innerHTML = ""; } } // show viewbox tooltip if main tooltip is blank function showMapTooltip(point, e, i, g) { tip(""); // clear tip const tag = e.target.tagName; const path = e.composedPath ? e.composedPath() : getComposedPath(e.target); // apply polyfill if (!path[path.length - 8]) return; const group = path[path.length - 7].id; const subgroup = path[path.length - 8].id; const land = pack.cells.h[i] >= 20; //const type = pack.features[cells.f[i]].type; // specific elements if (group === "rivers") {tip("Click to edit the River"); return;} if (group === "routes") {tip("Click to edit the Route"); return;} if (group === "terrain") {tip("Click to edit the Relief Icon"); return;} if (subgroup === "burgLabels" || subgroup === "burgIcons") {tip("Click to open Burg Editor"); return;} if (group === "labels") {tip("Click to edit the Label"); return;} if (group === "markers") {tip("Click to edit the Marker"); return;} if (group === "ruler") { if (tag === "rect") {tip("Drag to split the ruler into 2 parts"); return;} if (tag === "circle") {tip("Drag to adjust the measurer"); return;} if (tag === "path" || tag === "line") {tip("Drag to move the measurer"); return;} if (tag === "text") {tip("Click to remove the measurer"); return;} } if (subgroup === "burgIcons") {tip("Click to edit the Burg"); return;} if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;} if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;} if (group === "coastline") {tip("Click to edit the coastline"); return;} if (group === "zones") {tip(path[path.length-8].dataset.description); return;} // covering elements if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else if (layerIsOn("togglePopulation")) tip("Population: "+ getFriendlyPopulation(i)); else if (layerIsOn("toggleTemp")) tip("Temperature: " + convertTemperature(grid.cells.temp[g])); else if (layerIsOn("toggleBiomes") && pack.cells.biome[i]) tip("Biome: " + biomesData.name[pack.cells.biome[i]]); else if (layerIsOn("toggleReligions") && pack.cells.religion[i]) { const religion = pack.religions[pack.cells.religion[i]]; const type = religion.type === "Cult" || religion.type == "Heresy" ? religion.type : religion.type + " religion"; tip(type + ": " + religion.name); } else if (pack.cells.state[i] && (layerIsOn("toggleProvinces") || layerIsOn("toggleStates"))) { const state = pack.states[pack.cells.state[i]].fullName; const province = pack.cells.province[i]; const prov = province ? pack.provinces[province].fullName + ", " : ""; tip(prov + state); } else if (layerIsOn("toggleCultures") && pack.cells.culture[i]) tip("Culture: " + pack.cultures[pack.cells.culture[i]].name); else if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point)); //if (pack.cells.t[i] === 1 && !tooltip.textContent) tip("Click to edit the coastline"); } // get cell info on mouse move function updateCellInfo(point, i, g) { const cells = pack.cells; infoX.innerHTML = rn(point[0]); infoY.innerHTML = rn(point[1]); infoCell.innerHTML = i; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; infoArea.innerHTML = cells.area[i] ? si(cells.area[i] * distanceScaleInput.value ** 2) + unit : "n/a"; const h = pack.cells.h[i] < 20 ? grid.cells.h[pack.cells.g[i]] : pack.cells.h[i]; infoHeight.innerHTML = getFriendlyHeight(point) + " (" + h + ")"; infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]); infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a"; 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"; const f = cells.f[i]; infoFeature.innerHTML = f ? pack.features[f].group + " (" + f + ")" : "n/a"; infoBiome.innerHTML = biomesData.name[cells.biome[i]]; } // get user-friendly (real-world) height value from map data function getFriendlyHeight(p) { const packH = pack.cells.h[findCell(p[0], p[1])]; const gridH = grid.cells.h[findGridCell(p[0], p[1])]; const h = packH < 20 ? gridH : packH; return getHeight(h); } function getHeight(h) { 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; return rn(height * unitRatio) + " " + unit; } // get user-friendly (real-world) precipitation value from map data function getFriendlyPrecipitation(i) { const prec = grid.cells.prec[pack.cells.g[i]]; return prec * 100 + " mm"; } // get user-friendly (real-world) population value from map data function getFriendlyPopulation(i) { const rural = pack.cells.pop[i] * populationRate.value; const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate.value * urbanization.value : 0; return si(rural+urban); } // assign lock behavior document.querySelectorAll("[data-locked]").forEach(function(e) { e.addEventListener("mouseover", function(event) { if (this.className === "icon-lock") tip("Click to unlock the option and allow it to be randomized on new map generation"); else tip("Click to lock the option and always use the current value on new map generation"); event.stopPropagation(); }); e.addEventListener("click", function(event) { const id = (this.id).slice(5); if (this.className === "icon-lock") unlock(id); else lock(id); }); }); // lock option function lock(id) { const input = document.querySelector("[data-stored='"+id+"']"); if (input) localStorage.setItem(id, input.value); const el = document.getElementById("lock_" + id); if(!el) return; el.dataset.locked = 1; el.className = "icon-lock"; } // unlock option function unlock(id) { localStorage.removeItem(id); const el = document.getElementById("lock_" + id); if(!el) return; el.dataset.locked = 0; el.className = "icon-lock-open"; } // check if option is locked function locked(id) { const lockEl = document.getElementById("lock_" + id); return lockEl.dataset.locked == 1; } // check if option is stored in localStorage function stored(option) { return localStorage.getItem(option); } // apply drop-down menu option. If the value is not in options, add it function applyOption(select, option) { const custom = !Array.from(select.options).some(o => o.value == option); if (custom) select.options.add(new Option(option, option)); select.value = option; } // 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 Trello = link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Trello"); 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"); alertMessage.innerHTML = ` Fantasy Map Generator (FMG) is an open-source application, it means the code is published an anyone can use it. In case of FMG is also means that you own all created maps and can use them as you wish, you can even sell them.
The development is supported by community, 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} and the ${QAA}.
You can track the development process on ${Trello}.
Links: