Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified parsing/game-docs.json
Binary file not shown.
16 changes: 8 additions & 8 deletions parsing/gameData.json
Original file line number Diff line number Diff line change
Expand Up @@ -9784,7 +9784,7 @@
"isFicsmas": false
},
{
"id": "CartridgeChaos_Packaged",
"id": "CartridgeChaos",
"displayName": "Turbo Rifle Ammo",
"ingredients": [
{
Expand All @@ -9798,7 +9798,7 @@
"perMin": 15
},
{
"part": "PackagedTurboFuel",
"part": "LiquidTurboFuel",
"amount": 3,
"perMin": 15
}
Expand All @@ -9812,14 +9812,14 @@
}
],
"building": {
"name": "manufacturermk1",
"power": 55
"name": "blender",
"power": 75
},
"isAlternate": false,
"isFicsmas": false
},
{
"id": "CartridgeChaos",
"id": "CartridgeChaos_Packaged",
"displayName": "Turbo Rifle Ammo",
"ingredients": [
{
Expand All @@ -9833,7 +9833,7 @@
"perMin": 15
},
{
"part": "LiquidTurboFuel",
"part": "PackagedTurboFuel",
"amount": 3,
"perMin": 15
}
Expand All @@ -9847,8 +9847,8 @@
}
],
"building": {
"name": "blender",
"power": 75
"name": "manufacturermk1",
"power": 55
},
"isAlternate": false,
"isFicsmas": false
Expand Down
30 changes: 29 additions & 1 deletion parsing/src/buildings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
// Function to extract all buildings that produce something
import {getPowerProducerBuildingName} from "./common";

// Buildings that appear in mProducedIn but are not actual production buildings
const nonProductionBuildings = new Set([
'bp_buildgun',
'bp_workbenchcomponent',
'bp_workshopcomponent',
'automatedworkbench',
'factorygame',
]);

function getProducingBuildings(data: any[]): string[] {
const producingBuildingsSet = new Set<string>();

Expand All @@ -23,7 +32,12 @@ function getProducingBuildings(data: any[]): string[] {
.filter((buildingName: string | null) => buildingName !== null); // Filter out null values

if (producedInBuildings) {
producedInBuildings.forEach((buildingName: string) => producingBuildingsSet.add(buildingName));
// The 1.2 game data references non-production buildings (e.g. BuildGun,
// WorkBench) in mProducedIn. Exclude them so they don't inflate the
// building map or pull in equipment/tool recipes downstream.
producedInBuildings
.filter((buildingName: string) => !nonProductionBuildings.has(buildingName))
.forEach((buildingName: string) => producingBuildingsSet.add(buildingName));
}
}
// If a power generator
Expand Down Expand Up @@ -60,6 +74,20 @@ function getPowerConsumptionForBuildings(data: any[], producingBuildings: string
}
});

// Variable power buildings (e.g. Hadron Collider, Converter, Quantum Encoder) may
// have mPowerConsumption of 0 or lack it entirely. Use a sentinel value (0.1) so
// downstream recipe filtering still includes them. The actual power is calculated
// per-recipe from mVariablePowerConsumption fields. Power generators (which also
// have 0 power) are excluded — they legitimately produce power rather than consume it.
const generatorPrefix = 'generator';
producingBuildings.forEach((buildingName: string) => {
if (!Object.prototype.hasOwnProperty.call(buildingsPowerMap, buildingName)) {
buildingsPowerMap[buildingName] = buildingName.startsWith(generatorPrefix) ? 0 : 0.1;
} else if (buildingsPowerMap[buildingName] === 0 && !buildingName.startsWith(generatorPrefix)) {
buildingsPowerMap[buildingName] = 0.1;
}
});

// Finally sort the map by key
const sortedMap: { [key: string]: number } = {};
Object.keys(buildingsPowerMap).sort().forEach(key => {
Expand Down
2 changes: 2 additions & 0 deletions parsing/src/parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,13 +355,15 @@ function fixItemNames(items: ParserItemDataInterface): void {
// Go through the item names and do some manual fixes, e.g. renaming "Residual Plastic" to "Plastic"
const fixItems: Record<string, string> = {
"AlienProtein": "Alien Protein",
"AluminumIngot": "Aluminum Ingot", // the parser uses the recipe display name instead of the part descriptor name. The "Alternate: Pure Aluminum Ingot" recipe produces AluminumIngot, so the part gets that recipe name instead of "Aluminum Ingot"
"CompactedCoal": "Compacted Coal",
"DarkEnergy": "Dark Matter Residue",
"HeavyOilResidue": "Heavy Oil Residue",
"LiquidFuel": "Fuel",
"Plastic": "Plastic",
"PolymerResin": "Polymer Resin",
"Rubber": "Rubber",
"Silica": "Silica", // the parser uses the recipe display name instead of the part descriptor name. The "Alumina Solution" recipe produces both AluminaSolution and Silica as products — so when Silica was processed as a product of that recipe, it inherited the recipe name "Alumina Solution" instead of its own name "Silica"
"Snow": "Snow",
"Water": "Water",
};
Expand Down
8 changes: 6 additions & 2 deletions parsing/src/recipes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function getProductionRecipes(
// Process all buildings and check if any match the producingBuildings map
const validBuilding = rawBuildingKeys.some((rawBuilding: string) => {
const buildingKey: string = rawBuilding.replace(/\//g, '').replace(/\./g, '').toLowerCase().replace('build_', '');
return producingBuildings[buildingKey];
return typeof producingBuildings[buildingKey] === 'number';
})

return validBuilding;
Expand Down Expand Up @@ -112,7 +112,7 @@ function getProductionRecipes(
if (validBuildings.length > 0) {
// Sum up power for all valid buildings
powerPerBuilding = validBuildings.reduce((totalPower: number, building: string | number) => {
if (producingBuildings[building]) {
if (typeof producingBuildings[building] === 'number') {
const buildingPower: number = producingBuildings[building]
selectedBuilding = selectedBuilding || building; // Set the first valid building as selected
return totalPower + buildingPower; // Add power for this building
Expand Down Expand Up @@ -216,6 +216,10 @@ function getPowerGeneratingRecipes(
fuels.forEach((fuel: any) => {
const primaryFuel = getPartName(fuel.mFuelClass);
const primaryFuelPart = parts.parts[primaryFuel];
if (!primaryFuelPart) {
console.warn(`Skipping power recipe fuel with missing part data: ${primaryFuel}`);
return;
}

const burnDurationInMins = primaryFuelPart.energyGeneratedInMJ / burnRateMJ;
const burnDurationInS = burnDurationInMins * 60; // Convert to seconds
Expand Down
64 changes: 64 additions & 0 deletions parsing/tests/power.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { beforeAll, describe, expect, it } from '@jest/globals'

import { processFile } from '../src/processor'
import { getPowerGeneratingRecipes } from '../src/recipes'
import {ParserPowerRecipe} from "../src/interfaces/ParserPowerRecipe";
import {ParserItemDataInterface} from "../src/interfaces/ParserPart";

describe('Power Parsing', () => {
let results: any;
Expand Down Expand Up @@ -145,4 +147,66 @@ describe('Power Parsing', () => {
expect(recipe.building.power).toBe(2500);
})
})

describe('Missing fuel part handling', () => {
it('should skip a fuel whose part is missing from items.parts and warn', () => {
const fakeData = [
{
Classes: [
{
ClassName: "Build_GeneratorFake_C",
mPowerProduction: "100.000000",
mSupplementalToPowerRatio: "0",
mFuel: [
{
mFuelClass: "Desc_KnownFuel_C",
mSupplementalResourceClass: "",
mByproduct: "",
mByproductAmount: "",
},
{
mFuelClass: "Desc_MissingFuel_C",
mSupplementalResourceClass: "",
mByproduct: "",
mByproductAmount: "",
},
],
},
],
},
];

const items: ParserItemDataInterface = {
parts: {
KnownFuel: {
name: "Known Fuel",
stackSize: 100,
isFluid: false,
isFicsmas: false,
energyGeneratedInMJ: 600,
},
// MissingFuel is deliberately absent
},
rawResources: {},
};

const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});

let recipes: ParserPowerRecipe[];
expect(() => {
recipes = getPowerGeneratingRecipes(fakeData, items);
}).not.toThrow();

// The known fuel should produce a recipe; the missing one should be skipped
expect(recipes!.length).toBe(1);
expect(recipes![0].ingredients[0].part).toBe('KnownFuel');

// Verify the warning was emitted for the missing part
expect(warnSpy).toHaveBeenCalledWith(
'Skipping power recipe fuel with missing part data: MissingFuel'
);

warnSpy.mockRestore();
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -9784,7 +9784,7 @@
"isFicsmas": false
},
{
"id": "CartridgeChaos_Packaged",
"id": "CartridgeChaos",
"displayName": "Turbo Rifle Ammo",
"ingredients": [
{
Expand All @@ -9798,7 +9798,7 @@
"perMin": 15
},
{
"part": "PackagedTurboFuel",
"part": "LiquidTurboFuel",
"amount": 3,
"perMin": 15
}
Expand All @@ -9812,14 +9812,14 @@
}
],
"building": {
"name": "manufacturermk1",
"power": 55
"name": "blender",
"power": 75
},
"isAlternate": false,
"isFicsmas": false
},
{
"id": "CartridgeChaos",
"id": "CartridgeChaos_Packaged",
"displayName": "Turbo Rifle Ammo",
"ingredients": [
{
Expand All @@ -9833,7 +9833,7 @@
"perMin": 15
},
{
"part": "LiquidTurboFuel",
"part": "PackagedTurboFuel",
"amount": 3,
"perMin": 15
}
Expand All @@ -9847,8 +9847,8 @@
}
],
"building": {
"name": "blender",
"power": 75
"name": "manufacturermk1",
"power": 55
},
"isAlternate": false,
"isFicsmas": false
Expand Down
2 changes: 1 addition & 1 deletion web/src/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const config = {
apiUrl: import.meta.env.VITE_ENV === 'dev' ? 'http://localhost:3001' : 'https://api.satisfactory-factories.app',
dataVersion: '1.0-29',
dataVersion: '1.2-00',
}
Loading