import { registerLoadCallback } from "./main.js"; import { listenTo } from "./events.js"; 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 { 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"; const defaultRecipes = { ["nuclearcraft:overhaul_sfr"]: { coolant_recipe: { elements: [ { name: "water", type: "legacy_fluid" }, { name: "condensate_water", type: "legacy_fluid" }, ], type: "list", }, }, }; registerLoadCallback(() => { listenTo([ "config:end", "solver:reset:done" ], rescanDesignRecipes); listenTo([ "planner:selection:removed", "planner:selection:added"], rescanSelectionRecipes); }); function initializeRecipePicker({ parent, title, entryListType, multiblockType, recipes, currentSelection, updateSelection, }) { parent.querySelectorAll('[data-text="recipe:type"]') .forEach(e => e.innerText = title); function updateSelectedRecipe(selectedRecipe) { if (!selectedRecipe) { parent.querySelectorAll('[data-text="recipe:selected"]') .forEach(e => e.innerText = "Unknown"); parent.querySelectorAll('[data-atlas="recipe:selected"]') .forEach(e => e.replaceChildren(makeAtlasImage(-1))); return; } const selectedRecipeName = selectedRecipe.modules["plannerator:display_name"].display_name; const selectedAtlasId = getAtlasIndexFromMetadata(selectedRecipe, multiblockType, entryListType); const metadata = getAtlasMetadata(selectedAtlasId); parent.querySelectorAll('[data-text="recipe:selected"]') .forEach(e => e.innerText = selectedRecipeName); parent.querySelectorAll('[data-atlas="recipe:selected"]') .forEach(e => e.replaceChildren(makeAtlasImage(selectedAtlasId))); parent.querySelectorAll('.recipe--selected') .forEach(e => setTooltip(e, [...getRecipeTooltip(selectedRecipe, metadata)])); } updateSelectedRecipe(currentSelection); function handleDismiss() { console.log("dismiss"); parent.classList.remove("selecting"); window.removeEventListener("click", handleDismiss); } parent.addEventListener("click", e => e.stopPropagation()); const filterElem = parent.querySelector('[data-input="recipe:filter"]'); function updateRecipeList() { const filter = Filter.fromInput(filterElem); const recipeList = new DomList(parent.querySelector(":scope > .select")); recipeList.clear(); console.log(recipes); for (let i = 0; i < recipes.length; i++) { const recipe = recipes[i]; const recipeItem = recipeList.makeItem(); const recipeName = recipe.modules["plannerator:display_name"].display_name; const atlasId = getAtlasIndexFromMetadata(recipe, multiblockType, entryListType); const metadata = getAtlasMetadata(atlasId); recipeItem.querySelectorAll('[data-text="recipe:item"]') .forEach(e => e.innerText = recipeName); recipeItem.querySelectorAll('[data-atlas="recipe:item"]') .forEach(e => e.replaceChildren(makeAtlasImage(atlasId))); recipeItem.addEventListener("click", () => { handleDismiss(); updateSelectedRecipe(recipe); updateSelection(recipe); }); const tooltipData = [...getRecipeTooltip(recipe, metadata)]; setTooltip(recipeItem, tooltipData); if (!filter.test(recipeName, ...tooltipDataToPlain(tooltipData))) { recipeItem.classList.add("hidden"); } recipeList.addItem(recipeItem); } } updateRecipeList(); filterElem.addEventListener("input", debounce(updateRecipeList)); parent.querySelectorAll(':scope > .recipe--selected').forEach(e => { e.addEventListener("click", () => { parent.classList.add("selecting"); parent.querySelector('[autofocus]').focus(); window.addEventListener("click", handleDismiss); }); }); } function rescanDesignRecipes() { console.log("rescan recipes"); console.log(configuration); const multiblockType = getPlannerMultiblockType(); const configs = [ ...Object.entries(configuration.configuration), ...configuration.addons.flatMap(a => Object.entries(a.configuration)), ].filter(([type]) => type === multiblockType); const designRecipes = new DomList( document.getElementById("recipe-selector:design"), document.getElementById("recipe-selector:template"), ); designRecipes.clear(); if (multiblockType === "nuclearcraft:overhaul_sfr") { const coolantRecipes = configs.filter(([, v]) => "coolant_recipes" in v && v.coolant_recipes.length > 0) .flatMap(([, v]) => v.coolant_recipes); const designItem = designRecipes.makeItem(); const plannerDom = getPlannerDom(); const plannerMetadata = getSolverMetadata(plannerDom); let currentSelection = plannerMetadata?.coolant_recipe; if (!plannerMetadata) { currentSelection = defaultRecipes[multiblockType].coolant_recipe; setSolverMetadata(plannerDom, { coolant_recipe: currentSelection, }); } currentSelection = findFullEntry("coolant_recipes", currentSelection, { configurationHint: multiblockType }); initializeRecipePicker({ parent: designItem, title: "Coolant Recipe", entryListType: "coolant_recipes", recipes: coolantRecipes, updateSelection: r => setSolverMetadata(plannerDom, { coolant_recipe: r }), multiblockType, currentSelection, }); designRecipes.addItem(designItem); } } function rescanSelectionRecipes() { const selectedCells = getSelectedCells(); const selectedTiles = new Set(selectedCells.map(v => Number(v.dataset.tile))); const designRecipes = new DomList( document.getElementById("recipe-selector:selection"), document.getElementById("recipe-selector:template"), ); designRecipes.clear(); if (selectedTiles.size !== 1 || selectedTiles.has(-1)) { return; } const multiblockType = getPlannerMultiblockType(); const tileMetadata = getAtlasMetadata(selectedCells[0].dataset.tile); const tiles = findFullEntryFromAtlasMetadata(tileMetadata, { all: true }); 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, 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); } function* getRecipeTooltip(recipe, metadata) { const multiblockType = getPlannerMultiblockType(); let hasAdditionalData = false; if ("nuclearcraft:overhaul_sfr:coolant_recipe_stats" in recipe.modules) { hasAdditionalData = true; const stats = recipe.modules["nuclearcraft:overhaul_sfr:coolant_recipe_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 `Heat: ${stats.heat}`; yield `Output:` yield `Ratio ${stats.output_ratio}`; yield { type: "item", atlasIndex: atlasId, name: outputName, }; } 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); } if (metadata.configurationAddon) { yield { type: "italics", text: metadata.configurationAddon.name, }; } }