factorio-recipes/datagen.js

114 lines
4.5 KiB
JavaScript

const { readdirSync: readDir, readFileSync } = require("fs");
const { readFile, writeFile, mkdir } = require("fs/promises");
const homeDir = require("os").homedir();
const { join: joinPath, relative: relativePath, sep: pathSeparator } = require("path");
const { LuaFactory } = require('wasmoon');
const FACTORIO_DATA_DIR = process.env["FACTORIO_DATA_DIR"] ?? `${homeDir}/.steam/steam/steamapps/common/Factorio/data`;
const LOCALE = process.env["LOCALE"] ?? "en";
const MODS = "core;base" + (process.env["MODS"] ??
((process.env["SPACE_AGE"] ?? "true") == "true" ? ";quality;elevated-rails;space-age" : ""));
const mods = MODS.split(";").filter(v => v);
const readLocaleData = mod => readFileSync(joinPath(FACTORIO_DATA_DIR, mod, "locale", LOCALE, mod + ".cfg"), "utf-8")
.trim()
.split("\n")
.filter(v => v)
.map(v => v.split(/=(.*)/, 2))
.flatMap((category = null, ([ k, v ]) => k.startsWith("[") ? (category = k, []) : { key: k, value: v, category }))
.reduce((m, { key, value, category }) => ((m.get(category) ?? m.set(category, new Map()).get(category)).set(key, value), m), new Map());
const defines = [...readFileSync(joinPath(FACTORIO_DATA_DIR, "..", "doc-html", "defines.html"), "utf8")
.matchAll(/id="(defines\..*?)"/g)]
.map(v => v[1])
.flatMap((v, i) => i === 0 ? ["defines", v] : v) // prepend 'defines'
.map(v => [...v.matchAll(/([a-zA-Z0-9_]+)|\['(.+?)'\]/g)].map(([ , a, b ]) => a ?? b))
.reduce((acc, keys, idx) => {
let v = acc;
let lastKey = keys.pop();
keys.forEach(k => (typeof v[k] != "object" ? v[k] = {} : null, v = v[k]));
v[lastKey] = idx;
return acc;
}, {})
.defines;
const loadModFactory = async () => {
const luaFactory = new LuaFactory();
const lua = await luaFactory.createEngine({ enableProxy: false });
await luaFactory.mountFile("serpent.lua", await readFile(joinPath("serpent", "src", "serpent.lua")));
await lua.doString("serpent = require('serpent')");
await Promise.all(readDir(joinPath(FACTORIO_DATA_DIR), { recursive: true, withFileTypes: true })
.filter(v => v.isFile() && v.name.endsWith(".lua"))
.map(async file => {
const data = await readFile(joinPath(file.parentPath, file.name));
if (file.parentPath.endsWith("lualib")) {
await luaFactory.mountFile(file.name, data);
}
const rPath = relativePath(joinPath(FACTORIO_DATA_DIR), joinPath(file.parentPath, file.name))
.split(pathSeparator);
const luaModule = rPath.shift();
// console.log(joinPath(`__${luaModule}__`, ...rPath))
await luaFactory.mountFile(joinPath(`__${luaModule}__`, ...rPath), data);
}));
lua.global.set("defines", defines);
await lua.doString("math.atan2 = function(x, y) return math.atan(y / x) end");
await lua.doString("require('dataloader')");
const execStage = async (mod, stage) => {
const files = readDir(joinPath(FACTORIO_DATA_DIR, mod), { recursive: true, withFileTypes: true });
if (!files.some(v => v.name === `${stage}.lua`)) {
// skip
return;
}
await lua.doString(files
.filter(v => v.isFile() && v.name.endsWith(".lua") && !v.parentPath.endsWith("lualib") && v.parentPath.indexOf("prototype") >= 0)
.map(file => relativePath(joinPath(FACTORIO_DATA_DIR, mod), joinPath(file.parentPath, file.name)).replace(/\.lua$/, ""))
.flatMap(file => [ file, file.replaceAll(pathSeparator, '.') ])
.reduce((s, p) => s + `package.loaded['${p}'] = nil\n`, ""));
await Promise.all(files
.filter(v => v.isFile() && v.name.endsWith(".lua") && !v.parentPath.endsWith("lualib"))
.map(async file => {
const moduleName = relativePath(joinPath(FACTORIO_DATA_DIR, mod), joinPath(file.parentPath, file.name))
return luaFactory.mountFile(moduleName,
(mod === "space-age" && moduleName === "prototypes/ambient-sounds.lua" ? "require('__space-age__/sound/ambient/space/interlude-6/interlude-6')" : "") + // no clue why this fix is needed, it does nothing functional
await readFile(joinPath(file.parentPath, file.name)),
)
}));
console.log(`${stage} stage,\t${mod}`);
await lua.doFile(`${stage}.lua`);
return lua.global.get("data");
}
return async mods => {
for (const mod of mods) {
await execStage(mod, "data");
}
for (const mod of mods) {
await execStage(mod, "data-updates");
}
for (const mod of mods) {
await execStage(mod, "data-final-fixes");
}
return lua.global.get("data");
}
}
console.log("loading mods:", mods);
loadModFactory().then(async loadMods => {
const data = await loadMods(mods);
await mkdir("build", { recursive: true });
await writeFile(joinPath("build", "data.json"), JSON.stringify(data.raw));
});