import fs from "fs"; import { checkIcons, dataRawPath, getJsonData, scriptOutputPath } from "./helpers/file.helper.ts"; import type { Beacon, Item, Machine, Module, Quality, RecipeCategory, Signal } from "./process-data.models.ts"; import { groupPrototypes, type Group } from "./helpers/groups.helper.ts"; import { parseRecipe, type Recipe } from "./helpers/recipes.helper.ts"; import type { ProcessedData } from "./process-data.models.ts"; import { buildIconTextureMap, walkForIcons } from "./helpers/icons.helper.ts"; import path from "path"; import { ENTITY_KEYS, ITEM_KEYS, SPACE_LOCATION_KEYS } from "./lua-api/const.ts"; import type { FactorioRawData } from "./lua-api/models.ts"; const isNotHidden = (o: unknown) => !(o as { hidden?: boolean }).hidden; /** * Extract machines. */ function extractMachines(data: FactorioRawData): Machine[] { return Object.values(data["assembling-machine"] ?? {}) .filter(isNotHidden) .map((machine) => ({ name: machine.name, icon: `entity/${machine.name}.png`, crafting_categories: machine.crafting_categories, crafting_speed: machine.crafting_speed, allowed_effects: machine.allowed_effects, module_slots: machine.module_slots, selection_box: machine.selection_box, effect_receiver: machine.effect_receiver, })); } /** * Extract modules. */ function extractModules(data: FactorioRawData): Module[] { return Object.values(data.module ?? {}) .filter(isNotHidden) .map((module) => ({ name: module.name, icon: `item/${module.name}.png`, subgroup: module.subgroup, category: module.category, tier: module.tier, order: module.order, effect: module.effect, })); } /** * Extract beacon. */ function extractBeacon(data: FactorioRawData): Beacon[] { return Object.values(data.beacon ?? {}) .filter(isNotHidden) .map((beacon) => ({ name: beacon.name, icon: `item/${beacon.name}.png`, subgroup: beacon.subgroup, distribution_effectivity: beacon.distribution_effectivity, distribution_effectivity_bonus_per_quality_level: beacon.distribution_effectivity_bonus_per_quality_level, module_slots: beacon.module_slots, allowed_effects: beacon.allowed_effects, profile: beacon.profile, })); } /** * Extract all items (and item‑like things) from the raw dump. */ function extractItems(dataRaw: FactorioRawData): Record { return ITEM_KEYS.reduce((acc: Record, key) => { const data = dataRaw[key]; Object.values(data ?? {}).forEach((item) => { const isFluid = item.type === "fluid"; acc[item.name] = { name: item.name, hidden: !isNotHidden(item), icon: `${isFluid ? "fluid" : "item"}/${item.name}.png`, subgroup: item.subgroup ?? (isFluid ? "fluid" : "other"), order: item.order, stackSize: item.stack_size, }; }); return acc; }, {}); } export async function processData(outputFolder: string) { const data = getJsonData(dataRawPath) as FactorioRawData; const items = extractItems(data); const machines = extractMachines(data); const modules = extractModules(data); const recipeCategories: RecipeCategory[] = Object.values(data["recipe-category"] ?? {}) .filter(isNotHidden) .map((c) => ({ name: c.name, subgroup: c.subgroup, order: c.order })); const recipes = Object.values(data["recipe"] ?? {}) .filter(isNotHidden) .map((o) => parseRecipe(o, items)); const qualityLevels: Quality[] = Object.values(data.quality ?? {}) .filter(isNotHidden) .map((o) => ({ ...o, icon: `quality/${o.name}.png`, })); const signals: Record = {}; const EXCLUDED_ENTITY: Array = [ "corpse", "rail-remnants", "sticker", "elevated-straight-rail", "straight-rail", ]; const signalGroups: { signalType: string; entityTypes: Array }[] = [ { signalType: "virtual-signal", entityTypes: ["virtual-signal"] }, { signalType: "quality", entityTypes: ["quality"] }, { signalType: "entity", entityTypes: ENTITY_KEYS }, { signalType: "space-location", entityTypes: SPACE_LOCATION_KEYS }, ]; for (let { signalType, entityTypes } of signalGroups) for (let entityTypeName of entityTypes) { let entries = data[entityTypeName]; console.log(entityTypeName); if (entries !== undefined) for (let entry of Object.values(entries)) { if (isNotHidden(entry) && !EXCLUDED_ENTITY.includes(entry.type) && !entry.deconstruction_alternative) signals[entry.name] = { type: signalType, name: entry.name, subgroup: entry.subgroup ?? "other", icon: `${signalType}/${entry.name}.png`, order: entry.order, }; } } const stackSizes: Record = {}; for (let item of Object.values(items)) { if (!item.hidden) { const signal = { name: item.name, type: "item", subgroup: item.subgroup ?? "", icon: item.icon, order: item.order, }; let k = item.name; if (k in signals) { if (signal.subgroup && signals[k].subgroup != "other" && signals[k].subgroup != signal.subgroup) { k = "item." + k; } else { console.log(`Existing entry for ${k} in the same group`); } } signals[k] = signal; } if (item.name && item.stackSize && Number.isInteger(item.stackSize)) stackSizes[item.name] = item.stackSize; } for (let recipe of recipes) { if (!(recipe.name in signals)) signals[recipe.name] = { type: "recipe", name: recipe.name, subgroup: recipe.subgroup ?? "", icon: recipe.icon, order: recipe.order, }; } const recipeGroup: Array> = groupPrototypes(recipes, data, true); const signalGroup: Array> = groupPrototypes(Object.values(signals), data, true); const beacons = extractBeacon(data); const output: ProcessedData = { qualityLevels, machines, beacons, modules, recipeCategories, recipeGroup, signalGroup, stackSizes, }; fs.mkdirSync(outputFolder, { recursive: true }); const icons = new Set(); walkForIcons(output, icons); const iconsList = Array.from(icons).sort(); checkIcons(iconsList); fs.writeFileSync(path.resolve(outputFolder, "data.json"), JSON.stringify(output, null, 2), "utf-8"); await buildIconTextureMap(iconsList, scriptOutputPath, outputFolder); }