• 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

85.05
/src/SatisfactoryTree.Logic/Extraction/FactoryCatalogExtractor.cs
1
using SatisfactoryTree.Logic.Models;
2
using System.Diagnostics;
3
using System.Text.Json;
4

5
namespace SatisfactoryTree.Logic.Extraction
6
{
7
    public class FactoryCatalogExtractor
8
    {
9
        public static string InputFile { get; set; } = "";
11✔
10
        public static string OutputFile { get; set; } = "";
20✔
11

12
        public static bool GetContentFiles()
13
        {
9✔
14
            // Load the content file
15
            string contentPath = @"C:\Program Files (x86)\Steam\steamapps\common\Satisfactory\CommunityResources\Docs\en-US.json";
9✔
16
            DirectoryInfo? currentDir = new(Directory.GetCurrentDirectory());
9✔
17
            DirectoryInfo? parentDir = currentDir.Parent?.Parent?.Parent?.Parent?.Parent;
9!
18
            if (parentDir == null)
9!
NEW
19
            {
×
NEW
20
                throw new Exception("Parent directory structure is not as expected.");
×
21
            }
22
            string projectContentPath = Path.Combine(parentDir.FullName, "content");
9✔
23
            string projectContentFile = Path.Combine(projectContentPath, "en-US.json");
9✔
24

25
            // If the file exists, copy it to the content folder, that is located in the content folder in the root of the project
26
            if (File.Exists(contentPath) &&
9!
27
                Directory.Exists(projectContentPath))
9✔
NEW
28
            {
×
29
                //Get the current directory
NEW
30
                Debug.WriteLine("Copying file to " + projectContentPath);
×
NEW
31
                File.Copy(contentPath, projectContentFile, true);
×
NEW
32
            }
×
33
            InputFile = projectContentFile;
9✔
34
            OutputFile = Path.Combine(projectContentPath, "gameData.json");
9✔
35
            return true;
9✔
36
        }
9✔
37

38
        public static async Task<FactoryCatalog> ProcessGameFile()
39
        {
1✔
40
            Stopwatch stopwatch = new();
1✔
41
            stopwatch.Start();
1✔
42
            //try
43
            //{
44
            //Read file contexts from text file
45
            GetContentFiles();
1✔
46
            string fileContent = File.ReadAllText(InputFile);
1✔
47
            List<dynamic>? rawData = System.Text.Json.JsonSerializer.Deserialize<List<dynamic>>(fileContent);
1✔
48
            List<JsonElement> data = new();
1✔
49
            List<JsonElement> rawResourcesData = new();
1✔
50
            if (rawData != null)
1✔
51
            {
1✔
52
                foreach (JsonElement entry in rawData)
227✔
53
                {
112✔
54
                    string? nativeClass = entry.TryGetProperty("NativeClass", out JsonElement nativeClassElement) ? nativeClassElement.GetString() : string.Empty;
112!
55
                    if (entry.TryGetProperty("Classes", out JsonElement classesElement) && classesElement.ValueKind == JsonValueKind.Array)
112!
56
                    {
112✔
57
                        data.AddRange(classesElement.EnumerateArray());
112✔
58
                    }
112✔
59
                }
112✔
60
            }
1✔
61

62
            // Get an array of all buildings that produce something
63
            List<string> producingBuildings = ProcessRawBuildings.GetProducingBuildings(data);
1✔
64

65
            // Get power consumption for the producing buildings
66
            Dictionary<string, double> buildings = ProcessRawBuildings.GetPowerConsumptionForBuildings(data, producingBuildings);
1✔
67

68
            // Pass the producing buildings with power data to getRecipes to calculate perMin and powerPerProduct
69
            List<Recipe> recipes = ProcessRawRecipes.GetProductionRecipes(data, buildings);
1✔
70

71
            // Get parts
72
            RawPartsAndRawMaterials items = ProcessRawParts.GetItems(data, recipes);
1✔
73
            ProcessRawParts.FixItemNames(items);
1✔
74
            ProcessRawParts.FixTurbofuel(items, recipes);
1✔
75

76
            // IMPORTANT: The order here matters - don't run this before fixing the turbofuel.
77
            List<PowerGenerationRecipe> powerGenerationRecipes = ProcessRawRecipes.GetPowerGeneratingRecipes(data, items, buildings);
1✔
78

79
            // Since we've done some manipulation of the items data, re-sort it
80
            Dictionary<string, Part> sortedItems = new();
1✔
81
            foreach (string? key in items.Parts.Keys.OrderBy(k => k))
507✔
82
            {
168✔
83
                sortedItems[key] = items.Parts[key];
168✔
84
            }
168✔
85
            items.Parts = sortedItems;
1✔
86

87
            //Build the new recipe collection
88
            List<Recipe> newRecipes = new();
1✔
89
            foreach (Recipe recipe in recipes)
585✔
90
            {
291✔
91
                newRecipes.Add(new()
291✔
92
                {
291✔
93
                    Name = recipe.Name,
291✔
94
                    DisplayName = recipe.DisplayName,
291✔
95
                    Ingredients = recipe.Ingredients,
291✔
96
                    Products = recipe.Products,
291✔
97
                    Building = recipe.Building,
291✔
98
                    IsAlternate = recipe.IsAlternate,
291✔
99
                    IsFicsmas = recipe.IsFicsmas,
291✔
100
                    UsesSAMOre = recipe.UsesSAMOre
291✔
101
                });
291✔
102
            }
291✔
103
            //Now add the power generation recipes
104
            foreach (PowerGenerationRecipe recipe in powerGenerationRecipes)
37✔
105
            {
17✔
106
                List<Ingredient> ingredients = new();
17✔
107
                foreach (PowerIngredient ingredient in recipe.ingredients)
97✔
108
                {
23✔
109
                    ingredients.Add(new Ingredient()
23✔
110
                    {
23✔
111
                        part = ingredient.part,
23✔
112
                        amount = ingredient.perMin,
23✔
113
                        perMin = ingredient.perMin,
23✔
114
                        mwPerItem = ingredient.mwPerItem,
23✔
115
                    });
23✔
116
                }
23✔
117
                List<Product> products = new();
17✔
118
                if (recipe.byproduct != null)
17✔
119
                {
2✔
120
                    products.Add(
2✔
121
                        new Product()
2✔
122
                        {
2✔
123
                            part = recipe.byproduct.part,
2✔
124
                            amount = recipe.byproduct.perMin,
2✔
125
                            perMin = recipe.byproduct.perMin,
2✔
126
                            isByProduct = true
2✔
127
                        }
2✔
128
                    );
2✔
129
                }
2✔
130

131
                // Check if any ingredient is SAMIngot to set usesSAMOre flag
132
                bool usesSAMOre = ingredients.Any(ingredient => ingredient.part == "SAMIngot");
40✔
133

134
                newRecipes.Add(new()
17✔
135
                {
17✔
136
                    Name = recipe.id,
17✔
137
                    DisplayName = recipe.displayName,
17✔
138
                    Ingredients = ingredients,
17✔
139
                    Products = products,
17✔
140
                    Building = recipe.building,
17✔
141
                    IsAlternate = false,
17✔
142
                    IsFicsmas = false,
17✔
143
                    UsesSAMOre = usesSAMOre
17✔
144
                });
17✔
145
            }
17✔
146
            //sort the new recipes list by id
147
            newRecipes = newRecipes.OrderBy(r => r.Name).ToList();
309✔
148

149
            // Construct the final JSON object
150
            FactoryCatalog factoryCatalog = new(
1✔
151
                buildings,
1✔
152
                items,
1✔
153
                recipes,
1✔
154
                powerGenerationRecipes);
1✔
155

156
            // Write the output to the file
157
            JsonSerializerOptions options = new() { WriteIndented = true, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull };
1✔
158
            string outputJson = System.Text.Json.JsonSerializer.Serialize(factoryCatalog, options);
1✔
159
            await File.WriteAllTextAsync(OutputFile, outputJson);
1✔
160
            stopwatch.Stop();
1✔
161

162
            System.Console.WriteLine($"Processed {items.Parts.Count} parts, {buildings.Count} buildings, and {recipes.Count} recipes, all written to {OutputFile}.");
1✔
163
            System.Console.WriteLine($"Total processing time: {stopwatch.Elapsed.TotalMilliseconds} ms");
1✔
164
            return factoryCatalog;
1✔
165
            //}
166
            //catch (Exception ex)
167
            //{
168
            //    System.Console.Error.WriteLine($"Error processing file: {ex.Message}");
169
            //    return null;
170
            //}
171
        }
1✔
172

173
        public static async Task<FactoryCatalog?> LoadDataFromFile()
174
        {
8✔
175
            try
176
            {
8✔
177
                GetContentFiles();
8✔
178
                string targetFile = OutputFile;             
8✔
179

180
                if (!File.Exists(targetFile))
8!
NEW
181
                {
×
NEW
182
                    throw new FileNotFoundException($"Configuration file not found: {targetFile}");
×
183
                }
184

185
                string jsonContent = await File.ReadAllTextAsync(targetFile);
8✔
186
                
187
                JsonSerializerOptions options = new() 
8✔
188
                { 
8✔
189
                    PropertyNameCaseInsensitive = true,
8✔
190
                    DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull 
8✔
191
                };
8✔
192
                
193
                FactoryCatalog? factoryCatalog = JsonSerializer.Deserialize<FactoryCatalog>(jsonContent, options);
8✔
194
                
195
                if (factoryCatalog == null)
8!
NEW
196
                {
×
NEW
197
                    throw new InvalidOperationException("Failed to deserialize the configuration file");
×
198
                }
199

200
                System.Console.WriteLine($"Successfully loaded data from {targetFile}");
8✔
201
                System.Console.WriteLine($"Loaded {factoryCatalog.Parts?.Count ?? 0} parts, {factoryCatalog.Buildings?.Count ?? 0} buildings, {factoryCatalog.Recipes?.Count ?? 0} recipes, and {factoryCatalog.PowerGenerationRecipes?.Count ?? 0} power generation recipes");
8!
202
                
203
                return factoryCatalog;
8✔
204
            }
NEW
205
            catch (Exception ex)
×
NEW
206
            {
×
NEW
207
                System.Console.Error.WriteLine($"Error loading data from file: {ex.Message}");
×
NEW
208
                return null;
×
209
            }
210
        }
8✔
211
    }
212
}
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