nc-planner/scripts/planner.js

296 lines
8.7 KiB
JavaScript

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 } from "./tooltip.js";
import { lookupFromMetadata, resolveBlockName } from "./ncpf-utils.js";
let suppressHover = false;
let multiblockSize = [7, 7, 5]; // width (screen x), depth (screen y), height (xy grids)
let activeItem = -1;
function cellHoverEvent(e) {
if (suppressHover) return;
const cells = document.querySelectorAll("#planner-list [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-list [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);
}
function buildBlockPicker() {
const filter = Filter.fromInput("block-filter");
const grid = new DomList(document.querySelector("#block-list"));
grid.clear();
const blocks = atlases.flatMap(a => a.metadata)
.filter(m => m.configuration === "nuclearcraft:overhaul_sfr" && m.configurationList === "blocks");
console.log(blocks);
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(...tooltipData)) {
item.classList.add("hidden");
}
item.appendChild(makeAtlasImage(m.index));
item.addEventListener("click", listClickEvent);
grid.addItem(item);
});
}
function buildPlanner() {
document.addEventListener("keydown", e => {
if (e.key === "H") {
suppressHover = !suppressHover;
}
});
const list = new DomList(document.querySelector("#planner-list"));
list.clear();
for (let z = 0; z < multiblockSize[2]; z++) {
const item = list.makeItem();
item.style.setProperty("--width", multiblockSize[1]);
const grid = new DomList(item);
grid.clear();
for (let y = 0; y < multiblockSize[1]; y++) {
for (let x = 0; x < multiblockSize[0]; x++) {
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 = (-1).toString(10);
cell.appendChild(makeAtlasImage(-1));
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* 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;
if ("nuclearcraft:overhaul_sfr:heat_sink" in data.modules) {
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 });
}
}
}
}
function parseRuleAsTooltip(rule, props) {
void {
"cooling": 130,
"rules": [
{
"rules": [
{
"rules": [
{
"min": 2,
"max": 2,
"block": {
"metadata": 1,
"name": "nuclearcraft:solid_fission_sink2",
"type": "legacy_block",
"blockstate": {
"type": "lead"
}
},
"type": "between"
},
{
"min": 1,
"max": 1,
"block": {
"metadata": 1,
"name": "nuclearcraft:solid_fission_sink2",
"type": "legacy_block",
"blockstate": {
"type": "lead"
}
},
"type": "axial"
}
],
"type": "and"
},
{
"min": 1,
"max": 6,
"block": {
"name": "nuclearcraft:overhaul_sfr:casing",
"type": "module"
},
"type": "between"
}
],
"type": "and"
}
]
}
// Requires exactly 2 lead heat sink AND exactly 1 pairs of axial lead heat sink AND at least 1 casing
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}`;
}
return "";
}
default:
console.warn("unknown rule: %o", rule);
return "???";
}
}