Compare commits

..

No commits in common. "ea6813b415ec12da68278d5398652122f90519ca" and "b93cfafc5aeba2e7baeb59cdd5f3c28932c61997" have entirely different histories.

10 changed files with 137 additions and 354 deletions

View File

@ -2,7 +2,6 @@
<dictionary name="project"> <dictionary name="project">
<words> <words>
<w>ncpf</w> <w>ncpf</w>
<w>nuclearcraft</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,28 +7,11 @@
<script type="module" src="scripts/main.js"></script> <script type="module" src="scripts/main.js"></script>
</head> </head>
<body> <body>
<nav id="top-menu" class="menu-bar"> <nav id="top-menu">
<div data-section="File" data-alt="f"> <button>File</button>
<button data-alt="n" data-button="new-file">New</button> <button>Edit</button>
<button data-alt="o" data-button="open-file">Open</button> <button>Select</button>
<div data-section="Recent Files" data-alt="r"> <button>View</button>
<button disabled>-</button>
</div>
</div>
<div data-section="Edit" data-alt="e">
<button data-alt="u" data-button="undo">Undo</button>
<button data-alt="r" data-button="redo" disabled>Redo</button>
</div>
<div data-section="Select" data-alt="s">
<button data-alt="i">Invert</button>
</div>
<div data-section="View" data-alt="v">
<div data-section="Test" data-alt="e">
<div data-section="Test" data-alt="e">
<button disabled>-</button>
</div>
</div>
</div>
</nav> </nav>
<nav id="left-menu" data-state="planning"> <nav id="left-menu" data-state="planning">
<span data-resize="left-menu"></span> <span data-resize="left-menu"></span>

View File

@ -2,7 +2,8 @@ import { emitEvent } from "./events.js";
let atlasSize = 0; // in items let atlasSize = 0; // in items
let atlasResolution = 0; // in px let atlasResolution = 0; // in px
export let atlases = []; export let atlasBlobURL = "about:blank";
export let atlasMetadata = [];
async function getTextureSize(data) { async function getTextureSize(data) {
const texture = data.texture; const texture = data.texture;
@ -33,68 +34,54 @@ function putTextureOnCanvas(ctx, atlasSize, index, data) {
} }
export async function buildAtlas(data) { export async function buildAtlas(data) {
// TODO: atlas caching
emitEvent("config:process:atlas:start"); emitEvent("config:process:atlas:start");
const textures = [ const overhaulSFRBlocks = [
...Object.entries(data.configuration), ...data.configuration["nuclearcraft:overhaul_sfr"]?.blocks,
...data.addons.flatMap(a => Object.entries(a.configuration)), ...data.addons.flatMap(a => a.configuration["nuclearcraft:overhaul_sfr"]?.blocks)
] ]
.flatMap(([config, data]) => Object.entries(data) .filter(b => b && b.modules && b.modules["plannerator:texture"])
.map(([type, entries]) => [config, type, entries])) .map(b => (
.flatMap(([c, t, e]) => Array.isArray(e) {
? [[c, t, e]] ...b,
: t === "modules" && e["plannerator:global_elements"]
? [[c, "plannerator:global_elements", e["plannerator:global_elements"].elements]]
: [])
.flatMap(([c, t, l]) => l.map((v) => [c, t, v]))
.filter(([,, b]) => b && b.modules && b.modules["plannerator:texture"])
.map(([c, t, b]) => ({
configuration: c,
configurationList: t,
texture: b.modules["plannerator:texture"].texture, texture: b.modules["plannerator:texture"].texture,
...(Object.fromEntries(Object.entries(b).filter(([k]) => k !== "modules"))) }
})); ));
if (textures.length === 0) { if (overhaulSFRBlocks.length === 0) {
throw new Error("Configuration processing failed - no textured blocks found"); throw new Error("Configuration processing failed - no textured blocks found");
} }
await Promise.all(textures.map(getTextureSize)); await Promise.all(overhaulSFRBlocks.map(getTextureSize));
atlases = []; // TODO: handle configurations with unique sizes
let offset = 0; if (new Set(overhaulSFRBlocks.map(v => v.size.toString())).size > 1) {
for (const t of Object.values(Object.groupBy(textures, t => t.size.toString()))) { throw new Error("Configuration processing failed - unable to handle multi-sized textures yet");
if (t[0].size[0] !== t[0].size[1]) { }
if (overhaulSFRBlocks[0].size[0] !== overhaulSFRBlocks[0].size[1]) {
throw new Error("Configuration processing failed - textures must be square"); throw new Error("Configuration processing failed - textures must be square");
} }
const atlasEdgeSizeBlocks = 2 ** Math.ceil(Math.log2(Math.sqrt(t.length))); // TODO: handle sizes other than 16px
const atlasEdgeSize = atlasEdgeSizeBlocks * t[0].size[0]; if (overhaulSFRBlocks[0].size[0] !== 16) {
throw new Error("Configuration processing failed - textures must be 16px");
}
const atlasEdgeSizeBlocks = 2 ** Math.ceil(Math.log2(Math.sqrt(overhaulSFRBlocks.length)));
const atlasEdgeSize = atlasEdgeSizeBlocks * overhaulSFRBlocks[0].size[0];
const canvas = new OffscreenCanvas(atlasEdgeSize, atlasEdgeSize); const canvas = new OffscreenCanvas(atlasEdgeSize, atlasEdgeSize);
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
await Promise.all(t.map((b, i) => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, i, b))); await Promise.all(overhaulSFRBlocks.map((b, i) => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, i, b)));
t.forEach((m, i) => {
m.index = offset + i;
delete m.texture;
});
const blob = await canvas.convertToBlob({ type: "image/png" }); const blob = await canvas.convertToBlob({ type: "image/png" });
atlases.push({ atlasSize = atlasEdgeSizeBlocks;
offset, atlasResolution = atlasEdgeSize;
url: URL.createObjectURL(blob), atlasBlobURL = URL.createObjectURL(blob);
metadata: t, atlasMetadata = overhaulSFRBlocks;
size: atlasEdgeSizeBlocks,
resolution: atlasEdgeSize,
length: t.length,
});
offset += t.length;
}
console.log(atlases); console.log("Atlas blob: %s", atlasBlobURL);
emitEvent("config:process:atlas:end"); emitEvent("config:process:atlas:end");
} }
@ -109,44 +96,21 @@ export function makeAtlasImage(index) {
return div; return div;
} }
const originalIndex = index; const atlasIndexX = index % atlasSize;
for (const atlas of atlases) { const atlasIndexY = (index - atlasIndexX) / atlasSize;
if (index < atlas.length) { const size = atlasMetadata[index].size;
const atlasIndexX = index % atlas.size;
const atlasIndexY = (index - atlasIndexX) / atlas.size;
const size = atlas.metadata[index].size;
const x = atlasIndexX * size[0]; const x = atlasIndexX * size[0];
const y = atlasIndexY * size[1]; const y = atlasIndexY * size[1];
const div = document.createElement("div"); const div = document.createElement("div");
div.draggable = false; div.draggable = false;
div.dataset.originalIndex = originalIndex;
div.classList.add("atlas-texture"); div.classList.add("atlas-texture");
div.style.backgroundImage = `url(${atlas.url})`; div.style.backgroundImage = `url(${atlasBlobURL})`;
div.style.setProperty("--atlas-resolution", `${atlas.resolution}px`); div.style.setProperty("--atlas-resolution", `${atlasResolution}px`);
div.style.setProperty("--raw-x", `${x}px`); div.style.setProperty("--raw-x", `${x}px`);
div.style.setProperty("--raw-y", `${y}px`); div.style.setProperty("--raw-y", `${y}px`);
div.style.setProperty("--size-x", `${size[0]}px`); div.style.setProperty("--size-x", `${size[0]}px`);
div.style.setProperty("--size-y", `${size[1]}px`); div.style.setProperty("--size-y", `${size[1]}px`);
return div; return div;
}
index -= atlas.length;
}
return null;
}
export function getAtlasMetadata(index) {
if (index < -1) return null;
if (index === -1) return {};
for (const atlas of atlases) {
if (index < atlas.length - 1) {
return atlas.metadata[index];
}
index -= atlas.length;
}
return null;
} }

