nc-planner/scripts/planner.js

310 lines
10 KiB
JavaScript

import { 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 { deleteSolverMetadata, setSolverMetadata } from "./solver.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 defaultMultiblockSize = [9, 7, 9]; // width (screen x), height (xy grids), depth (screen y)
const defaultMultiblockType = "nuclearcraft:overhaul_sfr";
const maxMultiblockSize = {
["nuclearcraft:overhaul_sfr"]: [26, 26, 26],
}
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 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();
item.style.setProperty("--width", multiblockSize[0]);
item.dataset.y = y.toString(10);
if (y === 0 || y === multiblockSize[1] - 1) {
item.dataset.casing = "";
}
const grid = new DomList(item);
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();
cell.dataset.x = x.toString(10);
cell.dataset.y = y.toString(10);
cell.dataset.z = z.toString(10);
cell.dataset.tile = texture.toString(10);
if (x === 0 || x === multiblockSize[0] - 1
|| y === 0 || y === multiblockSize[1] - 1
|| z === 0 || z === multiblockSize[2] - 1) {
cell.dataset.casing = "";
}
cell.appendChild(makeAtlasImage(texture));
cell.addEventListener("mouseenter", cellHoverEvent);
cell.addEventListener("mouseleave", cellHoverEvent);
cell.addEventListener("mousedown", cellClickEvent);
cell.addEventListener("mouseenter", cellClickEvent);
cell.addEventListener("mouseleave", cellClickEvent);
cell.addEventListener("contextmenu", cellClickEvent);
grid.addItem(cell);
}
}
list.addItem(item);
}
}
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";
deleteSolverMetadata(e.target);
} 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) {
setSolverMetadata(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;