process-data.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import fs from "fs";
  2. import { checkIcons, dataRawPath, getJsonData, scriptOutputPath } from "./helpers/file.helper.ts";
  3. import type { Beacon, Item, Machine, Module, Quality, RecipeCategory, Signal } from "./process-data.models.ts";
  4. import { groupPrototypes, type Group } from "./helpers/groups.helper.ts";
  5. import { parseRecipe, type Recipe } from "./helpers/recipes.helper.ts";
  6. import type { ProcessedData } from "./process-data.models.ts";
  7. import { buildIconTextureMap, walkForIcons } from "./helpers/icons.helper.ts";
  8. import path from "path";
  9. import { ENTITY_KEYS, ITEM_KEYS, SPACE_LOCATION_KEYS } from "./lua-api/const.ts";
  10. import type { FactorioRawData } from "./lua-api/models.ts";
  11. const isNotHidden = (o: unknown) => !(o as { hidden?: boolean }).hidden;
  12. /**
  13. * Extract machines.
  14. */
  15. function extractMachines(data: FactorioRawData): Machine[] {
  16. return Object.values(data["assembling-machine"] ?? {})
  17. .filter(isNotHidden)
  18. .map((machine) => ({
  19. name: machine.name,
  20. icon: `entity/${machine.name}.png`,
  21. crafting_categories: machine.crafting_categories,
  22. crafting_speed: machine.crafting_speed,
  23. allowed_effects: machine.allowed_effects,
  24. module_slots: machine.module_slots,
  25. selection_box: machine.selection_box,
  26. effect_receiver: machine.effect_receiver,
  27. }));
  28. }
  29. /**
  30. * Extract modules.
  31. */
  32. function extractModules(data: FactorioRawData): Module[] {
  33. return Object.values(data.module ?? {})
  34. .filter(isNotHidden)
  35. .map((module) => ({
  36. name: module.name,
  37. icon: `item/${module.name}.png`,
  38. subgroup: module.subgroup,
  39. category: module.category,
  40. tier: module.tier,
  41. order: module.order,
  42. effect: module.effect,
  43. }));
  44. }
  45. /**
  46. * Extract beacon.
  47. */
  48. function extractBeacon(data: FactorioRawData): Beacon[] {
  49. return Object.values(data.beacon ?? {})
  50. .filter(isNotHidden)
  51. .map((beacon) => ({
  52. name: beacon.name,
  53. icon: `item/${beacon.name}.png`,
  54. subgroup: beacon.subgroup,
  55. distribution_effectivity: beacon.distribution_effectivity,
  56. distribution_effectivity_bonus_per_quality_level: beacon.distribution_effectivity_bonus_per_quality_level,
  57. module_slots: beacon.module_slots,
  58. allowed_effects: beacon.allowed_effects,
  59. profile: beacon.profile,
  60. }));
  61. }
  62. /**
  63. * Extract all items (and item‑like things) from the raw dump.
  64. */
  65. function extractItems(dataRaw: FactorioRawData): Record<string, Item> {
  66. return ITEM_KEYS.reduce((acc: Record<string, Item>, key) => {
  67. const data = dataRaw[key];
  68. Object.values(data ?? {}).forEach((item) => {
  69. const isFluid = item.type === "fluid";
  70. acc[item.name] = {
  71. name: item.name,
  72. hidden: !isNotHidden(item),
  73. icon: `${isFluid ? "fluid" : "item"}/${item.name}.png`,
  74. subgroup: item.subgroup ?? (isFluid ? "fluid" : "other"),
  75. order: item.order,
  76. stackSize: item.stack_size,
  77. };
  78. });
  79. return acc;
  80. }, {});
  81. }
  82. export async function processData(outputFolder: string) {
  83. const data = getJsonData(dataRawPath) as FactorioRawData;
  84. const items = extractItems(data);
  85. const machines = extractMachines(data);
  86. const modules = extractModules(data);
  87. const recipeCategories: RecipeCategory[] = Object.values(data["recipe-category"] ?? {})
  88. .filter(isNotHidden)
  89. .map((c) => ({ name: c.name, subgroup: c.subgroup, order: c.order }));
  90. const recipes = Object.values(data["recipe"] ?? {})
  91. .filter(isNotHidden)
  92. .map((o) => parseRecipe(o, items));
  93. const qualityLevels: Quality[] = Object.values(data.quality ?? {})
  94. .filter(isNotHidden)
  95. .map((o) => ({
  96. ...o,
  97. icon: `quality/${o.name}.png`,
  98. }));
  99. const signals: Record<string, Signal> = {};
  100. const EXCLUDED_ENTITY: Array<keyof FactorioRawData> = [
  101. "corpse",
  102. "rail-remnants",
  103. "sticker",
  104. "elevated-straight-rail",
  105. "straight-rail",
  106. ];
  107. const signalGroups: { signalType: string; entityTypes: Array<keyof FactorioRawData> }[] = [
  108. { signalType: "virtual-signal", entityTypes: ["virtual-signal"] },
  109. { signalType: "quality", entityTypes: ["quality"] },
  110. { signalType: "entity", entityTypes: ENTITY_KEYS },
  111. { signalType: "space-location", entityTypes: SPACE_LOCATION_KEYS },
  112. ];
  113. for (let { signalType, entityTypes } of signalGroups)
  114. for (let entityTypeName of entityTypes) {
  115. let entries = data[entityTypeName];
  116. console.log(entityTypeName);
  117. if (entries !== undefined)
  118. for (let entry of Object.values(entries)) {
  119. if (isNotHidden(entry) && !EXCLUDED_ENTITY.includes(entry.type) && !entry.deconstruction_alternative)
  120. signals[entry.name] = {
  121. type: signalType,
  122. name: entry.name,
  123. subgroup: entry.subgroup ?? "other",
  124. icon: `${signalType}/${entry.name}.png`,
  125. order: entry.order,
  126. };
  127. }
  128. }
  129. const stackSizes: Record<string, number> = {};
  130. for (let item of Object.values(items)) {
  131. if (!item.hidden) {
  132. const signal = {
  133. name: item.name,
  134. type: "item",
  135. subgroup: item.subgroup ?? "",
  136. icon: item.icon,
  137. order: item.order,
  138. };
  139. let k = item.name;
  140. if (k in signals) {
  141. if (signal.subgroup && signals[k].subgroup != "other" && signals[k].subgroup != signal.subgroup) {
  142. k = "item." + k;
  143. } else {
  144. console.log(`Existing entry for ${k} in the same group`);
  145. }
  146. }
  147. signals[k] = signal;
  148. }
  149. if (item.name && item.stackSize && Number.isInteger(item.stackSize)) stackSizes[item.name] = item.stackSize;
  150. }
  151. for (let recipe of recipes) {
  152. if (!(recipe.name in signals))
  153. signals[recipe.name] = {
  154. type: "recipe",
  155. name: recipe.name,
  156. subgroup: recipe.subgroup ?? "",
  157. icon: recipe.icon,
  158. order: recipe.order,
  159. };
  160. }
  161. const recipeGroup: Array<Group<Recipe>> = groupPrototypes(recipes, data, true);
  162. const signalGroup: Array<Group<Signal>> = groupPrototypes(Object.values(signals), data, true);
  163. const beacons = extractBeacon(data);
  164. const output: ProcessedData = {
  165. qualityLevels,
  166. machines,
  167. beacons,
  168. modules,
  169. recipeCategories,
  170. recipeGroup,
  171. signalGroup,
  172. stackSizes,
  173. };
  174. fs.mkdirSync(outputFolder, { recursive: true });
  175. const icons = new Set<string>();
  176. walkForIcons(output, icons);
  177. const iconsList = Array.from(icons).sort();
  178. checkIcons(iconsList);
  179. fs.writeFileSync(path.resolve(outputFolder, "data.json"), JSON.stringify(output, null, 2), "utf-8");
  180. await buildIconTextureMap(iconsList, scriptOutputPath, outputFolder);
  181. }