View File

@ -36,11 +36,6 @@ export class Filter {
test(...data) { test(...data) {
for (const datum of data) { for (const datum of data) {
if (typeof datum !== "string") {
console.log("cannot test %o", datum);
continue;
}
const normalized = datum.trim().toLocaleLowerCase().trim(); const normalized = datum.trim().toLocaleLowerCase().trim();
for (const n of this.negate) { for (const n of this.negate) {
if (normalized.includes(n)) { if (normalized.includes(n)) {

View File

@ -5,7 +5,6 @@ import { initializePlanner } from "./planner.js";
import { initializeMenuResizing } from "./resize-menus.js"; import { initializeMenuResizing } from "./resize-menus.js";
import { getState, setState, STATE } from "./state.js"; import { getState, setState, STATE } from "./state.js";
import { initializeTooltips } from "./tooltip.js"; import { initializeTooltips } from "./tooltip.js";
import { initializeMenuBar } from "./menu-bar.js";
window.addEventListener("load", () => { window.addEventListener("load", () => {
emitEvent("setup-start"); emitEvent("setup-start");
@ -38,9 +37,8 @@ window.addEventListener("load", () => {
initializePlanner(); initializePlanner();
initializeMenuResizing(); initializeMenuResizing();
initializeTooltips(); initializeTooltips();
initializeMenuBar();
emitEvent("setup-end"); emitEvent("setup-end");
void loadConfiguration("addons"); void loadConfiguration("default");
}); });

View File

@ -1 +0,0 @@
export function initializeMenuBar() {}

View File

@ -1,73 +0,0 @@
import { configuration } from "./configuration.js";
function findInPossible(entryList, entry) {
switch (entry.type) {
case "legacy_block":
return entryList.find(v => v.name === entry.name
&& v.metadata === entry.metadata
&& (!entry.blockstate || JSON.stringify(v.blockstate) === JSON.stringify(entry.blockstate)));
case "oredict":
return entryList.find(v => v.oredict === entry.oredict);
default:
console.warn("unknown type", entry.type);
}
return null;
}
export function resolveBlockName(block, { configurationHint }) {
if (block.type === "module") {
switch (block.name) {
case "nuclearcraft:overhaul_sfr:fuel_cell":
return "Fuel Cell";
case "nuclearcraft:overhaul_sfr:moderator":
return "Moderator";
case "nuclearcraft:overhaul_sfr:casing":
return "Casing";
case "nuclearcraft:overhaul_sfr:reflector":
return "Reflector";
case "nuclearcraft:overhaul_sfr:neutron_shield":
return "Neutron Shield";
case "nuclearcraft:overhaul_sfr:irradiator":
return "Irradiator";
default:
console.warn("unknown module", block);
return null;
}
}
const possibleBlocks = [
...Object.entries(configuration.configuration),
...configuration.addons.flatMap(a => Object.entries(a.configuration)),
]
.filter(([k, v]) => (!configurationHint || k === configurationHint) && "blocks" in v)
.flatMap(([, v]) => v.blocks);
const entry = findInPossible(possibleBlocks, block);
if (!entry) return null;
return entry.modules["plannerator:display_name"].display_name;
}
export function lookupFromMetadata(metadata) {
if (!metadata.configuration || !metadata.configurationList || !metadata.type)
return null;
const possibleEntries = [
...Object.entries(configuration.configuration),
...configuration.addons.flatMap(a => Object.entries(a.configuration)),
]
.filter(([k, v]) => k === metadata.configuration && metadata.configurationList in v)
.flatMap(([, v]) => v[metadata.configurationList])
.filter(v => v.type === metadata.type);
return findInPossible(possibleEntries, metadata);
}

View File

@ -1,9 +1,8 @@
import { atlases, makeAtlasImage } from "./atlas.js"; import {atlasMetadata, makeAtlasImage} from "./atlas.js";
import { DomList } from "./dom-list.js"; import {DomList} from "./dom-list.js";
import { emitEvent, listenTo } from "./events.js"; import { emitEvent, listenTo } from "./events.js";
import { Filter } from "./filter.js"; import { Filter } from "./filter.js";
import { createTooltip } from "./tooltip.js"; import { createTooltip } from "./tooltip.js";
import { lookupFromMetadata, resolveBlockName } from "./ncpf-utils.js";
let suppressHover = false; let suppressHover = false;
let multiblockSize = [7, 7, 5]; // width (screen x), depth (screen y), height (xy grids) let multiblockSize = [7, 7, 5]; // width (screen x), depth (screen y), height (xy grids)
@ -104,7 +103,9 @@ function listClickEvent(e) {
} }
export function initializePlanner() { export function initializePlanner() {
listenTo("block-picker:filter", buildBlockPicker); listenTo("block-picker:filter", e => {
buildBlockPicker();
});
listenTo("config:end", buildBlockPicker); listenTo("config:end", buildBlockPicker);
@ -113,22 +114,78 @@ export function initializePlanner() {
function buildBlockPicker() { function buildBlockPicker() {
const filter = Filter.fromInput("block-filter"); const filter = Filter.fromInput("block-filter");
console.log(filter);
const grid = new DomList(document.querySelector("#block-list")); const grid = new DomList(document.querySelector("#block-list"));
grid.clear(); grid.clear();
const blocks = atlases.flatMap(a => a.metadata) atlasMetadata.forEach((m, i) => {
.filter(m => m.configuration === "nuclearcraft:overhaul_sfr" && m.configurationList === "blocks");
console.log(blocks);
blocks.forEach(m => {
const item = grid.makeItem(); const item = grid.makeItem();
item.dataset.index = m.index.toString(10); item.dataset.index = i.toString(10);
const tooltipData = [...getBlockTooltip(m)]; if (m.modules["plannerator:display_name"].display_name === "Lithium Heat Sink") {
item.dataset.tooltip = createTooltip(tooltipData).toString(10); console.log(m);
if (!filter.test(...tooltipData)) { 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"
}
]
}
}
item.dataset.tooltip = createTooltip([
m.modules["plannerator:display_name"].display_name,
...("nuclearcraft:overhaul_sfr:heat_sink" in m.modules ? [
"Heatsink",
`Cooling: ${m.modules["nuclearcraft:overhaul_sfr:heat_sink"].cooling ?? "???"} H/t`,
] : []),
]).toString(10);
if (!filter.test(m.modules["plannerator:display_name"].display_name)) {
item.classList.add("hidden"); item.classList.add("hidden");
} }
item.appendChild(makeAtlasImage(m.index)); item.appendChild(makeAtlasImage(i));
item.addEventListener("click", listClickEvent); item.addEventListener("click", listClickEvent);
grid.addItem(item); grid.addItem(item);
}); });
@ -169,140 +226,3 @@ function buildPlanner() {
list.addItem(item); 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;
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);
}
}
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 "???";
}
}