allow selecting block recipes
This commit is contained in:
parent
deea8abdd4
commit
481e700087
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,15 @@
|
|||
import { emitEvent } from "./events.js";
|
||||
import { checkEquivalence } from "./ncpf-utils.js";
|
||||
import { registerLoadCallback } from "./main.js";
|
||||
|
||||
const defaultSize = 16; // px
|
||||
|
||||
export let atlases = [];
|
||||
|
||||
registerLoadCallback(() => {
|
||||
document.body.style.setProperty("--default-size", `${defaultSize}px`);
|
||||
});
|
||||
|
||||
async function getTextureSize(data) {
|
||||
const texture = data.texture;
|
||||
if (!texture) {
|
||||
|
|
@ -22,8 +29,8 @@ async function getTextureSize(data) {
|
|||
|
||||
function putTextureOnCanvas(ctx, atlasSize, data) {
|
||||
if (!data.duplicate) {
|
||||
const atlasIndexX = data.index % atlasSize;
|
||||
const atlasIndexY = (data.index - atlasIndexX) / atlasSize;
|
||||
const atlasIndexX = data.textureIndex % atlasSize;
|
||||
const atlasIndexY = (data.textureIndex - atlasIndexX) / atlasSize;
|
||||
const x = atlasIndexX * data.size[0];
|
||||
const y = atlasIndexY * data.size[1];
|
||||
ctx.drawImage(data.image, x, y);
|
||||
|
|
@ -94,13 +101,14 @@ export async function buildAtlas(data) {
|
|||
}
|
||||
|
||||
const dedupTextureMap = new Map();
|
||||
t.forEach(texture => {
|
||||
t.forEach((texture, i) => {
|
||||
texture.index = i;
|
||||
if (dedupTextureMap.has(texture.texture)) {
|
||||
texture.index = dedupTextureMap.get(texture.texture)
|
||||
texture.textureIndex = dedupTextureMap.get(texture.texture)
|
||||
texture.duplicate = true;
|
||||
} else {
|
||||
texture.index = dedupTextureMap.size;
|
||||
dedupTextureMap.set(texture.texture, texture.index);
|
||||
texture.textureIndex = dedupTextureMap.size;
|
||||
dedupTextureMap.set(texture.texture, texture.textureIndex);
|
||||
texture.duplicate = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -143,9 +151,10 @@ export function makeAtlasImage(index) {
|
|||
const originalIndex = index;
|
||||
for (const atlas of atlases) {
|
||||
if (index < atlas.length) {
|
||||
const atlasIndexX = index % atlas.size;
|
||||
const atlasIndexY = (index - atlasIndexX) / atlas.size;
|
||||
const size = atlas.metadata[index].size;
|
||||
const meta = atlas.metadata[index];
|
||||
const atlasIndexX = meta.textureIndex % atlas.size;
|
||||
const atlasIndexY = (meta.textureIndex - atlasIndexX) / atlas.size;
|
||||
const size = meta.size;
|
||||
const x = atlasIndexX * size[0];
|
||||
const y = atlasIndexY * size[1];
|
||||
|
||||
|
|
@ -155,6 +164,7 @@ export function makeAtlasImage(index) {
|
|||
div.classList.add("atlas-texture");
|
||||
div.style.backgroundImage = `url(${atlas.url})`;
|
||||
div.style.setProperty("--atlas-resolution", `${atlas.resolution}px`);
|
||||
div.style.setProperty("--atlas-scale", `${defaultSize / size[0]}`);
|
||||
div.style.setProperty("--raw-x", `${x}px`);
|
||||
div.style.setProperty("--raw-y", `${y}px`);
|
||||
div.style.setProperty("--size-x", `${size[0]}px`);
|
||||
|
|
@ -183,13 +193,15 @@ export function getAtlasMetadata(index) {
|
|||
}
|
||||
|
||||
export function getAtlasIndexFromMetadata(metadata, configuration, configurationList = null) {
|
||||
let index = 0;
|
||||
for (const atlas of atlases) {
|
||||
for (const atlasItemMeta of atlas.metadata) {
|
||||
if (checkEquivalence(metadata, atlasItemMeta)
|
||||
&& atlasItemMeta.configuration === configuration
|
||||
&& (!configurationList || atlasItemMeta.configurationList === configurationList)) {
|
||||
return atlasItemMeta.index;
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@ export async function saveFile(name = undefined) {
|
|||
const minimizedBlock = minimizedBlocks[designIndex];
|
||||
let recipeIndex = -1;
|
||||
|
||||
if (!solverMetadata) return -1;
|
||||
|
||||
if (multiblockType === "nuclearcraft:overhaul_sfr") {
|
||||
if (!minimizedBlock.modules) {
|
||||
minimizedBlock.modules = {};
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@ window.addEventListener("load", () => {
|
|||
emitEvent("setup-end");
|
||||
|
||||
void loadConfiguration("default");
|
||||
// void loadConfiguration("fuel-test");
|
||||
|
||||
// awaitEvent("config:end").then(() => loadExampleFile("tiny reactor"));
|
||||
// awaitEvent("config:end").then(() => loadExampleFile("Pandora's Fission Reactor"));
|
||||
// awaitEvent("config:end").then(() => loadExampleFile("multi-reactor"));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { configuration } from "./configuration.js";
|
|||
import { objectKeepKeys } from "./js-utils.js";
|
||||
|
||||
export function checkEquivalence(a, b) {
|
||||
if (!a.type || !b.type || a.type !== b.type) return false;
|
||||
if (!a || !b || !a.type || !b.type || a.type !== b.type) return false;
|
||||
switch (a.type) {
|
||||
case "legacy_block":
|
||||
return a.name === b.name
|
||||
|
|
@ -13,6 +13,10 @@ export function checkEquivalence(a, b) {
|
|||
case "legacy_fluid":
|
||||
return a.name === b.name;
|
||||
|
||||
case "legacy_item":
|
||||
return a.name === b.name
|
||||
&& a.metadata === b.metadata;
|
||||
|
||||
case "oredict":
|
||||
return a.oredict === b.oredict;
|
||||
|
||||
|
|
@ -47,16 +51,30 @@ export function stripEntryToMinimums(entry) {
|
|||
throw new Error(`unknown entry type ${entry.type}`);
|
||||
}
|
||||
|
||||
export function findFullEntry(type, entry, { configurationHint } = {}) {
|
||||
export function findFullEntry(type, entry, { configurationHint, all = false } = {}) {
|
||||
const possibleEntries = [
|
||||
...Object.entries(configuration.configuration),
|
||||
...configuration.addons.flatMap(a => Object.entries(a.configuration)),
|
||||
]
|
||||
.filter(([k, v]) => (!configurationHint || k === configurationHint) && type in v)
|
||||
.flatMap(([, v]) => v[type]);
|
||||
if (all) {
|
||||
return possibleEntries.filter(other => checkEquivalence(entry, other));
|
||||
}
|
||||
return possibleEntries.find(other => checkEquivalence(entry, other));
|
||||
}
|
||||
|
||||
export function findFullEntryFromAtlasMetadata(metadata, options = {}) {
|
||||
return findFullEntry(
|
||||
metadata.configurationList,
|
||||
metadata,
|
||||
{
|
||||
configurationHint: metadata.configuration,
|
||||
...options,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function findFullEntryAnywhere(entry, { configurationHint } = {}) {
|
||||
const possibleEntries = [
|
||||
...Object.entries(configuration.configuration),
|
||||
|
|
@ -70,7 +88,12 @@ export function findFullEntryAnywhere(entry, { configurationHint } = {}) {
|
|||
: []),
|
||||
] : [[k, v]]))
|
||||
.flatMap(([t, v]) => v.map(e => [t, e]));
|
||||
const [ type, foundEntry ] = possibleEntries.find(([, other]) => checkEquivalence(entry, other));
|
||||
const findResult = possibleEntries.find(([, other]) => checkEquivalence(entry, other));
|
||||
if (!findResult) {
|
||||
console.log("did not find", entry, "in", possibleEntries);
|
||||
return null;
|
||||
}
|
||||
const [ type, foundEntry ] = findResult;
|
||||
return { type, entry: foundEntry };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,14 @@ import { configuration } from "./configuration.js";
|
|||
import { getPlannerDom, getPlannerMultiblockType, getSelectedCells } from "./planner.js";
|
||||
import { DomList } from "./dom-list.js";
|
||||
import { getAtlasIndexFromMetadata, getAtlasMetadata, makeAtlasImage } from "./atlas.js";
|
||||
import { getSolverMetadata, setSolverMetadata } from "./solver.js";
|
||||
import { findFullEntry, findFullEntryAnywhere, lookupFromMetadata } from "./ncpf-utils.js";
|
||||
import { deleteSolverMetadata, getSolverMetadata, setSolverMetadata } from "./solver.js";
|
||||
import {
|
||||
checkEquivalence,
|
||||
findFullEntry,
|
||||
findFullEntryAnywhere,
|
||||
findFullEntryFromAtlasMetadata,
|
||||
lookupFromMetadata, stripEntryToMinimums
|
||||
} from "./ncpf-utils.js";
|
||||
import { createTooltip, setTooltip, tooltipDataToPlain } from "./tooltip.js";
|
||||
import { Filter } from "./filter.js";
|
||||
import { debounce } from "./dom-utils.js";
|
||||
|
|
@ -28,8 +34,15 @@ registerLoadCallback(() => {
|
|||
listenTo([ "planner:selection:removed", "planner:selection:added"], rescanSelectionRecipes);
|
||||
});
|
||||
|
||||
function initializeRecipePicker(params) {
|
||||
const { parent, title, entryListType, multiblockType, recipes, currentSelection, updateSelection } = params;
|
||||
function initializeRecipePicker({
|
||||
parent,
|
||||
title,
|
||||
entryListType,
|
||||
multiblockType,
|
||||
recipes,
|
||||
currentSelection,
|
||||
updateSelection,
|
||||
}) {
|
||||
|
||||
parent.querySelectorAll('[data-text="recipe:type"]')
|
||||
.forEach(e => e.innerText = title);
|
||||
|
|
@ -37,7 +50,7 @@ function initializeRecipePicker(params) {
|
|||
function updateSelectedRecipe(selectedRecipe) {
|
||||
if (!selectedRecipe) {
|
||||
parent.querySelectorAll('[data-text="recipe:selected"]')
|
||||
.forEach(e => e.innerText = "None");
|
||||
.forEach(e => e.innerText = "Unknown");
|
||||
parent.querySelectorAll('[data-atlas="recipe:selected"]')
|
||||
.forEach(e => e.replaceChildren(makeAtlasImage(-1)));
|
||||
return;
|
||||
|
|
@ -174,20 +187,55 @@ function rescanSelectionRecipes() {
|
|||
const multiblockType = getPlannerMultiblockType();
|
||||
|
||||
const tileMetadata = getAtlasMetadata(selectedCells[0].dataset.tile);
|
||||
const tile = findFullEntry("blocks", tileMetadata, { configurationHint: multiblockType });
|
||||
const tiles = findFullEntryFromAtlasMetadata(tileMetadata, { all: true });
|
||||
|
||||
if (!tile.modules || !tile.modules["ncpf:block_recipes"]) {
|
||||
console.log(tiles);
|
||||
|
||||
if (!tiles[0] || !tiles[0].modules || !tiles[0].modules["ncpf:block_recipes"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cellRecipes = selectedCells.map(v => getSolverMetadata(v)?.block_recipe);
|
||||
const recipeSet = cellRecipes.some(v => v);
|
||||
const recipeAllSame = cellRecipes.every(v => v === cellRecipes[0] || checkEquivalence(v, cellRecipes[0]));
|
||||
console.log(">", cellRecipes, recipeSet, recipeAllSame);
|
||||
|
||||
const recipes = tiles.flatMap(t => t.modules["ncpf:block_recipes"].recipes);
|
||||
|
||||
recipes.unshift({
|
||||
modules: {
|
||||
["plannerator:display_name"]: {
|
||||
display_name: "Unfiltered",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let currentRecipe = null
|
||||
if (recipeAllSame) {
|
||||
if (recipeSet) {
|
||||
currentRecipe = recipes.find(r => checkEquivalence(cellRecipes[0], r));
|
||||
} else {
|
||||
currentRecipe = recipes[0];
|
||||
}
|
||||
}
|
||||
|
||||
const designItem = designRecipes.makeItem();
|
||||
|
||||
initializeRecipePicker({
|
||||
parent: designItem,
|
||||
title: "Block Recipe",
|
||||
entryType: "ncpf:block_recipes",
|
||||
recipes: tile.modules["ncpf:block_recipes"].recipes,
|
||||
recipes,
|
||||
multiblockType,
|
||||
updateSelection: (e) => {
|
||||
if (e === recipes[0]) {
|
||||
selectedCells.forEach(v => deleteSolverMetadata(v));
|
||||
return;
|
||||
}
|
||||
const stripped = stripEntryToMinimums(e);
|
||||
selectedCells.forEach(v => setSolverMetadata(v, { block_recipe: stripped }));
|
||||
},
|
||||
currentSelection: currentRecipe,
|
||||
});
|
||||
|
||||
designRecipes.addItem(designItem);
|
||||
|
|
@ -215,6 +263,47 @@ function* getRecipeTooltip(recipe, metadata) {
|
|||
};
|
||||
}
|
||||
|
||||
if ("nuclearcraft:overhaul_sfr:fuel_stats" in recipe.modules) {
|
||||
hasAdditionalData = true;
|
||||
const stats = recipe.modules["nuclearcraft:overhaul_sfr:fuel_stats"];
|
||||
|
||||
const { entry: output, type } = findFullEntryAnywhere(stats.output, { configurationHint: multiblockType });
|
||||
const outputName = output.modules["plannerator:display_name"].display_name;
|
||||
const atlasId = getAtlasIndexFromMetadata(output, multiblockType, type);
|
||||
|
||||
yield `Efficiency: ${stats.efficiency}`;
|
||||
yield `Heat: ${stats.heat}`;
|
||||
yield `Time: ${stats.time}`;
|
||||
yield `Criticality: ${stats.criticality}`;
|
||||
if (stats.self_priming) {
|
||||
yield "Self-priming";
|
||||
}
|
||||
yield {
|
||||
type: "item",
|
||||
atlasIndex: atlasId,
|
||||
name: outputName,
|
||||
prefix: "Output: ",
|
||||
};
|
||||
}
|
||||
|
||||
if ("nuclearcraft:overhaul_sfr:irradiator_stats" in recipe.modules) {
|
||||
hasAdditionalData = true;
|
||||
const stats = recipe.modules["nuclearcraft:overhaul_sfr:irradiator_stats"];
|
||||
|
||||
const { entry: output, type } = findFullEntryAnywhere(stats.output, { configurationHint: multiblockType });
|
||||
const outputName = output.modules["plannerator:display_name"].display_name;
|
||||
const atlasId = getAtlasIndexFromMetadata(output, multiblockType, type);
|
||||
|
||||
yield `Efficiency: ${stats.efficiency}`;
|
||||
yield `Heat: ${stats.heat}`;
|
||||
yield {
|
||||
type: "item",
|
||||
atlasIndex: atlasId,
|
||||
name: outputName,
|
||||
prefix: "Output: ",
|
||||
};
|
||||
}
|
||||
|
||||
if (!hasAdditionalData) {
|
||||
console.log("unknown recipe", recipe, metadata);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ export function getSolverMetadata(id) {
|
|||
return solverMetadata[id];
|
||||
}
|
||||
export function deleteSolverMetadata(id) {
|
||||
if (typeof id === "object") {
|
||||
// assume DOM object
|
||||
if (id.dataset && id.dataset.solverMetadata) {
|
||||
return deleteSolverMetadata(id.dataset.solverMetadata);
|
||||
}
|
||||
}
|
||||
delete solverMetadata[id];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ export function deleteTooltip(id) {
|
|||
}
|
||||
|
||||
export function setTooltip(element, data) {
|
||||
if (data.length === 0) return;
|
||||
|
||||
if (element.dataset.tooltip) {
|
||||
tooltips[Number(element.dataset.tooltip)] = data;
|
||||
return;
|
||||
|
|
@ -142,6 +144,9 @@ function createTooltipDom(id) {
|
|||
break;
|
||||
|
||||
case "item":
|
||||
if (row.prefix) {
|
||||
rowElem.appendChild(document.createTextNode(row.prefix));
|
||||
}
|
||||
rowElem.appendChild(makeAtlasImage(row.atlasIndex));
|
||||
rowElem.appendChild(document.createTextNode(row.name));
|
||||
rowElem.classList.add("tooltip--item");
|
||||
|
|
|
|||
18
style.css
18
style.css
|
|
@ -179,14 +179,13 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
|
|||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
|
||||
background-size: calc(var(--scale-factor) * var(--atlas-resolution))
|
||||
calc(var(--scale-factor) * var(--atlas-resolution));
|
||||
background-size: calc(var(--atlas-resolution) * var(--atlas-scale, 1) * var(--scale-factor));
|
||||
|
||||
width: calc(var(--scale-factor) * var(--size-x));
|
||||
height: calc(var(--scale-factor) * var(--size-y));
|
||||
width: calc(var(--default-size) * var(--scale-factor));
|
||||
height: calc(var(--default-size) * var(--scale-factor));
|
||||
|
||||
background-position-x: calc(var(--scale-factor) * var(--raw-x) * -1);
|
||||
background-position-y: calc(var(--scale-factor) * var(--raw-y) * -1);
|
||||
background-position-x: calc(var(--raw-x) * var(--atlas-scale, 1) * var(--scale-factor) * -1);
|
||||
background-position-y: calc(var(--raw-y) * var(--atlas-scale, 1) * var(--scale-factor) * -1);
|
||||
|
||||
opacity: var(--atlas-opacity, 1);
|
||||
}
|
||||
|
|
@ -197,8 +196,6 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
|
|||
|
||||
width: calc(var(--scale-factor) * var(--size-x, var(--default-size)));
|
||||
height: calc(var(--scale-factor) * var(--size-y, var(--default-size)));
|
||||
outline: calc(var(--scale-factor) * 0.5px) solid var(--cell-empty-outline-color, #ccc);
|
||||
outline-offset: calc(var(--scale-factor) * -0.5px);
|
||||
|
||||
opacity: var(--atlas-opacity, 1);
|
||||
}
|
||||
|
|
@ -257,6 +254,11 @@ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
|
|||
outline: none;
|
||||
}
|
||||
|
||||
#planner .atlas-air {
|
||||
outline: calc(var(--scale-factor) * 0.5px) solid var(--cell-empty-outline-color, #ccc);
|
||||
outline-offset: calc(var(--scale-factor) * -0.5px);
|
||||
}
|
||||
|
||||
#block-picker-list [data-active] {
|
||||
outline: calc(var(--scale-factor) * 1px) solid var(--block-active-outline-color, #000);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue