• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

samsmithnz / SatisfactoryTree / 17569747743

09 Sep 2025 02:18AM UTC coverage: 89.817% (+36.9%) from 52.88%
17569747743

Pull #284

github

web-flow
Merge 3858cabe9 into e771c4f78
Pull Request #284: Reworking logic to handle imports and exports

274 of 314 branches covered (87.26%)

Branch coverage included in aggregate %.

855 of 943 new or added lines in 26 files covered. (90.67%)

855 of 943 relevant lines covered (90.67%)

1804.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.8
/src/SatisfactoryTree.Logic/Extraction/ProcessRawRecipes.cs
1
using SatisfactoryTree.Logic.Models;
2
using System.Text.Json;
3
using System.Text.RegularExpressions;
4

5
namespace SatisfactoryTree.Logic.Extraction
6
{
7
    public class ProcessRawRecipes
8
    {
NEW
9
        private static readonly string[] sourceArray = ["bp_workbenchcomponent", "bp_workshopcomponent", "factorygame"];
×
10

11
        public static List<Recipe> GetProductionRecipes(List<JsonElement> data, Dictionary<string, double> producingBuildings)
12
        {
4✔
13
            List<Recipe> recipes = new();
4✔
14

15
            foreach (JsonElement entry in data)
22,572✔
16
            {
11,280✔
17
                //Debug.Write(entry.ToString());
18
                string? producedIn = entry.TryGetProperty("mProducedIn", out JsonElement mProducedIn) ? mProducedIn.GetString() : string.Empty;
11,280✔
19
                string? displayName = entry.TryGetProperty("mDisplayName", out JsonElement mDisplayName) ? mDisplayName.GetString() : string.Empty;
11,280✔
20
                if (string.IsNullOrEmpty(producedIn) || Common.Blacklist.Contains(producedIn))
11,280✔
21
                {
10,116✔
22
                    continue;
10,116✔
23
                }
24
                string className = entry.GetProperty("ClassName").ToString();
1,164✔
25
                string? ingredientsJSON = entry.TryGetProperty("mIngredients", out JsonElement mIngredients) ? mIngredients.GetString() : string.Empty;
1,164!
26
                string? productsJSON = entry.TryGetProperty("mProduct", out JsonElement mProduct) ? mProduct.GetString() : string.Empty;
1,164!
27
                string? manufacturingDurationJSON = entry.TryGetProperty("mManufactoringDuration", out JsonElement mManufactoringDuration) ? mManufactoringDuration.GetString() : string.Empty;
1,164!
28
                double manufacturingDuration = 0;
1,164✔
29
                if (manufacturingDurationJSON != null)
1,164✔
30
                {
1,164✔
31
                    double.TryParse(manufacturingDurationJSON.ToString(), out manufacturingDuration);
1,164✔
32
                }
1,164✔
33

34
                List<Ingredient> ingredients = new();
1,164✔
35
                if (ingredientsJSON != null && ingredientsJSON.Length > 0)
1,164!
36
                {
1,160✔
37
                    MatchCollection ingredientMatches = Regex.Matches(ingredientsJSON, @"ItemClass="".*?\/Desc_(.*?)\.Desc_.*?"",Amount=(\d+)");
1,160✔
38
                    if (ingredientMatches != null)
1,160✔
39
                    {
1,160✔
40
                        foreach (Match match in ingredientMatches)
8,208✔
41
                        {
2,364✔
42
                            string partName = match.Groups[1].Value;
2,364✔
43
                            double partAmount = 0;
2,364✔
44
                            double.TryParse(match.Groups[2].Value, out partAmount);
2,364✔
45
                            if (Common.IsFluid(partName))
2,364✔
46
                            {
344✔
47
                                partAmount = partAmount / 1000;
344✔
48
                            }
344✔
49
                            double perMin = 0;
2,364✔
50
                            if (manufacturingDuration > 0 && partAmount > 0)
2,364!
51
                            {
2,364✔
52
                                perMin = (60 / manufacturingDuration) * partAmount;
2,364✔
53
                            }
2,364✔
54
                            ingredients.Add(new()
2,364✔
55
                            {
2,364✔
56
                                part = partName,
2,364✔
57
                                amount = partAmount,
2,364✔
58
                                perMin = perMin
2,364✔
59
                            });
2,364✔
60
                        }
2,364✔
61
                    }
1,160✔
62
                }
1,160✔
63

64

65
                List<Product> products = new();
1,164✔
66
                if (productsJSON != null && productsJSON.Length > 0)
1,164!
67
                {
1,164✔
68
                    MatchCollection productMatches;
69
                    if (className == "Recipe_Alternate_AutomatedMiner_C")
1,164✔
70
                    {
4✔
71
                        // BP_ItemDescriptorPortableMiner_C
72
                        productMatches = Regex.Matches(productsJSON, @"ItemClass="".*?\/BP_ItemDescriptor(.*?)\.BP_ItemDescriptor.*?"",Amount=(\d+)");
4✔
73
                    }
4✔
74
                    else
75
                    {
1,160✔
76
                        productMatches = Regex.Matches(productsJSON, @"ItemClass="".*?\/Desc_(.*?)\.Desc_.*?"",Amount=(\d+)");
1,160✔
77
                    }
1,160✔
78
                    if (productMatches != null)
1,164✔
79
                    {
1,164✔
80
                        foreach (Match match in productMatches)
6,116✔
81
                        {
1,312✔
82
                            string partName = match.Groups[1].Value;
1,312✔
83
                            double partAmount = 0;
1,312✔
84
                            double.TryParse(match.Groups[2].Value, out partAmount);
1,312✔
85
                            if (Common.IsFluid(partName))
1,312✔
86
                            {
200✔
87
                                partAmount = partAmount / 1000;
200✔
88
                            }
200✔
89
                            double perMin = 0;
1,312✔
90
                            if (manufacturingDuration > 0 && partAmount > 0)
1,312!
91
                            {
1,312✔
92
                                perMin = (60 / manufacturingDuration) * partAmount;
1,312✔
93
                            }
1,312✔
94
                            products.Add(new()
1,312✔
95
                            {
1,312✔
96
                                part = partName,
1,312✔
97
                                amount = partAmount,
1,312✔
98
                                perMin = perMin,
1,312✔
99
                                isByProduct = products.Count > 0
1,312✔
100
                            });
1,312✔
101
                        }
1,312✔
102
                    }
1,164✔
103
                }
1,164✔
104
             
105

106
                // Extract all producing buildings
107
                var producedInMatches = Regex.Matches(producedIn, @"Build_\w+_C");
1,164✔
108

109
                // Calculate power per building and choose the most relevant one
110
                double powerPerBuilding = 0;
1,164✔
111
                string? selectedBuilding = null;
1,164✔
112

113
                if (producedInMatches.Any())
1,164✔
114
                {
1,164✔
115
                    foreach (object? buildingMatch in producedInMatches)
5,828✔
116
                    {
1,168✔
117
                        if (buildingMatch != null)
1,168✔
118
                        {
1,168✔
119
                            string building2 = buildingMatch.ToString();
1,168✔
120
                            building2 = Common.GetBuildingName(building2).ToLower();
1,168✔
121
                            if (producingBuildings.ContainsKey(building2))
1,168✔
122
                            {
1,164✔
123
                                double buildingPower = producingBuildings[building2];
1,164✔
124
                                if (selectedBuilding == null)
1,164✔
125
                                {
1,164✔
126
                                    selectedBuilding = building2; // Set the first valid building as selected
1,164✔
127
                                }
1,164✔
128
                                powerPerBuilding += buildingPower; // Add power for this building
1,164✔
129
                            }
1,164✔
130
                        }
1,168✔
131
                    }
1,168✔
132
                }
1,164✔
133

134
                // Calculate variable power for recipes that need it
135
                double? lowPower = null;
1,164✔
136
                double? highPower = null;
1,164✔
137
                if (selectedBuilding == "hadroncollider" || selectedBuilding == "converter" || selectedBuilding == "quantumencoder")
1,164✔
138
                {
172✔
139
                    // Get the power from the recipe instead of the building
140
                    double lowPowerTemp = 0;
172✔
141
                    double highPowerTemp = 0;
172✔
142
                    string? lowPowerJson = entry.GetProperty("mVariablePowerConsumptionConstant").ToString();
172✔
143
                    string? highPowerJson = entry.GetProperty("mVariablePowerConsumptionFactor").ToString();
172✔
144

145
                    if (lowPowerJson != null)
172✔
146
                    {
172✔
147
                        double.TryParse(lowPowerJson.ToString(), out lowPowerTemp);
172✔
148
                        lowPower = lowPowerTemp;
172✔
149
                    }
172✔
150
                    if (highPowerJson != null)
172✔
151
                    {
172✔
152
                        double.TryParse(highPowerJson.ToString(), out highPowerTemp);
172✔
153
                        highPower = highPowerTemp;
172✔
154
                    }
172✔
155

156
                    // Calculate the average power
157
                    if (lowPower != null && highPower != null)
172!
158
                    {
172✔
159
                        powerPerBuilding = (double)((lowPower + highPower) / 2);
172!
160
                    }
172✔
161

162
                }
172✔
163

164
                // Create building object with the selected building and calculated power
165
                Building building = new(selectedBuilding ?? "") // Use the first valid building, or empty string if none
1,164!
166
                { 
1,164✔
167
                    Power = powerPerBuilding
1,164✔
168
                };
1,164✔
169

170
                if (lowPower.HasValue && highPower.HasValue)
1,164✔
171
                {
172✔
172
                    building.MinPower = lowPower;
172✔
173
                    building.MaxPower = highPower;
172✔
174
                }
172✔
175

176
                // Check if any ingredient is SAMIngot to set usesSAMOre flag
177
                bool usesSAMOre = ingredients.Any(ingredient => ingredient.part == "SAMIngot");
3,440✔
178

179
                //if (blacklist. producedIn)
180
                recipes.Add(new Recipe
1,164✔
181
                {
1,164✔
182
                    id = Common.GetRecipeName(className),
1,164✔
183
                    displayName = displayName,
1,164✔
184
                    ingredients = ingredients,
1,164✔
185
                    products = products,
1,164✔
186
                    building = building,
1,164✔
187
                    isAlternate = displayName.Contains("Alternate"),
1,164✔
188
                    isFicsmas = Common.IsFicsmas(displayName),
1,164✔
189
                    usesSAMOre = usesSAMOre
1,164✔
190
                });
1,164✔
191
            }
1,164✔
192

193
            return recipes.OrderBy(r => r.displayName).ToList();
1,168✔
194
        }
4✔
195

196
        public static List<PowerGenerationRecipe> GetPowerGeneratingRecipes(List<JsonElement> data, RawPartsAndRawMaterials parts, Dictionary<string, double> producingBuildings)
197
        {
4✔
198
            var recipes = new List<PowerGenerationRecipe>();
4✔
199

200
            foreach (JsonElement entry in data)
22,572✔
201
            {
11,280✔
202
                string className = entry.GetProperty("ClassName").ToString();
11,280✔
203
                string? displayName = entry.TryGetProperty("mDisplayName", out JsonElement mDisplayName) ? mDisplayName.GetString() : string.Empty;
11,280✔
204
                //string? producedIn = entry.TryGetProperty("mProducedIn", out JsonElement mProducedIn) ? mProducedIn.GetString() : string.Empty;
205
                //string? products = entry.TryGetProperty("mProduct", out JsonElement mProduct) ? mProduct.GetString() : string.Empty;
206
                //string? ingredients = entry.TryGetProperty("mIngredients", out JsonElement mIngredients) ? mIngredients.GetString() : string.Empty;
207

208
                string? powerProductionJSON = entry.TryGetProperty("mPowerProduction", out JsonElement mPowerProduction) ? mPowerProduction.GetString() : string.Empty;
11,280✔
209
                double powerProduction = 0;
11,280✔
210
                if (powerProductionJSON != null)
11,280✔
211
                {
11,280✔
212
                    double.TryParse(powerProductionJSON.ToString(), out powerProduction);
11,280✔
213
                }
11,280✔
214
                string? supplementalToPowerRatioJSON = entry.TryGetProperty("mSupplementalToPowerRatio", out JsonElement mSupplementalToPowerRatio) ? mSupplementalToPowerRatio.GetString() : string.Empty;
11,280✔
215
                double supplementalToPowerRatio = 0;
11,280✔
216
                if (supplementalToPowerRatioJSON != null)
11,280✔
217
                {
11,280✔
218
                    double.TryParse(supplementalToPowerRatioJSON.ToString(), out supplementalToPowerRatio);
11,280✔
219
                }
11,280✔
220

221
                //string? fuelJSON = entry.TryGetProperty("mFuel", out JsonElement mFuel) ? mFuel.GetString() : string.Empty;
222
                JsonElement? fuelJSON = entry.TryGetProperty("mFuel", out JsonElement mFuel) ? mFuel : (JsonElement?)null;
11,280✔
223

224
                Building building = new(Common.GetPowerBuildingName(className)) // Use the first valid building, or empty string if none
11,280✔
225
                {
11,280✔
226
                    Power = Math.Round(powerProduction) // generated power - can be rounded to the nearest whole number (all energy numbers are whole numbers)
11,280✔
227
                };
11,280✔
228

229
                double supplementalRatio = supplementalToPowerRatio;
11,280✔
230
                // 1. Generator MW generated. This is an hourly value.
231
                // 2. Divide by 60, to get the minute value
232
                // 3. Now calculate the MJ, using the MJ->MW constant (1/3600), (https://en.wikipedia.org/wiki/Joule#Conversions)
233
                // 4. Now divide this number by the part energy to calculate how many pieces per min
234
                double burnRateMJ = (powerProduction / 60d) / (1d / 3600d);
11,280✔
235

236
                //1List<Ingredient> ingredients = new();
237
                if (fuelJSON.HasValue && fuelJSON.Value.ValueKind == JsonValueKind.Array)
11,280✔
238
                {
16✔
239
                    foreach (JsonElement fuel in fuelJSON.Value.EnumerateArray())
184✔
240
                    {
68✔
241

242
                        string? primaryFuel = fuel.TryGetProperty("mFuelClass", out JsonElement mFuelClass) ? mFuelClass.GetString() : string.Empty;
68!
243
                        string primaryFuelName = Common.GetPartName(primaryFuel);
68✔
244
                        string? byProductAmountJSON = fuel.TryGetProperty("mByproductAmount", out JsonElement mByproductAmount) ? mByproductAmount.GetString() : string.Empty;
68!
245
                        double byProductAmount = 0;
68✔
246
                        if (byProductAmountJSON != null)
68✔
247
                        {
68✔
248
                            double.TryParse(byProductAmountJSON.ToString(), out byProductAmount);
68✔
249
                        }
68✔
250
                        Part primaryFuelPart = parts.Parts[primaryFuelName];
68✔
251
                        double burnDurationInMins = primaryFuelPart.energyGeneratedInMJ / burnRateMJ;
68✔
252
                        double burnDurationInS = burnDurationInMins * 60; // Convert to seconds
68✔
253
                        Fuel fuelItem = new()
68!
254
                        {
68✔
255
                            primaryFuel = primaryFuelName,
68✔
256
                            supplementaryFuel = fuel.TryGetProperty("mSupplementalResourceClass", out JsonElement mSupplementalResourceClass) ? Common.GetPartName(mSupplementalResourceClass.GetString()) : "",
68✔
257
                            byProduct = fuel.TryGetProperty("mByproduct", out JsonElement mByproduct) ? Common.GetPartName(mByproduct.GetString()) : "",
68✔
258
                            byProductAmount = byProductAmount,
68✔
259
                            byProductAmountPerMin = byProductAmount / burnDurationInMins,
68✔
260
                            burnDurationInS = burnDurationInS
68✔
261
                        };
68✔
262

263
                        // Find the part for the primary fuel
264
                        double primaryPerMin = 0;
68✔
265
                        if (primaryFuelPart.energyGeneratedInMJ > 0)
68✔
266
                        {
68✔
267
                            // The rounding here is important to remove floating point errors that appear with some types
268
                            // (this is step 4 from above)
269
                            primaryPerMin = Math.Round(burnRateMJ / primaryFuelPart.energyGeneratedInMJ, 5);
68✔
270
                        }
68✔
271
                        double primaryAmount = 0;
68✔
272
                        if (primaryPerMin > 0)
68✔
273
                        {
68✔
274
                            primaryAmount = primaryPerMin / 60d;
68✔
275

276
                            List<PowerIngredient> ingredients = new()
68✔
277
                            {
68✔
278
                                new()
68✔
279
                                {
68✔
280
                                    part = fuelItem.primaryFuel,
68✔
281
                                    perMin = primaryPerMin,
68✔
282
                                    mwPerItem = building.Power / primaryPerMin
68✔
283
                                }
68✔
284
                            };
68✔
285

286
                            if (!string.IsNullOrEmpty(fuelItem.supplementaryFuel) && supplementalRatio > 0)
68✔
287
                            {
24✔
288
                                ingredients.Add(new PowerIngredient
24✔
289
                                {
24✔
290
                                    part = fuelItem.supplementaryFuel,
24✔
291
                                    perMin = (3d / 50d) * supplementalRatio * building.Power, // Calculate the ratio of the supplemental resource to the primary fuel
24✔
292
                                    supplementalRatio = (3d / 50d) * supplementalRatio
24✔
293
                                });
24✔
294
                            }
24✔
295

296
                            PowerProduct? products = null;
68✔
297
                            if (!string.IsNullOrEmpty(fuelItem.byProduct))
68✔
298
                            {
8✔
299
                                products = new PowerProduct
8✔
300
                                {
8✔
301
                                    part = fuelItem.byProduct,
8✔
302
                                    perMin = fuelItem.byProductAmountPerMin
8✔
303
                                };
8✔
304
                            }
8✔
305

306
                            recipes.Add(new PowerGenerationRecipe
68✔
307
                            {
68✔
308
                                id = Common.GetPowerGenerationRecipeName(className) + '_' + fuelItem.primaryFuel,
68✔
309
                                displayName = displayName + " (" + primaryFuelPart.name + ")",
68✔
310
                                ingredients = ingredients,
68✔
311
                                byproduct = products,
68✔
312
                                building = building
68✔
313
                            });
68✔
314
                            if (recipes[recipes.Count - 1].id == "GeneratorFuel_RocketFuel")
68✔
315
                            {
4✔
316
                                System.Diagnostics.Debug.Write("GeneratorFuel_RocketFuel");
4✔
317
                            }
4✔
318
                        }
68✔
319
                    }
68✔
320
                }
16✔
321
            }
11,280✔
322
           
323
            return recipes.OrderBy(r => r.displayName).ToList();
72✔
324
        }
4✔
325
    }
326
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc