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

samsmithnz / SatisfactoryTree / 17631712519

11 Sep 2025 01:58AM UTC coverage: 90.507% (+37.6%) from 52.88%
17631712519

push

github

web-flow
Merge pull request #284 from samsmithnz/ReworkingLogicToHandleImportsAndExports

Reworking logic to handle imports and exports

298 of 344 branches covered (86.63%)

Branch coverage included in aggregate %.

951 of 1036 new or added lines in 28 files covered. (91.8%)

951 of 1036 relevant lines covered (91.8%)

861.84 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
        {
1✔
13
            List<Recipe> recipes = new();
1✔
14

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

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

64

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

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

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

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

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

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

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

162
                }
43✔
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
291!
166
                { 
291✔
167
                    Power = powerPerBuilding
291✔
168
                };
291✔
169

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

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

179
                //if (blacklist. producedIn)
180
                recipes.Add(new Recipe
291✔
181
                {
291✔
182
                    Name = Common.GetRecipeName(className),
291✔
183
                    DisplayName = displayName,
291✔
184
                    Ingredients = ingredients,
291✔
185
                    Products = products,
291✔
186
                    Building = building,
291✔
187
                    IsAlternate = displayName.Contains("Alternate"),
291✔
188
                    IsFicsmas = Common.IsFicsmas(displayName),
291✔
189
                    UsesSAMOre = usesSAMOre
291✔
190
                });
291✔
191
            }
291✔
192

193
            return recipes.OrderBy(r => r.DisplayName).ToList();
292✔
194
        }
1✔
195

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

200
            foreach (JsonElement entry in data)
5,643✔
201
            {
2,820✔
202
                string className = entry.GetProperty("ClassName").ToString();
2,820✔
203
                string? displayName = entry.TryGetProperty("mDisplayName", out JsonElement mDisplayName) ? mDisplayName.GetString() : string.Empty;
2,820✔
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;
2,820✔
209
                double powerProduction = 0;
2,820✔
210
                if (powerProductionJSON != null)
2,820✔
211
                {
2,820✔
212
                    double.TryParse(powerProductionJSON.ToString(), out powerProduction);
2,820✔
213
                }
2,820✔
214
                string? supplementalToPowerRatioJSON = entry.TryGetProperty("mSupplementalToPowerRatio", out JsonElement mSupplementalToPowerRatio) ? mSupplementalToPowerRatio.GetString() : string.Empty;
2,820✔
215
                double supplementalToPowerRatio = 0;
2,820✔
216
                if (supplementalToPowerRatioJSON != null)
2,820✔
217
                {
2,820✔
218
                    double.TryParse(supplementalToPowerRatioJSON.ToString(), out supplementalToPowerRatio);
2,820✔
219
                }
2,820✔
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;
2,820✔
223

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

229
                double supplementalRatio = supplementalToPowerRatio;
2,820✔
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);
2,820✔
235

236
                //1List<Ingredient> ingredients = new();
237
                if (fuelJSON.HasValue && fuelJSON.Value.ValueKind == JsonValueKind.Array)
2,820✔
238
                {
4✔
239
                    foreach (JsonElement fuel in fuelJSON.Value.EnumerateArray())
46✔
240
                    {
17✔
241

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

263
                        // Find the part for the primary fuel
264
                        double primaryPerMin = 0;
17✔
265
                        if (primaryFuelPart.EnergyGeneratedInMJ > 0)
17✔
266
                        {
17✔
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);
17✔
270
                        }
17✔
271
                        double primaryAmount = 0;
17✔
272
                        if (primaryPerMin > 0)
17✔
273
                        {
17✔
274
                            primaryAmount = primaryPerMin / 60d;
17✔
275

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

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

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

306
                            recipes.Add(new PowerGenerationRecipe
17✔
307
                            {
17✔
308
                                id = Common.GetPowerGenerationRecipeName(className) + '_' + fuelItem.primaryFuel,
17✔
309
                                displayName = displayName + " (" + primaryFuelPart.Name + ")",
17✔
310
                                ingredients = ingredients,
17✔
311
                                byproduct = products,
17✔
312
                                building = building
17✔
313
                            });
17✔
314
                            if (recipes[recipes.Count - 1].id == "GeneratorFuel_RocketFuel")
17✔
315
                            {
1✔
316
                                System.Diagnostics.Debug.Write("GeneratorFuel_RocketFuel");
1✔
317
                            }
1✔
318
                        }
17✔
319
                    }
17✔
320
                }
4✔
321
            }
2,820✔
322
           
323
            return recipes.OrderBy(r => r.displayName).ToList();
18✔
324
        }
1✔
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

© 2025 Coveralls, Inc