diff --git a/Yafc.Model/Analysis/Analysis.cs b/Yafc.Model/Analysis/Analysis.cs index a28b8f32..d0be4317 100644 --- a/Yafc.Model/Analysis/Analysis.cs +++ b/Yafc.Model/Analysis/Analysis.cs @@ -68,10 +68,6 @@ public static float ApproximateFlow(this FactorioObject recipe, bool atCurrentMi return CostAnalysis.Get(atCurrentMilestones).flow[recipe]; } - public static float ProductCost(this Recipe recipe, bool atCurrentMilestones = false) { - return CostAnalysis.Get(atCurrentMilestones).recipeProductCost[recipe]; - } - public static float RecipeWaste(this Recipe recipe, bool atCurrentMilestones = false) { return CostAnalysis.Get(atCurrentMilestones).recipeWastePercentage[recipe]; } diff --git a/Yafc.Model/Analysis/CostAnalysis.cs b/Yafc.Model/Analysis/CostAnalysis.cs index c75ffc60..a2515722 100644 --- a/Yafc.Model/Analysis/CostAnalysis.cs +++ b/Yafc.Model/Analysis/CostAnalysis.cs @@ -32,7 +32,6 @@ public static CostAnalysis Get(bool atCurrentMilestones) { public Mapping cost; public Mapping recipeCost; - public Mapping recipeProductCost; public Mapping flow; public Mapping recipeWastePercentage; public Goods[]? importantItems; @@ -40,6 +39,10 @@ public static CostAnalysis Get(bool atCurrentMilestones) { private string? itemAmountPrefix; private bool ShouldInclude(FactorioObject obj) { + // Consider all recipes + if (obj is Recipe) { + return true; + } return onlyCurrentMilestones ? obj.IsAutomatableWithCurrentMilestones() : obj.IsAutomatable(); } @@ -103,7 +106,6 @@ public override void Compute(Project project, ErrorCollector warnings) { } var export = Database.objects.CreateMapping(); - var recipeProductionCost = Database.recipesAndTechnologies.CreateMapping(); recipeCost = Database.recipes.CreateMapping(); flow = Database.objects.CreateMapping(); var lastVariable = Database.goods.CreateMapping(); @@ -230,6 +232,12 @@ public override void Compute(Project project, ErrorCollector warnings) { constraint.SetUb(logisticsCost); export[recipe] = logisticsCost; recipeCost[recipe] = logisticsCost; + + // export controls the value shown by the "There are better recipes to create X (wasting Y% of YAFC cost)" string. + // recipeCost controls the decisions made by the solver. We only want to affect the solver. + if (onlyCurrentMilestones ? !recipe.IsAccessibleWithCurrentMilestones() : !recipe.IsAccessible()) { + recipeCost[recipe] *= project.settings.InaccessibleRecipePenalty; + } } // TODO this is temporary fix for strange item sources (make the cost of item not higher than the cost of its source) @@ -298,11 +306,9 @@ public override void Compute(Project project, ErrorCollector warnings) { if (o is RecipeOrTechnology recipe) { foreach (var ingredient in recipe.ingredients) // TODO split { - export[o] += export[ingredient.goods] * ingredient.amount; - } - - foreach (var product in recipe.products) { - recipeProductionCost[recipe] += product.amount * export[product.goods]; + if (float.IsFinite(export[ingredient.goods])) { + export[o] += export[ingredient.goods] * ingredient.amount; + } } } else if (o is Entity entity) { @@ -316,7 +322,6 @@ public override void Compute(Project project, ErrorCollector warnings) { } } cost = export; - recipeProductCost = recipeProductionCost; recipeWastePercentage = Database.recipes.CreateMapping(); if (result is Solver.ResultStatus.OPTIMAL or Solver.ResultStatus.FEASIBLE) { diff --git a/Yafc.Model/Model/ProductionTable.cs b/Yafc.Model/Model/ProductionTable.cs index a9e65c3a..77153d3c 100644 --- a/Yafc.Model/Model/ProductionTable.cs +++ b/Yafc.Model/Model/ProductionTable.cs @@ -468,10 +468,10 @@ private static void AddLinkCoef(Constraint cst, Variable var, ProductionLink lin RecipeRow? ownerRecipe = link.owner.owner as RecipeRow; while (ownerRecipe != null) { if (link.notMatchedFlow > 0f) { - ownerRecipe.parameters.warningFlags |= WarningFlags.OverproductionRequired; + ownerRecipe.parameters = ownerRecipe.parameters.WithFlag(WarningFlags.OverproductionRequired); } else { - ownerRecipe.parameters.warningFlags |= WarningFlags.DeadlockCandidate; + ownerRecipe.parameters = ownerRecipe.parameters.WithFlag(WarningFlags.DeadlockCandidate); } ownerRecipe = ownerRecipe.owner.owner as RecipeRow; @@ -483,10 +483,10 @@ private static void AddLinkCoef(Constraint cst, Variable var, ProductionLink lin foreach (var link in linkList) { if (link.flags.HasFlags(ProductionLink.Flags.LinkRecursiveNotMatched)) { if (link.notMatchedFlow > 0f) { - recipe.parameters.warningFlags |= WarningFlags.OverproductionRequired; + recipe.parameters = recipe.parameters.WithFlag(WarningFlags.OverproductionRequired); } else { - recipe.parameters.warningFlags |= WarningFlags.DeadlockCandidate; + recipe.parameters = recipe.parameters.WithFlag(WarningFlags.DeadlockCandidate); } } } @@ -544,12 +544,12 @@ private bool CheckBuiltCountExceeded() { for (int i = 0; i < recipes.Count; i++) { var recipe = recipes[i]; if (recipe.buildingCount > recipe.builtBuildings) { - recipe.parameters.warningFlags |= WarningFlags.ExceedsBuiltCount; + recipe.parameters = recipe.parameters.WithFlag(WarningFlags.ExceedsBuiltCount); builtCountExceeded = true; } else if (recipe.subgroup != null) { if (recipe.subgroup.CheckBuiltCountExceeded()) { - recipe.parameters.warningFlags |= WarningFlags.ExceedsBuiltCount; + recipe.parameters = recipe.parameters.WithFlag(WarningFlags.ExceedsBuiltCount); builtCountExceeded = true; } } diff --git a/Yafc.Model/Model/Project.cs b/Yafc.Model/Model/Project.cs index 0d2e8ff0..c3c98a5f 100644 --- a/Yafc.Model/Model/Project.cs +++ b/Yafc.Model/Model/Project.cs @@ -212,6 +212,7 @@ public class ProjectSettings(Project project) : ModelObject(project) { public int reactorSizeX { get; set; } = 2; public int reactorSizeY { get; set; } = 2; public float PollutionCostModifier { get; set; } = 0; + public float InaccessibleRecipePenalty { get; set; } = 100; public event Action? changed; protected internal override void ThisChanged(bool visualOnly) { changed?.Invoke(visualOnly); diff --git a/Yafc.Model/Model/RecipeParameters.cs b/Yafc.Model/Model/RecipeParameters.cs index ef9f19ea..ba7003d3 100644 --- a/Yafc.Model/Model/RecipeParameters.cs +++ b/Yafc.Model/Model/RecipeParameters.cs @@ -31,14 +31,8 @@ public struct UsedModule { public int beaconCount; } - internal class RecipeParameters(float recipeTime, float fuelUsagePerSecondPerBuilding, float productivity, WarningFlags warningFlags, ModuleEffects activeEffects, UsedModule modules) { + internal sealed record RecipeParameters(float recipeTime, float fuelUsagePerSecondPerBuilding, float productivity, WarningFlags warningFlags, ModuleEffects activeEffects, UsedModule modules) { public const float MIN_RECIPE_TIME = 1f / 60; - public float recipeTime { get; } = recipeTime; - public float fuelUsagePerSecondPerBuilding { get; } = fuelUsagePerSecondPerBuilding; - public float productivity { get; } = productivity; - public WarningFlags warningFlags { get; internal set; } = warningFlags; - public ModuleEffects activeEffects { get; } = activeEffects; - public UsedModule modules { get; } = modules; public static RecipeParameters Empty = new(0, 0, 0, 0, default, default); @@ -182,5 +176,7 @@ public static RecipeParameters CalculateParameters(RecipeRow row) { return new RecipeParameters(recipeTime, fuelUsagePerSecondPerBuilding, productivity, warningFlags, activeEffects, modules); } + + internal RecipeParameters WithFlag(WarningFlags additionalFlag) => this with { warningFlags = warningFlags | additionalFlag }; } } diff --git a/Yafc.UI/Core/ExceptionScreen.cs b/Yafc.UI/Core/ExceptionScreen.cs index 1d0bb016..f62396d1 100644 --- a/Yafc.UI/Core/ExceptionScreen.cs +++ b/Yafc.UI/Core/ExceptionScreen.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using SDL2; using Serilog; @@ -33,7 +34,7 @@ protected internal override void Close() { exists = false; } - protected override void BuildContents(ImGui gui) { + protected override Task BuildContents(ImGui gui) { gui.BuildText(ex.GetType().Name, Font.header); gui.BuildText(ex.Message, new TextBlockDisplayStyle(Font.subheader, true)); gui.BuildText(ex.StackTrace, TextBlockDisplayStyle.WrappedText); @@ -50,6 +51,8 @@ protected override void BuildContents(ImGui gui) { _ = SDL.SDL_SetClipboardText(ex.Message + "\n\n" + ex.StackTrace); } } + + return Task.CompletedTask; } } } diff --git a/Yafc.UI/Core/Window.cs b/Yafc.UI/Core/Window.cs index 21c8dfdc..9b2e3222 100644 --- a/Yafc.UI/Core/Window.cs +++ b/Yafc.UI/Core/Window.cs @@ -1,5 +1,6 @@ using System; using System.Numerics; +using System.Threading.Tasks; using SDL2; using Serilog; @@ -201,12 +202,12 @@ public void ShowDropDown(ImGui targetGui, Rect target, GuiBuilder builder, Paddi ShowDropDown(simpleDropDown); } - private void Build(ImGui gui) { + private async void Build(ImGui gui) { if (closed) { return; } - BuildContents(gui); + await BuildContents(gui); if (dropDown != null) { dropDown.Build(gui); if (!dropDown.active) { @@ -222,7 +223,7 @@ private void Build(ImGui gui) { } } - protected abstract void BuildContents(ImGui gui); + protected abstract Task BuildContents(ImGui gui); public virtual void Dispose() => rootGui.Dispose(); internal ImGui.DragOverlay GetDragOverlay() => draggingOverlay ??= new ImGui.DragOverlay(); diff --git a/Yafc.UI/Core/WindowMain.cs b/Yafc.UI/Core/WindowMain.cs index a1fa296f..9bbd0590 100644 --- a/Yafc.UI/Core/WindowMain.cs +++ b/Yafc.UI/Core/WindowMain.cs @@ -1,6 +1,7 @@ using System; using System.Numerics; using System.Runtime.InteropServices; +using System.Threading.Tasks; using SDL2; using Serilog; @@ -35,12 +36,12 @@ protected void Create(string title, int display, float initialWidth, float initi base.Create(); } - protected override void BuildContents(ImGui gui) { - BuildContent(gui); + protected override async Task BuildContents(ImGui gui) { + await BuildContent(gui); gui.SetContextRect(new Rect(default, size)); } - protected abstract void BuildContent(ImGui gui); + protected abstract Task BuildContent(ImGui gui); protected override void OnRepaint() { rootGui.Rebuild(); diff --git a/Yafc.UI/ImGui/ImGuiTextInputHelper.cs b/Yafc.UI/ImGui/ImGuiTextInputHelper.cs index 37bae168..9c4b9d9c 100644 --- a/Yafc.UI/ImGui/ImGuiTextInputHelper.cs +++ b/Yafc.UI/ImGui/ImGuiTextInputHelper.cs @@ -63,6 +63,9 @@ public bool BuildTextInput(string? text, out string newText, string? placeholder if (displayStyle.Icon != Icon.None) { gui.BuildIcon(displayStyle.Icon, lineSize, (SchemeColor)displayStyle.ColorGroup + 3); } + if (displayStyle.Prefix != null) { + gui.BuildText(displayStyle.Prefix); + } textRect = gui.RemainingRow(0.3f).AllocateRect(0, lineSize, RectAlignment.MiddleFullRow); } diff --git a/Yafc.UI/ImGui/TextDisplayStyles.cs b/Yafc.UI/ImGui/TextDisplayStyles.cs index 56f47051..7d4831db 100644 --- a/Yafc.UI/ImGui/TextDisplayStyles.cs +++ b/Yafc.UI/ImGui/TextDisplayStyles.cs @@ -43,7 +43,8 @@ public record TextBlockDisplayStyle(Font? Font = null, bool WrapText = false, Re /// The to place between the text and the edges of the editable area. (The box area not used by .) /// The to apply when drawing the text within the edit box. /// The to use when drawing the edit box. -public record TextBoxDisplayStyle(Icon Icon, Padding Padding, RectAlignment Alignment, SchemeColorGroup ColorGroup) { +/// The text to display inside the textbox, before the value. +public record TextBoxDisplayStyle(Icon Icon, Padding Padding, RectAlignment Alignment, SchemeColorGroup ColorGroup, string? Prefix = null) { /// /// Gets the default display style, used for the Preferences screen and calls to . /// diff --git a/Yafc/Windows/AboutScreen.cs b/Yafc/Windows/AboutScreen.cs index ca9570aa..5d39e22f 100644 --- a/Yafc/Windows/AboutScreen.cs +++ b/Yafc/Windows/AboutScreen.cs @@ -1,4 +1,5 @@ -using Yafc.UI; +using System.Threading.Tasks; +using Yafc.UI; namespace Yafc { public class AboutScreen : WindowUtility { @@ -6,7 +7,7 @@ public class AboutScreen : WindowUtility { public AboutScreen(Window parent) : base(ImGuiUtils.DefaultScreenPadding) => Create("About YAFC-CE", 50, parent); - protected override void BuildContents(ImGui gui) { + protected override Task BuildContents(ImGui gui) { gui.allocator = RectAllocator.Center; gui.BuildText("Yet Another Factorio Calculator", new TextBlockDisplayStyle(Font.header, Alignment: RectAlignment.Middle)); gui.BuildText("(Community Edition)", TextBlockDisplayStyle.Centered); @@ -66,6 +67,8 @@ protected override void BuildContents(ImGui gui) { gui.allocator = RectAllocator.Center; gui.BuildText("Factorio name, content and materials are trademarks and copyrights of Wube Software"); BuildLink(gui, "https://factorio.com/"); + + return Task.CompletedTask; } private void BuildLink(ImGui gui, string url, string? text = null) { diff --git a/Yafc/Windows/FilesystemScreen.cs b/Yafc/Windows/FilesystemScreen.cs index 5cc47bdf..862f34ec 100644 --- a/Yafc/Windows/FilesystemScreen.cs +++ b/Yafc/Windows/FilesystemScreen.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Numerics; +using System.Threading.Tasks; using SDL2; using Yafc.UI; @@ -42,7 +43,7 @@ public FilesystemScreen(string? header, string description, string button, strin previousFocus = InputSystem.Instance.SetDefaultKeyboardFocus(this); } - protected override void BuildContents(ImGui gui) { + protected override Task BuildContents(ImGui gui) { gui.BuildText(description, TextBlockDisplayStyle.WrappedText); if (gui.BuildTextInput(location, out string newLocation, null)) { if (Directory.Exists(newLocation)) { @@ -62,6 +63,8 @@ protected override void BuildContents(ImGui gui) { } } } + + return Task.CompletedTask; } private void BuildSelectButton(ImGui gui) { diff --git a/Yafc/Windows/MainScreen.cs b/Yafc/Windows/MainScreen.cs index 184481ea..d9888b52 100644 --- a/Yafc/Windows/MainScreen.cs +++ b/Yafc/Windows/MainScreen.cs @@ -95,23 +95,27 @@ private void SetProject(Project project) { _ = InputSystem.Instance.SetDefaultKeyboardFocus(this); } - private void ProjectSettingsChanged(bool visualOnly) { + private async void ProjectSettingsChanged(bool visualOnly) { if (visualOnly) { return; } if (topScreen == null) { - ReRunAnalysis(); + await ReRunAnalysis(); } else { analysisUpdatePending = true; } } - private void ReRunAnalysis() { + private async Task ReRunAnalysis() { analysisUpdatePending = false; ErrorCollector collector = new ErrorCollector(); Analysis.ProcessAnalyses(this, project, collector); + project.pages.ForEach(p => p.SetToRecalculate()); + Task primary = (activePage?.RunSolveJob() ?? Task.CompletedTask); + await (secondaryPage?.RunSolveJob() ?? Task.CompletedTask); + await primary; rootGui.MarkEverythingForRebuild(); if (collector.severity > ErrorSeverity.None) { ErrorListPanel.Show(collector); @@ -205,7 +209,7 @@ public void RebuildProjectView() { secondaryPageView?.bodyContent.MarkEverythingForRebuild(); } - protected override void BuildContent(ImGui gui) { + protected override async Task BuildContent(ImGui gui) { if (pseudoScreens.Count > 0) { var top = pseudoScreens[0]; if (gui.isBuilding) { @@ -224,7 +228,7 @@ protected override void BuildContent(ImGui gui) { _ = InputSystem.Instance.SetDefaultKeyboardFocus(this); topScreen = null; if (analysisUpdatePending) { - ReRunAnalysis(); + await ReRunAnalysis(); } } BuildTabBar(gui); diff --git a/Yafc/Windows/PreferencesScreen.cs b/Yafc/Windows/PreferencesScreen.cs index 1c1a4382..ba845c98 100644 --- a/Yafc/Windows/PreferencesScreen.cs +++ b/Yafc/Windows/PreferencesScreen.cs @@ -56,6 +56,18 @@ public override void Build(ImGui gui) { } } + using (gui.EnterRowWithHelpIcon($""" + Yafc considers all enabled recipes on your production sheets, even if they are inaccessible. The cost of inaccessible recipes will be multiplied by this amount, to discourage Yafc from using them. + For example, an inaccessible ¥100 recipe will be treated as if it costs ¥{DataUtils.FormatAmount(100 * settings.InaccessibleRecipePenalty, UnitOfMeasure.None)}. + """)) { + gui.BuildText("Cost penalty for inaccessible recipes:", topOffset: 0.5f); + DisplayAmount amount = new(settings.InaccessibleRecipePenalty, UnitOfMeasure.None); + if (gui.BuildFloatInput(amount, TextBoxDisplayStyle.DefaultTextInput with { Prefix = "×" }) && amount.Value >= 1) { + settings.RecordUndo().InaccessibleRecipePenalty = amount.Value; + gui.Rebuild(); + } + } + ChooseObject(gui, "Default belt:", Database.allBelts, prefs.defaultBelt, s => { prefs.RecordUndo().defaultBelt = s; gui.Rebuild(); diff --git a/Yafc/Windows/WelcomeScreen.cs b/Yafc/Windows/WelcomeScreen.cs index 487c5161..9624e4ee 100644 --- a/Yafc/Windows/WelcomeScreen.cs +++ b/Yafc/Windows/WelcomeScreen.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using SDL2; using Serilog; using Yafc.Model; @@ -97,7 +98,7 @@ private void BuildError(ImGui gui) { gui.DrawRectangle(gui.lastRect, SchemeColor.Error); } - protected override void BuildContents(ImGui gui) { + protected override Task BuildContents(ImGui gui) { gui.spacing = 1.5f; gui.BuildText("Yet Another Factorio Calculator", new TextBlockDisplayStyle(Font.header, Alignment: RectAlignment.Middle)); if (loading) { @@ -185,6 +186,8 @@ protected override void BuildContents(ImGui gui) { } } } + + return Task.CompletedTask; } private void ProjectErrorMoreInfo(ImGui gui) { diff --git a/changelog.txt b/changelog.txt index c2750549..74bace6e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -30,6 +30,7 @@ Date: the launch-settings from the most-recently opened project. Bugfixes: - Several fixes to the legacy summary page, including a regression in 0.8.1. + - Milestone-locked and inaccessible recipes will be avoided when multiple recipes are available. Internal changes: - Add .git-blame-ignore revs. It doesn't work with Visual Studio, but it might be useful in CLI or other IDEs. - Add the ability for tests to load lua and run tests that need parts of a Yafc project.