Skip to content

Commit

Permalink
Add an option for net production instead of gross production when cho…
Browse files Browse the repository at this point in the history
…osing recipes (#135)

For example, with the box checked, Kovarex enrichment won't show up in the dropdown for selecting recipes when
attempting to consume U-235, nor when attempting to produce U-238 because the net stats of Kovarex are consuming U0238 and producing U-235.
  • Loading branch information
shpaass authored May 23, 2024
2 parents d2a36bf + dd8b07f commit 1e39ff6
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 14 deletions.
2 changes: 1 addition & 1 deletion CommandLineToolExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static void Main(string[] args) {
// Empty project path loads default project (with one empty page).
// Project is irrelevant if you just need data, but you need it to perform sheet calculations
// Set to not render any icons
project = FactorioDataSource.Parse(factorioPath, "", "", false, new ConsoleProgressReport(), errorCollector, "en", false);
project = FactorioDataSource.Parse(factorioPath, "", "", false, false, new ConsoleProgressReport(), errorCollector, "en", false);
}
catch (Exception ex) {
// Critical errors that make project un-loadable will be thrown as exceptions
Expand Down
7 changes: 7 additions & 0 deletions Yafc.Model/Data/DataUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ public static Bits GetMilestoneOrder(FactorioId id) {
public static string dataPath { get; internal set; }
public static string modsPath { get; internal set; }
public static bool expensiveRecipes { get; internal set; }
/// <summary>
/// If <see langword="true"/>, recipe selection windows will only display recipes that provide net production or consumption of the <see cref="Goods"/> in question.
/// If <see langword="false"/>, recipe selection windows will show all recipes that produce or consume any quantity of that <see cref="Goods"/>.<br/>
/// For example, Kovarex enrichment will appear for both production and consumption of both U-235 and U-238 when <see langword="false"/>,
/// but will appear as only producing U-235 and consuming U-238 when <see langword="true"/>.
/// </summary>
public static bool netProduction { get; internal set; }
public static string[] allMods { get; internal set; }
public static Icon NoFuelIcon { get; internal set; }
public static Icon WarningIcon { get; internal set; }
Expand Down
18 changes: 16 additions & 2 deletions Yafc.Parser/Data/FactorioDataDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,21 @@ private static void AddTemperatureToFluidIcon(Fluid fluid) {
];
}

public Project LoadData(string projectPath, LuaTable data, LuaTable prototypes, IProgress<(string, string)> progress, ErrorCollector errorCollector, bool renderIcons) {
/// <summary>
/// Process the data loaded from Factorio and the mods, and load the project specified by <paramref name="projectPath"/>.
/// </summary>
/// <param name="projectPath">The path to the project file to create or load. May be <see langword="null"/> or empty.</param>
/// <param name="data">The Lua table data (containing data.raw) that was populated by the lua scripts.</param>
/// <param name="prototypes">The Lua table defines.prototypes that was populated by the lua scripts.</param>
/// <param name="netProduction">If <see langword="true"/>, recipe selection windows will only display recipes that provide net production or consumption of the <see cref="Goods"/> in question.
/// If <see langword="false"/>, recipe selection windows will show all recipes that produce or consume any quantity of that <see cref="Goods"/>.<br/>
/// For example, Kovarex enrichment will appear for both production and consumption of both U-235 and U-238 when <see langword="false"/>,
/// but will appear as only producing U-235 and consuming U-238 when <see langword="true"/>.</param>
/// <param name="progress">An <see cref="IProgress{T}"/> that receives two strings describing the current loading state.</param>
/// <param name="errorCollector">An <see cref="ErrorCollector"/> that will collect the errors and warnings encountered while loading and processing the file and data.</param>
/// <param name="renderIcons">If <see langword="true"/>, Yafc will render the icons necessary for UI display.</param>
/// <returns>A <see cref="Project"/> containing the information loaded from <paramref name="projectPath"/>. Also sets the <see langword="static"/> properties in <see cref="Database"/>.</returns>
public Project LoadData(string projectPath, LuaTable data, LuaTable prototypes, bool netProduction, IProgress<(string, string)> progress, ErrorCollector errorCollector, bool renderIcons) {
progress.Report(("Loading", "Loading items"));
raw = (LuaTable)data["raw"];
foreach (object prototypeName in ((LuaTable)prototypes["item"]).ObjectElements.Keys) {
Expand Down Expand Up @@ -127,7 +141,7 @@ Func<Item, bool> AllowedModulesFilter(Recipe key) {
var iconRenderTask = renderIcons ? Task.Run(RenderIcons) : Task.CompletedTask;
UpdateRecipeIngredientFluids();
UpdateRecipeCatalysts();
CalculateMaps();
CalculateMaps(netProduction);
ExportBuiltData();
progress.Report(("Post-processing", "Calculating dependencies"));
Dependencies.Calculate();
Expand Down
25 changes: 21 additions & 4 deletions Yafc.Parser/Data/FactorioDataDeserializer_Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,14 @@ private bool IsBarrelingRecipe(Recipe barreling, Recipe unbarreling) {
return true;
}

private void CalculateMaps() {
/// <summary>
/// Locates and stores all the links between different objects, e.g. which crafters can be used by a recipe, which recipes produce a particular product, and so on.
/// </summary>
/// <param name="netProduction">If <see langword="true"/>, recipe selection windows will only display recipes that provide net production or consumption of the <see cref="Goods"/> in question.
/// If <see langword="false"/>, recipe selection windows will show all recipes that produce or consume any quantity of that <see cref="Goods"/>.<br/>
/// For example, Kovarex enrichment will appear for both production and consumption of both U-235 and U-238 when <see langword="false"/>,
/// but will appear as only producing U-235 and consuming U-238 when <see langword="true"/>.</param>
private void CalculateMaps(bool netProduction) {
DataBucket<Goods, Recipe> itemUsages = new DataBucket<Goods, Recipe>();
DataBucket<Goods, Recipe> itemProduction = new DataBucket<Goods, Recipe>();
DataBucket<Goods, FactorioObject> miscSources = new DataBucket<Goods, FactorioObject>();
Expand All @@ -221,16 +228,26 @@ private void CalculateMaps() {
case Recipe recipe:
allRecipes.Add(recipe);
foreach (var product in recipe.products) {
if (product.amount > 0) {
// If the ingredient has variants and is an output, we aren't doing catalyst things: water@15-90 to water@90 does produce water@90,
// even if it consumes 10 water@15-90 to produce 9 water@90.
Ingredient ingredient = recipe.ingredients.FirstOrDefault(i => i.goods == product.goods && i.variants is null);
float inputAmount = netProduction ? (ingredient?.amount ?? 0) : 0;
float outputAmount = product.amount;
if (outputAmount > inputAmount) {
itemProduction.Add(product.goods, recipe);
}
}

foreach (var ingredient in recipe.ingredients) {
if (ingredient.variants == null) {
// The reverse also applies. 9 water@15-90 to produce 10 water@15 consumes water@90, even though it's a net water producer.
float inputAmount = ingredient.amount;
Product product = ingredient.variants is null ? recipe.products.FirstOrDefault(p => p.goods == ingredient.goods) : null;
float outputAmount = netProduction ? (product?.amount ?? 0) : 0;

if (ingredient.variants == null && inputAmount > outputAmount) {
itemUsages.Add(ingredient.goods, recipe);
}
else {
else if (ingredient.variants != null) {
ingredient.goods = ingredient.variants[0];
foreach (var variant in ingredient.variants) {
itemUsages.Add(variant, recipe);
Expand Down
23 changes: 21 additions & 2 deletions Yafc.Parser/FactorioDataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,25 @@ private static void FindMods(string directory, IProgress<(string, string)> progr
}
}

public static Project Parse(string factorioPath, string modPath, string projectPath, bool expensive, IProgress<(string, string)> progress, ErrorCollector errorCollector, string locale, bool renderIcons = true) {
/// <summary>
/// Create or load the file <paramref name="projectPath"/> (if specified), with the Factorio data at <paramref name="factorioPath"/> and <paramref name="modPath"/>.
/// </summary>
/// <param name="factorioPath">The path to the data/ folder, containing the base and core folders.</param>
/// <param name="modPath">The path to the mods/ folder, containing mod-list.json and the mods. Both zipped and unzipped mods are supported. May be empty (but not <see langword="null"/>)
/// to load only vanilla Factorio data.</param>
/// <param name="projectPath">The path to the project file to create or load. May be <see langword="null"/> or empty.</param>
/// <param name="expensive">Whether to use expensive recipes.</param>
/// <param name="netProduction">If <see langword="true"/>, recipe selection windows will only display recipes that provide net production or consumption of the <see cref="Goods"/> in question.
/// If <see langword="false"/>, recipe selection windows will show all recipes that produce or consume any quantity of that <see cref="Goods"/>.<br/>
/// For example, Kovarex enrichment will appear for both production and consumption of both U-235 and U-238 when <see langword="false"/>,
/// but will appear as only producing U-235 and consuming U-238 when <see langword="true"/>.</param>
/// <param name="progress">An <see cref="IProgress{T}"/> that receives two strings describing the current loading state.</param>
/// <param name="errorCollector">An <see cref="ErrorCollector"/> that will collect the errors and warnings encountered while loading and processing the file and data.</param>
/// <param name="locale">One of the languages supported by Factorio. Typically just the two-letter language code, e.g. en, but occasionally also includes the region code, e.g. pt-PT.</param>
/// <param name="renderIcons">If <see langword="true"/>, Yafc will render the icons necessary for UI display.</param>
/// <returns>A <see cref="Project"/> containing the information loaded from <paramref name="projectPath"/>. Also sets the <see langword="static"/> properties in <see cref="Database"/>.</returns>
/// <exception cref="NotSupportedException">Thrown if a mod enabled in mod-list.json could not be found in <paramref name="modPath"/>.</exception>
public static Project Parse(string factorioPath, string modPath, string projectPath, bool expensive, bool netProduction, IProgress<(string MajorState, string MinorState)> progress, ErrorCollector errorCollector, string locale, bool renderIcons = true) {
LuaContext dataContext = null;
try {
currentLoadingMod = null;
Expand Down Expand Up @@ -257,6 +275,7 @@ public static Project Parse(string factorioPath, string modPath, string projectP
DataUtils.dataPath = factorioPath;
DataUtils.modsPath = modPath;
DataUtils.expensiveRecipes = expensive;
DataUtils.netProduction = netProduction;


dataContext = new LuaContext();
Expand All @@ -283,7 +302,7 @@ public static Project Parse(string factorioPath, string modPath, string projectP
currentLoadingMod = null;

FactorioDataDeserializer deserializer = new FactorioDataDeserializer(expensive, factorioVersion ?? defaultFactorioVersion);
var project = deserializer.LoadData(projectPath, dataContext.data, dataContext.defines["prototypes"] as LuaTable, progress, errorCollector, renderIcons);
var project = deserializer.LoadData(projectPath, dataContext.data, dataContext.defines["prototypes"] as LuaTable, netProduction, progress, errorCollector, renderIcons);
Console.WriteLine("Completed!");
progress.Report(("Completed!", ""));
return project;
Expand Down
11 changes: 9 additions & 2 deletions Yafc/Utils/Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ public void Save() {
public string language { get; set; } = "en";
public string overrideFont { get; set; }

public void AddProject(string path, string dataPath, string modsPath, bool expensiveRecipes) {
public void AddProject(string path, string dataPath, string modsPath, bool expensiveRecipes, bool netProduction) {
recentProjects = recentProjects.Where(x => string.Compare(path, x.path, StringComparison.InvariantCultureIgnoreCase) != 0)
.Prepend(new ProjectDefinition { path = path, modsPath = modsPath, dataPath = dataPath, expensive = expensiveRecipes }).ToArray();
.Prepend(new ProjectDefinition { path = path, modsPath = modsPath, dataPath = dataPath, expensive = expensiveRecipes, netProduction = netProduction }).ToArray();
Save();
}
}
Expand All @@ -58,5 +58,12 @@ public class ProjectDefinition {
public string dataPath { get; set; }
public string modsPath { get; set; }
public bool expensive { get; set; }
/// <summary>
/// If <see langword="true"/>, recipe selection windows will only display recipes that provide net production or consumption of the <see cref="Goods"/> in question.
/// If <see langword="false"/>, recipe selection windows will show all recipes that produce or consume any quantity of that <see cref="Goods"/>.<br/>
/// For example, Kovarex enrichment will appear for both production and consumption of both U-235 and U-238 when <see langword="false"/>,
/// but will appear as only producing U-235 and consuming U-238 when <see langword="true"/>.
/// </summary>
public bool netProduction { get; set; }
}
}
2 changes: 1 addition & 1 deletion Yafc/Windows/MainScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ private async Task<bool> SaveProjectAs() {
FilesystemScreen.Mode.SelectOrCreateFile, "project", this, null, "yafc");
if (path != null) {
project.Save(path);
Preferences.Instance.AddProject(path, DataUtils.dataPath, DataUtils.modsPath, DataUtils.expensiveRecipes);
Preferences.Instance.AddProject(path, DataUtils.dataPath, DataUtils.modsPath, DataUtils.expensiveRecipes, DataUtils.netProduction);
return true;
}

Expand Down
11 changes: 9 additions & 2 deletions Yafc/Windows/WelcomeScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class WelcomeScreen : WindowUtility, IProgress<(string, string)> {
private string currentLoad1, currentLoad2;
private string path = "", dataPath = "", modsPath = "";
private bool expensive;
private bool netProduction;
private string createText;
private bool canCreate;
private readonly ScrollArea errorScroll;
Expand Down Expand Up @@ -137,6 +138,10 @@ protected override void BuildContents(ImGui gui) {
gui.BuildText("In-game objects language:");
}

using (gui.EnterRow()) {
gui.BuildCheckBox("Use net production/consumption when analyzing recipes", netProduction, out netProduction);
}

using (gui.EnterRow()) {
if (Preferences.Instance.recentProjects.Length > 1) {
if (gui.BuildButton("Recent projects", SchemeColor.Grey)) {
Expand Down Expand Up @@ -281,12 +286,14 @@ private void BuildPathSelect(ImGui gui, ref string path, string description, str
private void SetProject(ProjectDefinition project) {
if (project != null) {
expensive = project.expensive;
netProduction = project.netProduction;
modsPath = project.modsPath ?? "";
path = project.path ?? "";
dataPath = project.dataPath ?? "";
}
else {
expensive = false;
netProduction = false;
modsPath = "";
path = "";
dataPath = "";
Expand All @@ -305,7 +312,7 @@ private void SetProject(ProjectDefinition project) {
private async void LoadProject() {
try {
var (dataPath, modsPath, projectPath, expensiveRecipes) = (this.dataPath, this.modsPath, path, expensive);
Preferences.Instance.AddProject(projectPath, dataPath, modsPath, expensiveRecipes);
Preferences.Instance.AddProject(projectPath, dataPath, modsPath, expensiveRecipes, netProduction);
Preferences.Instance.Save();
tip = tips.Length > 0 ? tips[DataUtils.random.Next(tips.Length)] : "";

Expand All @@ -314,7 +321,7 @@ private async void LoadProject() {

await Ui.ExitMainThread();
ErrorCollector collector = new ErrorCollector();
var project = FactorioDataSource.Parse(dataPath, modsPath, projectPath, expensiveRecipes, this, collector, Preferences.Instance.language);
var project = FactorioDataSource.Parse(dataPath, modsPath, projectPath, expensiveRecipes, netProduction, this, collector, Preferences.Instance.language);
await Ui.EnterMainThread();
Console.WriteLine("Opening main screen");
_ = new MainScreen(displayIndex, project);
Expand Down
2 changes: 2 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Date: soon
- Add the option to specify a number of belts of production, and to specify per-second/minute/hour
production regardless of the current display setting.
- When searching in the page list, allow searching in page contents as well as in page names.
- Allow the user to select whether catalysts should be considered produced and consumed by the recipes that
use them. (e.g. Does coal liquefaction consume heavy oil?)
Changes:
- Add a help message and proper handling for command line arguments
- Removed default pollution cost from calculation. Added a setting to customize pollution cost.
Expand Down

0 comments on commit 1e39ff6

Please sign in to comment.