import { atlases, makeAtlasImage } from "./atlas.js"; import { DomList } from "./dom-list.js"; import { emitEvent, listenTo } from "./events.js"; import { Filter } from "./filter.js"; import { createTooltip, tooltipDataToPlain } from "./tooltip.js"; import { lookupFromMetadata, resolveBlockName } from "./ncpf-utils.js"; import { addEventToButton } from "./dom-utils.js"; let suppressHover = false; let multiblockSize = [9, 7, 9]; // width (screen x), height (xy grids), depth (screen y) let multiblockType = "nuclearcraft:overhaul_sfr"; let activeItem = -1; function cellHoverEvent(e) { if (suppressHover) 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 (e.buttons !== 1 && e.buttons !== 2 && e.type !== "mouseup") { return; } if (e.buttons === 1 && activeItem === -1) return; if (e.type === "mousedown") { window.addEventListener("mouseup", cellClickEvent); emitEvent("planner:drag:start"); } 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"; return; } 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)); } if (e.target.dataset.preview === activeItem.toString(10)) return; e.target.dataset.preview = activeItem.toString(10); e.target.replaceChildren(makeAtlasImage(activeItem)); } function listClickEvent(e) { const items = document.querySelectorAll("#block-list [data-index]"); items.forEach(i => delete i.dataset.active); activeItem = Number(e.currentTarget.dataset.index); e.currentTarget.dataset.active = ""; } export function initializePlanner() { listenTo("block-picker:filter", buildBlockPicker); listenTo("config:end", buildBlockPicker); listenTo("config:end", buildPlanner); addEventToButton("show-casing", "click", e => { const wasChecked = e.currentTarget.dataset.toggled === "true"; const show = !wasChecked; e.currentTarget.dataset.toggled = show.toString(); const planner = document.querySelector("#planner"); planner.dataset.casing = show ? "show" : "hide"; }); document.addEventListener("keydown", e => { if (e.key === "H") { suppressHover = !suppressHover; } }); } function buildBlockPicker() { const filter = Filter.fromInput("block-filter"); const grid = new DomList(document.querySelector("#block-list")); grid.clear(); atlases.flatMap(a => a.metadata) .filter(m => m.configuration === multiblockType && m.configurationList === "blocks") .forEach(m => { const item = grid.makeItem(); item.dataset.index = m.index.toString(10); const tooltipData = [...getBlockTooltip(m)]; item.dataset.tooltip = createTooltip(tooltipData).toString(10); if (!filter.test(...tooltipDataToPlain(tooltipData))) { item.classList.add("hidden"); } item.appendChild(makeAtlasImage(m.index)); item.addEventListener("click", listClickEvent); grid.addItem(item); }); } function buildPlanner(textures) { // handle case when 1st argument is event if (!Array.isArray(textures)) textures = undefined; const planner = document.querySelector("#planner"); planner.dataset.casing = "hide"; 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]); 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); } } export function clearPlanner(size, type) { multiblockSize = size; multiblockType = type; buildPlanner(); } export function loadPlanner(size, type, textures) { multiblockSize = size; multiblockType = type; buildPlanner(textures); } function* getBlockTooltip(metadata) { const data = lookupFromMetadata(metadata); if (!data) { console.warn("failed to get data from metadata", metadata); return; } yield data.modules["plannerator:display_name"].display_name; let hasAdditionalData = false; if ("nuclearcraft:overhaul_sfr:heat_sink" in data.modules) { hasAdditionalData = true; const heatSink = data.modules["nuclearcraft:overhaul_sfr:heat_sink"]; yield "Heatsink"; if (heatSink.cooling) { yield `Cooling: ${heatSink.cooling} H/t`; } if (heatSink.rules) { for (const rule of heatSink.rules) { yield "Requires " + parseRuleAsTooltip(rule, { configurationHint: metadata.configuration }); } } } if ("nuclearcraft:overhaul_sfr:casing" in data.modules) { hasAdditionalData = true; yield "Casing"; } if ("nuclearcraft:overhaul_sfr:controller" in data.modules) { hasAdditionalData = true; yield "Controller"; } if ("nuclearcraft:overhaul_sfr:fuel_cell" in data.modules) { hasAdditionalData = true; yield "Fuel Cell"; } if ("nuclearcraft:overhaul_sfr:port" in data.modules) { hasAdditionalData = true; if (data.modules["nuclearcraft:overhaul_sfr:port"].output) { yield "Output Port"; } else { yield "Input Port"; } } if ("nuclearcraft:overhaul_sfr:irradiator" in data.modules) { hasAdditionalData = true; yield "Irradiator"; } if ("nuclearcraft:overhaul_sfr:conductor" in data.modules) { hasAdditionalData = true; yield "Conductor"; } if ("nuclearcraft:overhaul_sfr:moderator" in data.modules) { hasAdditionalData = true; const moderator = data.modules["nuclearcraft:overhaul_sfr:moderator"]; yield "Moderator"; yield `Flux: ${moderator.flux}`; yield `Efficiency: ${moderator.efficiency}`; } if ("nuclearcraft:overhaul_sfr:reflector" in data.modules) { hasAdditionalData = true; const reflector = data.modules["nuclearcraft:overhaul_sfr:reflector"]; yield "Reflector"; yield `Efficiency: ${reflector.efficiency}`; yield `Reflectivity: ${reflector.reflectivity}`; } if ("nuclearcraft:overhaul_sfr:neutron_shield" in data.modules) { hasAdditionalData = true; const shield = data.modules["nuclearcraft:overhaul_sfr:neutron_shield"]; yield "Neutron Shield"; yield `Efficiency: ${shield.efficiency}`; yield `Heat per Flux: ${shield.heat_per_flux}`; } if (!hasAdditionalData) { console.log("unknown block", data); } yield { type: "italics", text: metadata.configurationAddon.name, }; } function parseRuleAsTooltip(rule, props) { switch (rule.type) { case "and": return rule.rules.map(r => parseRuleAsTooltip(r, props)).join(" AND "); case "or": return rule.rules.map(r => parseRuleAsTooltip(r, props)).join(" OR "); case "between": { const block = resolveBlockName(rule.block, props); if (!block) { console.warn("could not find block %o", rule.block); return "???"; } if (rule.min === rule.max) { return `exactly ${rule.min} ${block}`; } else if (rule.max === 6) { return `at least ${rule.min} ${block}`; } else if (rule.min === 0) { return `no more than ${rule.max} ${block}`; } else { return `between ${rule.min} and ${rule.max} ${block}`; } } case "axial": { const block = resolveBlockName(rule.block, props); if (!block) { console.warn("could not find block %o", rule.block); return "???"; } if (rule.min === rule.max) { return `exactly ${rule.min} axial ${block}`; } else if (rule.max === 6) { return `at least ${rule.min} axial ${block}`; } else if (rule.min === 0) { return `no more than ${rule.max} axial ${block}`; } else { return `between ${rule.min} and ${rule.max} axial ${block}`; } } default: console.warn("unknown rule: %o", rule); return "???"; } }