process-data.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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. };
  77. });
  78. return acc;
  79. }, {});
  80. }
  81. export async function processData(outputFolder: string) {
  82. const data = getJsonData(dataRawPath) as FactorioRawData;
  83. const items = extractItems(data);
  84. const machines = extractMachines(data);
  85. const modules = extractModules(data);
  86. const recipeCategories: RecipeCategory[] = Object.values(data["recipe-category"] ?? {})
  87. .filter(isNotHidden)
  88. .map((c) => ({ name: c.name, subgroup: c.subgroup, order: c.order }));
  89. const recipes = Object.values(data["recipe"] ?? {})
  90. .filter(isNotHidden)
  91. .map((o) => parseRecipe(o, items));
  92. const qualityLevels: Quality[] = Object.values(data.quality ?? {})
  93. .filter(isNotHidden)
  94. .map((o) => ({
  95. ...o,
  96. icon: `quality/${o.name}.png`,
  97. }));
  98. const signals: Record<string, Signal> = {};
  99. const EXCLUDED_ENTITY: Array<keyof FactorioRawData> = [
  100. "corpse",
  101. "rail-remnants",
  102. "sticker",
  103. "elevated-straight-rail",
  104. "straight-rail",
  105. ];
  106. const signalGroups: { signalType: string; entityTypes: Array<keyof FactorioRawData> }[] = [
  107. { signalType: "virtual-signal", entityTypes: ["virtual-signal"] },
  108. { signalType: "quality", entityTypes: ["quality"] },
  109. { signalType: "entity", entityTypes: ENTITY_KEYS },
  110. { signalType: "space-location", entityTypes: SPACE_LOCATION_KEYS },
  111. ];
  112. for (let { signalType, entityTypes } of signalGroups)
  113. for (let entityTypeName of entityTypes) {
  114. let entries = data[entityTypeName];
  115. console.log(entityTypeName);
  116. if (entries !== undefined)
  117. for (let entry of Object.values(entries)) {
  118. if (isNotHidden(entry) && !EXCLUDED_ENTITY.includes(entry.type) && !entry.deconstruction_alternative)
  119. signals[entry.name] = {
  120. type: signalType,
  121. name: entry.name,
  122. subgroup: entry.subgroup ?? "other",
  123. icon: `${signalType}/${entry.name}.png`,
  124. order: entry.order,
  125. };
  126. }
  127. }
  128. for (let item of Object.values(items)) {
  129. if (!item.hidden) {
  130. const signal = {
  131. name: item.name,
  132. type: "item",
  133. subgroup: item.subgroup ?? "",
  134. icon: item.icon,
  135. order: item.order,
  136. };
  137. let k = item.name;
  138. if (k in signals) {
  139. if (signal.subgroup && signals[k].subgroup != "other" && signals[k].subgroup != signal.subgroup) {
  140. k = "item." + k;
  141. } else {
  142. console.log(`Existing entry for ${k} in the same group`);
  143. }
  144. }
  145. signals[k] = signal;
  146. }
  147. }
  148. for (let recipe of recipes) {
  149. if (!(recipe.name in signals))
  150. signals[recipe.name] = {
  151. type: "recipe",
  152. name: recipe.name,
  153. subgroup: recipe.subgroup ?? "",
  154. icon: recipe.icon,
  155. order: recipe.order,
  156. };
  157. }
  158. const recipeGroup: Array<Group<Recipe>> = groupPrototypes(recipes, data, true);
  159. const signalGroup: Array<Group<Signal>> = groupPrototypes(Object.values(signals), data, true);
  160. const beacons = extractBeacon(data);
  161. const output: ProcessedData = {
  162. qualityLevels,
  163. machines,
  164. beacons,
  165. modules,
  166. recipeCategories,
  167. recipeGroup,
  168. signalGroup,
  169. };
  170. fs.mkdirSync(outputFolder, { recursive: true });
  171. const icons = new Set<string>();
  172. walkForIcons(output, icons);
  173. const iconsList = Array.from(icons).sort();
  174. checkIcons(iconsList);
  175. fs.writeFileSync(path.resolve(outputFolder, "data.json"), JSON.stringify(output, null, 2), "utf-8");
  176. await buildIconTextureMap(iconsList, scriptOutputPath, outputFolder);
  177. }