import * as d3 from "d3";
import {heightmapTemplates} from "config/heightmap-templates";
import {precreatedHeightmaps} from "config/precreated-heightmaps";
import {lock, locked} from "scripts/options/lock";
import {clearMainTip, tip} from "scripts/tooltips";
import {last} from "utils/arrayUtils";
import {applyDropdownOption} from "utils/nodeUtils";
import {minmax, rn} from "utils/numberUtils";
import {gauss, P, rand, rw} from "utils/probabilityUtils";
import {byId, stored} from "utils/shorthands";
import {regenerateMap} from "scripts/generation/generation";
import {fitScaleBar} from "modules/measurers";
import {openDialog} from "dialogs";
import {closeDialogs} from "dialogs/utils";
import {quickSave, saveToDropbox, dowloadMap} from "modules/io/save.js";
$("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"});
$("#exitCustomization").draggable({handle: "div"});
$("#mapLayers").disableSelection();
// remove glow if tip is aknowledged
if (stored("disable_click_arrow_tooltip")) {
clearMainTip();
optionsTrigger.classList.remove("glow");
}
// Show options pane on trigger click
function showOptions(event) {
if (!stored("disable_click_arrow_tooltip")) {
clearMainTip();
localStorage.setItem("disable_click_arrow_tooltip", true);
optionsTrigger.classList.remove("glow");
}
regenerate.style.display = "none";
byId("options").style.display = "block";
optionsTrigger.style.display = "none";
if (event) event.stopPropagation();
}
// Hide options pane on trigger click
export function hideOptions(event) {
byId("options").style.display = "none";
optionsTrigger.style.display = "block";
if (event) event.stopPropagation();
}
// To toggle options on hotkey press
export function toggleOptions(event) {
if (byId("options").style.display === "none") showOptions(event);
else hideOptions(event);
}
// Toggle "New Map!" pane on hover
optionsTrigger.on("mouseenter", function () {
if (optionsTrigger.classList.contains("glow")) return;
if (byId("options").style.display === "none") regenerate.style.display = "block";
});
collapsible.on("mouseleave", function () {
regenerate.style.display = "none";
});
// Activate options tab on click
document
.getElementById("options")
.querySelector("div.tab")
.on("click", function (event) {
if (event.target.tagName !== "BUTTON") return;
const id = event.target.id;
const active = byId("options").querySelector(".tab > button.active");
if (active && id === active.id) return; // already active tab is clicked
if (active) active.classList.remove("active");
byId(id).classList.add("active");
document
.getElementById("options")
.querySelectorAll(".tabcontent")
.forEach(e => (e.style.display = "none"));
if (id === "layersTab") layersContent.style.display = "block";
else if (id === "styleTab") styleContent.style.display = "block";
else if (id === "optionsTab") optionsContent.style.display = "block";
else if (id === "toolsTab")
customization === 1 ? (customizationMenu.style.display = "block") : (toolsContent.style.display = "block");
else if (id === "aboutTab") aboutContent.style.display = "block";
});
// show popup with a list of Patreon supportes (updated manually)
async function showSupporters() {
const {supporters} = await import("../dynamic/supporters.js");
alertMessage.innerHTML =
"
" + supporters.map(n => `
${n}
`).join("") + "
";
$("#alert").dialog({
resizable: false,
title: "Patreon Supporters",
width: "54vw",
position: {my: "center", at: "center", of: "svg"}
});
}
// on any option or dialog change
byId("options").on("change", storeValueIfRequired);
byId("dialogs").on("change", storeValueIfRequired);
byId("options").on("input", updateOutputToFollowInput);
byId("dialogs").on("input", updateOutputToFollowInput);
function storeValueIfRequired(ev) {
if (ev.target.dataset.stored) lock(ev.target.dataset.stored);
}
function updateOutputToFollowInput(ev) {
const id = ev.target.id;
const value = ev.target.value;
// specific cases
if (id === "manorsInput") return (manorsOutput.value = value == 1000 ? "auto" : value);
// generic case
if (id.slice(-5) === "Input") {
const output = byId(id.slice(0, -5) + "Output");
if (output) output.value = value;
} else if (id.slice(-6) === "Output") {
const input = byId(id.slice(0, -6) + "Input");
if (input) input.value = value;
}
}
// Option listeners
const optionsContent = byId("optionsContent");
optionsContent.on("input", function (event) {
const id = event.target.id;
const value = event.target.value;
if (id === "mapWidthInput" || id === "mapHeightInput") mapSizeInputChange();
else if (id === "pointsInput") changeCellsDensity(+value);
else if (id === "culturesSet") changeCultureSet();
else if (id === "regionsInput" || id === "regionsOutput") changeStatesNumber(value);
else if (id === "emblemShape") changeEmblemShape(value);
else if (id === "tooltipSizeInput" || id === "tooltipSizeOutput") changeTooltipSize(value);
else if (id === "themeHueInput") changeThemeHue(value);
else if (id === "themeColorInput") changeDialogsTheme(themeColorInput.value, transparencyInput.value);
else if (id === "transparencyInput") changeDialogsTheme(themeColorInput.value, value);
});
optionsContent.on("change", function (event) {
const id = event.target.id;
const value = event.target.value;
if (id === "zoomExtentMin" || id === "zoomExtentMax") changeZoomExtent(value);
else if (id === "optionsSeed") generateMapWithSeed("seed change");
else if (id === "uiSizeInput" || id === "uiSizeOutput") changeUIsize(value);
else if (id === "shapeRendering") setRendering(value);
else if (id === "yearInput") changeYear();
else if (id === "eraInput") changeEra();
else if (id === "stateLabelsModeInput") options.stateLabelsMode = value;
});
optionsContent.on("click", function (event) {
const id = event.target.id;
if (id === "toggleFullscreen") toggleFullscreen();
else if (id === "optionsMapHistory") showSeedHistoryDialog();
else if (id === "optionsCopySeed") copyMapURL();
else if (id === "optionsEraRegenerate") regenerateEra();
else if (id === "templateInputContainer") openDialog("heightmapSelection");
else if (id === "zoomExtentDefault") restoreDefaultZoomExtent();
else if (id === "translateExtent") toggleTranslateExtent(event.target);
else if (id === "speakerTest") testSpeaker();
else if (id === "themeColorRestore") restoreDefaultThemeColor();
});
function mapSizeInputChange() {
changeMapSize();
localStorage.setItem("mapWidth", mapWidthInput.value);
localStorage.setItem("mapHeight", mapHeightInput.value);
}
export function addResizeListener() {
// fit full-screen map if window is resized
window.addEventListener("resize", function () {
if (stored("mapWidth") && stored("mapHeight")) return;
const {innerWidth, innerHeight} = window;
byId("mapWidthInput").value = innerWidth;
byId("mapHeightInput").value = innerHeight;
changeMapSize();
});
}
// change svg size on manual size change or window resize, do not change graph size
function changeMapSize() {
svgWidth = Math.min(+mapWidthInput.value, window.innerWidth);
svgHeight = Math.min(+mapHeightInput.value, window.innerHeight);
svg.attr("width", svgWidth).attr("height", svgHeight);
const maxWidth = Math.max(+mapWidthInput.value, graphWidth);
const maxHeight = Math.max(+mapHeightInput.value, graphHeight);
Zoom.translateExtent([0, 0, maxWidth, maxHeight]);
landmass.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
fogging.selectAll("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
defs.select("mask#fog > rect").attr("width", maxWidth).attr("height", maxHeight);
texture.select("image").attr("width", maxWidth).attr("height", maxHeight);
fitScaleBar();
if (window.fitLegendBox) fitLegendBox();
}
// just apply canvas size that was already set
export function applyMapSize() {
const zoomMin = +zoomExtentMin.value;
const zoomMax = +zoomExtentMax.value;
graphWidth = +mapWidthInput.value;
graphHeight = +mapHeightInput.value;
svgWidth = Math.min(graphWidth, window.innerWidth);
svgHeight = Math.min(graphHeight, window.innerHeight);
svg.attr("width", svgWidth).attr("height", svgHeight);
Zoom.translateExtent([0, 0, graphWidth, graphHeight]);
Zoom.scaleExtent([zoomMin, zoomMax]);
Zoom.scaleTo(svg, zoomMin);
}
function toggleFullscreen() {
if (mapWidthInput.value != window.innerWidth || mapHeightInput.value != window.innerHeight) {
mapWidthInput.value = window.innerWidth;
mapHeightInput.value = window.innerHeight;
localStorage.removeItem("mapHeight");
localStorage.removeItem("mapWidth");
} else {
mapWidthInput.value = graphWidth;
mapHeightInput.value = graphHeight;
}
changeMapSize();
}
function toggleTranslateExtent(el) {
const on = !Number(el.dataset.on);
const extent = on
? [-graphWidth / 2, -graphHeight / 2, graphWidth * 1.5, graphHeight * 1.5]
: [0, 0, graphWidth, graphHeight];
Zoom.translateExtent(extent);
el.dataset.on = Number(on);
}
// add voice options
const voiceInterval = setInterval(function () {
const voices = speechSynthesis.getVoices();
if (voices.length) clearInterval(voiceInterval);
else return;
const select = byId("speakerVoice");
voices.forEach((voice, i) => {
select.options.add(new Option(voice.name, i, false));
});
if (stored("speakerVoice")) select.value = stored("speakerVoice");
// se voice to store
else select.value = voices.findIndex(voice => voice.lang === "en-US"); // or to first found English-US
}, 1000);
function testSpeaker() {
const text = `${mapName.value}, ${options.year} ${options.era}`;
const speaker = new SpeechSynthesisUtterance(text);
const voices = speechSynthesis.getVoices();
if (voices.length) {
const voiceId = +byId("speakerVoice").value;
speaker.voice = voices[voiceId];
}
speechSynthesis.speak(speaker);
}
function generateMapWithSeed() {
if (optionsSeed.value == seed) return tip("The current map already has this seed", false, "error");
regeneratePrompt();
}
function showSeedHistoryDialog() {
const lines = mapHistory.map((h, i) => {
const created = new Date(h.created).toLocaleTimeString();
const button = ``;
return `