import { getAtlasIndexFromMetadata, getAtlasMetadata, makeAtlasImage } from "./atlas.js"; import { DomList } from "./dom-list.js"; import { getElements } from "./dom-utils.js"; import { emitEvent, listenTo } from "./events.js"; import { initialize3dArray } from "./js-utils.js"; import { recall, save } from "./storage.js"; import { registerLoadCallback } from "./main.js"; import { getBlockPickerActiveItem, getBlockPickerRecipe } from "./block-picker.js"; import { clearCellMetadata, getMetadata, setCellMetadata } from "./metadata.js"; import { retainTooltipHover } from "./tooltip.js"; import { checkEquivalence, lookupFromMetadata } from "./ncpf-utils.js"; const zoomOptions = [ 0.5, 1, 2, 3, 4, 5 ]; const zoomDefault = 2; const zoomStorageKey = "zoom-size"; const casingStorageKey = "casing"; const casingDefault = false; // hidden by default const recipeOverlayStorageKey = "recipeOverlay"; const recipeOverlayDefault = true; // shown by default const defaultMultiblockSize = [9, 7, 9]; // width (screen x), height (xy grids), depth (screen y) const defaultMultiblockType = "nuclearcraft:overhaul_sfr"; export const maxMultiblockSize = { ["nuclearcraft:overhaul_sfr"]: [26, 26, 26], } export const minMultiblockSize = { ["nuclearcraft:overhaul_sfr"]: [3, 3, 3], } let suppressHover = false; let multiblockSize = [...defaultMultiblockSize]; let multiblockType = defaultMultiblockType; registerLoadCallback(() => { listenTo("config:end", buildPlanner); const casingShow = recall(casingStorageKey, casingDefault); document.body.dataset.casing = casingShow ? "show" : "hide"; getElements("toggle", "planner:toggle-casing") .forEach(e => e.dataset.toggled = casingShow.toString()); listenTo("planner:toggle-casing", e => { save(casingStorageKey, e.detail.state); document.body.dataset.casing = e.detail.state ? "show" : "hide"; }); const recipeOverlayShow = recall(recipeOverlayStorageKey, recipeOverlayDefault); document.body.dataset.recipeOverlay = recipeOverlayShow ? "show" : "hide"; getElements("toggle", "planner:toggle-block-recipe-overlay") .forEach(e => e.dataset.toggled = recipeOverlayShow.toString()); listenTo("planner:toggle-block-recipe-overlay", e => { save(recipeOverlayStorageKey, e.detail.state); document.body.dataset.recipeOverlay = e.detail.state ? "show" : "hide"; }); const zoomLevel = recall(zoomStorageKey, zoomDefault); document.getElementById("planner").style .setProperty("--scale-factor", zoomLevel); document.querySelectorAll('select[data-input="planner:zoom"]').forEach(elem => { for (const op of zoomOptions) { const option = document.createElement("option"); option.value = op.toString(10); option.innerText = `${op * 100}%`; option.selected = zoomLevel === op; elem.appendChild(option); } }); listenTo("planner:zoom:change", e => { const value = Number(e.detail.currentTarget.value); if (zoomOptions.includes(value)) { emitEvent("planner:zoom:set", value); } else { console.warn("bad planner zoom value", e); } }); listenTo(["planner:zoom:+", "planner:zoom:-"], e => { const zoomIn = e.type === "ncp:planner:zoom:+"; const currentScale = Number(document.getElementById("planner").style .getPropertyValue("--scale-factor") ?? zoomDefault); const currentIndex = zoomOptions.indexOf(currentScale); if (zoomIn && currentIndex < zoomOptions.length - 1) { emitEvent("planner:zoom:set", zoomOptions[currentIndex + 1]); } if (!zoomIn && currentIndex > 0) { emitEvent("planner:zoom:set", zoomOptions[currentIndex - 1]); } }); listenTo("planner:zoom:set", e => { const scale = e.detail; const scaleIndex = zoomOptions.indexOf(scale); document.getElementById("planner").style .setProperty("--scale-factor", scale.toString(10)); document.querySelectorAll('select[data-input="planner:zoom"]') .forEach(elem => elem.value = scale); getElements("button", "planner:zoom:+") .forEach(e => e.disabled = scaleIndex >= zoomOptions.length - 1); getElements("button", "planner:zoom:-") .forEach(e => e.disabled = scaleIndex <= 0); save(zoomStorageKey, scale); }); listenTo("block-picker:selection", clearSelectedCells); listenTo("dev:suppress-hover", () => { suppressHover = !suppressHover; console.log("Suppressing hover:", suppressHover); }); }); function buildPlanner(textures) { // handle case when 1st argument is event if (!Array.isArray(textures)) textures = undefined; const planner = document.getElementById("planner"); const list = new DomList(planner); list.clear(); for (let y = 0; y < multiblockSize[1]; y++) { const item = list.makeItem(); createPlannerPlane(item, y, textures); list.addItem(item); } } export function createPlannerPlane(dom, y, textures) { dom.style.setProperty("--width", multiblockSize[0]); dom.dataset.y = y.toString(); if (y === 0 || y === multiblockSize[1] - 1) { dom.dataset.casing = ""; } dom.addEventListener("mousemove", retainTooltipHover); const grid = new DomList(dom); grid.clear(); for (let z = 0; z < multiblockSize[2]; z++) { for (let x = 0; x < multiblockSize[0]; x++) { const texture = textures ? textures[x][y][z] : -1; if (textures && !texture) { console.log(x, y, z, textures); } const cell = grid.makeItem(); createPlannerCell(cell, x, y, z, texture); grid.addItem(cell); } } } export function createPlannerCell(dom, x, y, z, texture) { dom.dataset.x = x.toString(); dom.dataset.y = y.toString(); dom.dataset.z = z.toString(); dom.dataset.tile = texture.toString(); if (x === 0 || x === multiblockSize[0] - 1 || y === 0 || y === multiblockSize[1] - 1 || z === 0 || z === multiblockSize[2] - 1) { dom.dataset.casing = ""; } dom.dataset.tooltip = "auto"; dom.appendChild(makeAtlasImage(texture)); dom.addEventListener("mouseenter", cellHoverEvent); dom.addEventListener("mouseleave", cellHoverEvent); dom.addEventListener("mousedown", cellClickEvent); dom.addEventListener("mouseenter", cellClickEvent); dom.addEventListener("mouseleave", cellClickEvent); dom.addEventListener("contextmenu", cellClickEvent); } function cellHoverEvent(e) { if (suppressHover) return; if (getBlockPickerActiveItem() === -1) return; if (document.body.dataset.resizing) return; const cells = document.querySelectorAll("#planner [data-x]"); cells.forEach(c => { delete c.dataset.highlight; delete c.dataset.highlightDistance; }); if (e.type === "mouseenter") { cells.forEach(c => { if (c.dataset.y === e.target.dataset.y && c.dataset.z === e.target.dataset.z) { c.dataset.highlight = "x"; c.dataset.highlightDistance = Math.abs(c.dataset.x - e.target.dataset.x).toString(10); } if (c.dataset.x === e.target.dataset.x && c.dataset.z === e.target.dataset.z) { c.dataset.highlight = "y"; c.dataset.highlightDistance = Math.abs(c.dataset.y - e.target.dataset.y).toString(10); } if (c.dataset.x === e.target.dataset.x && c.dataset.y === e.target.dataset.y) { c.dataset.highlight = "z"; c.dataset.highlightDistance = Math.abs(c.dataset.z - e.target.dataset.z).toString(10); } if (c === e.target) { delete c.dataset.highlight; } }); } } function cellClickEvent(e) { if (e.type === "contextmenu") { e.preventDefault(); return; } if (document.body.dataset.resizing) { return; } if (e.buttons !== 1 && e.buttons !== 2 && e.type !== "mouseup") { return; } const activeItem = getBlockPickerActiveItem(); if (activeItem === -1) { cellSelectEvent(e); return; } if (e.type === "mousedown") { window.addEventListener("mouseup", cellClickEvent); emitEvent("planner:drag:start"); clearSelectedCells(); } if (e.type === "mouseup") { window.removeEventListener("mouseup", cellClickEvent); emitEvent("planner:drag:end"); const cells = document.querySelectorAll("#planner [data-x]"); cells.forEach(c => { if (c.dataset.preview) { c.dataset.tile = c.dataset.preview; emitEvent("planner:cell:change", c); delete c.dataset.preview; } if (c.dataset.previewRemove) { delete c.dataset.previewRemove; c.replaceChildren(makeAtlasImage(-1)); c.dataset.tile = (-1).toString(10); emitEvent("planner:cell:change", c); } }); return; } if (e.buttons === 2) { // remove if (e.target.tile === "-1") return; e.target.dataset.previewRemove = "yes"; clearCellMetadata(e.target, "block_recipe"); } else if (e.buttons === 1) { // add if (e.target.dataset.preview === activeItem.toString(10)) return; e.target.dataset.preview = activeItem.toString(10); e.target.replaceChildren(makeAtlasImage(activeItem)); const pickerRecipe = getBlockPickerRecipe(); if (pickerRecipe) { setCellMetadata(e.target, "block_recipe", pickerRecipe); } } } function cellSelectEvent(e) { if (e.type === "mousedown" && !e.ctrlKey && e.buttons !== 2) { clearSelectedCells(); } if (e.buttons === 2) { // remove const wasSelected = "selected" in e.target.dataset; delete e.target.dataset.selected; if (wasSelected) { emitEvent("planner:selection:removed", e.target); } } else if (e.buttons === 1) { // add const wasSelected = "selected" in e.target.dataset; e.target.dataset.selected = ""; if (!wasSelected) { emitEvent("planner:selection:added", e.target); } } } function clearSelectedCells() { getSelectedCells() .forEach(cell => { delete cell.dataset.selected; emitEvent("planner:selection:removed", cell); }); } export function getSelectedCells() { return [...document.querySelectorAll("#planner [data-selected]")]; } export function clearPlanner(size = null, type = null) { multiblockSize = size ?? [...defaultMultiblockSize]; multiblockType = type ?? defaultMultiblockType; buildPlanner(); } export function loadPlanner(size, type, textures) { multiblockSize = size; multiblockType = type; buildPlanner(textures); } export function readPlannerCellData() { const planner = document.getElementById("planner"); const tiles = initialize3dArray(...multiblockSize, -1); const grids = [...planner.children].filter(e => e.dataset.y); for (let y = 0; y < multiblockSize[1]; y++) { const grid = grids[y]; const gridTiles = [...grid.children].filter(e => e.dataset.tile); for (let z = 0; z < multiblockSize[2]; z++) { for (let x = 0; x < multiblockSize[0]; x++) { tiles[x][y][z] = { ...gridTiles[x + z * multiblockSize[0]].dataset }; } } } return tiles; } export function getPlannerDom() { return document.getElementById("planner"); } export function getPlannerCellsDom() { return document.querySelectorAll("#planner [data-tile]"); } export function getPlannerCellDom(x, y, z) { return document.querySelector(`#planner [data-x="${x}"][data-y="${y}"][data-z="${z}"]`); } export const getPlannerMultiblockType = () => multiblockType; export const getPlannerMultiblockSize = () => multiblockSize; export function* getCellTooltip(cell) { if (cell.dataset.tile === "-1") { return; } const metadata = getAtlasMetadata(cell.dataset.tile) const data = lookupFromMetadata(metadata); const solverMetadata = getMetadata(cell); yield data.modules["plannerator:display_name"].display_name; if (solverMetadata) { if (solverMetadata.block_recipe) { const recipeList = data.modules["ncpf:block_recipes"].recipes; const recipe = recipeList.find(r => checkEquivalence(r, solverMetadata.block_recipe)); yield { type: "item", name: recipe.modules["plannerator:display_name"].display_name, atlasIndex: getAtlasIndexFromMetadata(recipe, multiblockType, "ncpf:block_recipes"), } } } yield { type: "italics", text: metadata.configurationAddon.name, }; }