import { awaitEvent, emitEvent, listenTo } from "./events.js"; import { checkEquivalence, findFullEntry, stripEntryToMinimums } from "./ncpf-utils.js"; import { getPlannerDom, getPlannerMultiblockSize, getPlannerMultiblockType, loadPlanner, readPlannerCellData } from "./planner.js"; import { getAtlasIndexFromMetadata, getAtlasMetadata } from "./atlas.js"; import { getSolverMetadata, resetSolver } from "./solver.js"; import { registerLoadCallback } from "./main.js"; registerLoadCallback(() => { listenTo("file:load", () => showOpenFileDialog()); listenTo("file:new", () => {}); listenTo("file:save", () => saveFile()); }); export function showOpenFileDialog() { const input = document.createElement("input"); input.type = "file"; input.accept = ".ncpf.json"; input.addEventListener("change", async () => { const basename = input.files[0].name.split(/[/\\]/).pop(); const filename = basename.replace(/\.json$/, "").replace(/\.ncpf$/, ""); const data = await new Promise(resolve => { const reader = new FileReader(); reader.addEventListener("load", () => { resolve(JSON.parse(reader.result)); }); reader.readAsText(input.files[0]); }); void loadFile(data, filename); }); input.click(); } export async function loadExampleFile(filename) { const data = await fetch(`./examples/${filename}.ncpf.json`) .then(data => data.json()); return loadFile(data); // return loadFile(data, filename); } export async function loadFile(data, name) { if (!data.designs) throw new Error("invalid file"); if (data.designs.length === 0) throw new Error("no designs"); let design = data.designs[0]; if (data.designs.length > 1) { const prepChoice = awaitEvent("fileLoad:chooseDesign:choice"); emitEvent("fileLoad:chooseDesign", data); const res = await prepChoice; design = data.designs[res.detail]; } if (!design) throw new Error("no design"); const blocks = data.configuration[design.type].blocks .map(block => getAtlasIndexFromMetadata(block, design.type, "blocks")); const designWithAtlas = design.design.map(a => a.map(b => b.map(c => c === -1 ? -1 : blocks[c]))); loadPlanner(design.dimensions, design.type, designWithAtlas); resetSolver(design, data.configuration[design.type]); if (name) { getPlannerDom().dataset.name = name; } console.log("load file", data); } export async function saveFile(name = undefined) { if (!name) { name = getPlannerDom().dataset.name; } if (!name) { emitEvent("fileSave:nameDesign"); const nameEvent = await awaitEvent("fileSave:nameDesign:named"); name = nameEvent.detail; console.log(nameEvent) } if (!name) { name = "unnamed"; } const multiblockType = getPlannerMultiblockType(); const designAsCellData = readPlannerCellData(); const designAsAtlas = designAsCellData .map(xPlane => xPlane.map(yRow => yRow.map(c => Number(c.tile)))); const designBlocksAtlasMap = Object.fromEntries( [...new Set(designAsAtlas.flat(3))] .filter(v => v !== -1) .sort((a, b) => a - b) .map((ai, di) => [ ai, { ...getAtlasMetadata(ai), designIndex: di } ]) ); const designAsBlocks = designAsAtlas .map(xPlane => xPlane .map(yRow => yRow.map(atlasIndex => designBlocksAtlasMap[atlasIndex])) ); const designAsBlockIndices = designAsBlocks .map(xPlane => xPlane.map(yRow => yRow.map(b => b?.designIndex ?? -1))); const minimizedBlocks = Object.entries(designBlocksAtlasMap) .sort(([a], [b]) => a - b) .map(([, m]) => stripEntryToMinimums(m)); const blockRecipes = designAsBlocks .map((xPlane, x) => xPlane .map((yRow, y) => yRow .map((block, z) => { if (!block) return; const fullBlock = findFullEntry( "blocks", block, { configurationHint: multiblockType }, ); if (!fullBlock || !fullBlock.modules || !("ncpf:block_recipes" in fullBlock.modules)) return; const designIndex = designBlocksAtlasMap[Number(designAsCellData[x][y][z].tile)].designIndex; const solverMetadata = getSolverMetadata(designAsCellData[x][y][z].solverMetadata); const minimizedBlock = minimizedBlocks[designIndex]; let recipeIndex = -1; if (multiblockType === "nuclearcraft:overhaul_sfr") { if (!minimizedBlock.modules) { minimizedBlock.modules = {}; } if (!minimizedBlock.modules["ncpf:block_recipes"]) { minimizedBlock.modules["ncpf:block_recipes"] = { recipes: [], }; } const recipes = minimizedBlock.modules["ncpf:block_recipes"].recipes; const recipe = solverMetadata["block_recipe"]; recipeIndex = recipes.findIndex(r => checkEquivalence(r, recipe)); if (recipeIndex === -1) { recipes.push(stripEntryToMinimums(recipe)); recipeIndex = recipes.length - 1; } } return recipeIndex; }) .filter(b => b != null) ) .filter(r => r.length > 0) ) .filter(p => p.length > 0); let fileData = null; if (multiblockType === "nuclearcraft:overhaul_sfr") { const coolantRecipes = []; let coolantRecipeIndex; const plannerMetadata = getSolverMetadata(getPlannerDom()); coolantRecipes.push(stripEntryToMinimums(plannerMetadata.coolant_recipe)); coolantRecipeIndex = 0; fileData = { addons: [], configuration: { [multiblockType]: { blocks: minimizedBlocks, coolant_recipes: coolantRecipes, }, }, designs: [ { block_recipes: blockRecipes, coolant_recipe: coolantRecipeIndex, design: designAsBlockIndices, dimensions: getPlannerMultiblockSize(), type: multiblockType, }, ], version: 1, }; } console.log(`save file (${name})`, fileData); if (fileData) { const blob = new Blob([ JSON.stringify(fileData) ], { type: "application/json", }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `${name}.ncpf.json`; a.click(); } }