nc-planner/scripts/files.js

200 lines
6.4 KiB
JavaScript

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;
}
if (!name) {
return;
}
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 (!solverMetadata) return -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();
}
}