Skip to content

Commit 344729c

Browse files
authored
Fix #306 Wrong temperature handling in fluid products (#490)
This closes #306, by both fixing the fluid temperature when data.raw doesn't specify it, and by changing the recipe selection to only show recipes for the selected ingredient variant. There's also some refactoring that happened while I was trying to make OpenProductDropdown__dropDownContent less messy.
2 parents 8f49648 + 1909268 commit 344729c

File tree

7 files changed

+141
-91
lines changed

7 files changed

+141
-91
lines changed

Yafc.Model.Tests/Model/ProductionTableContentTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public async Task AllCombinationsWithVariousFixedCounts_DisplayedProductsMatchSc
139139

140140
foreach (var (display, solver) in row.Ingredients.Zip(((IRecipeRow)row).IngredientsForSolver)) {
141141
var (solverGoods, solverAmount, _, _) = solver;
142-
var (displayGoods, displayAmount, _, _) = display;
142+
var (displayGoods, displayAmount, _) = display;
143143

144144
try {
145145
// If this fails, something weird went wrong
@@ -211,7 +211,7 @@ private static void RunTest(RecipeRow row, Action<RecipeRow, ProductionTable, Ac
211211

212212
// Ensure that the fuel consumption remains constant, except when the entity and fuel change simultaneously.
213213
row.fixedFuel = true;
214-
((Goods oldFuel, _), float fuelAmount, _, _) = row.FuelInformation;
214+
((Goods oldFuel, _), float fuelAmount, _) = row.FuelInformation;
215215
testCombinations(row, table, testFuel(row, table));
216216
row.fixedFuel = false;
217217

@@ -243,7 +243,7 @@ Action testFuel(RecipeRow row, ProductionTable table) => () => {
243243
row.fixedBuildings = 1;
244244
row.fixedFuel = true;
245245
table.Solve((ProjectPage)table.owner).Wait();
246-
((oldFuel, _), fuelAmount, _, _) = row.FuelInformation;
246+
((oldFuel, _), fuelAmount, _) = row.FuelInformation;
247247
assertCalls++;
248248
}
249249
};

Yafc.Model.Tests/Model/SelectableVariantsTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public async Task CanSelectVariantFuel_VariantFuelChanges() {
1919
await table.Solve((ProjectPage)table.owner);
2020
Assert.Equal("steam@165", row.FuelInformation.Goods.target.name);
2121

22-
row.fuel = row.FuelInformation.Variants[1].With(Quality.Normal);
22+
row.fuel = row.FuelInformation.Goods.target.fluid.variants[1].With(Quality.Normal);
2323
await table.Solve((ProjectPage)table.owner);
2424
Assert.Equal("steam@500", row.FuelInformation.Goods.target.name);
2525
}
@@ -38,7 +38,7 @@ public async Task CanSelectVariantFuelWithFavorites_VariantFuelChanges() {
3838
await table.Solve((ProjectPage)table.owner);
3939
Assert.Equal("steam@500", row.FuelInformation.Goods.target.name);
4040

41-
row.fuel = row.FuelInformation.Variants[0].With(Quality.Normal);
41+
row.fuel = row.FuelInformation.Goods.target.fluid.variants[0].With(Quality.Normal);
4242
await table.Solve((ProjectPage)table.owner);
4343
Assert.Equal("steam@165", row.FuelInformation.Goods.target.name);
4444
}
@@ -56,7 +56,7 @@ public async Task CanSelectVariantIngredient_VariantIngredientChanges() {
5656
await table.Solve((ProjectPage)table.owner);
5757
Assert.Equal("steam@165", row.Ingredients.Single().Goods.target.name);
5858

59-
row.ChangeVariant(row.Ingredients.Single().Goods.target, row.Ingredients.Single().Variants[1]);
59+
row.ChangeVariant(row.Ingredients.Single().Goods.target, row.Ingredients.Single().Goods.target.fluid.variants[1]);
6060
await table.Solve((ProjectPage)table.owner);
6161
Assert.Equal("steam@500", row.Ingredients.Single().Goods.target.name);
6262
}

Yafc.Model/Model/ProductionTable.cs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ public struct ProductionTableFlow(IObjectWithQuality<Goods> goods, float amount,
1818
public IProductionLink? link = link;
1919
}
2020

21+
[DeserializeWithNonPublicConstructor]
2122
public sealed partial class ProductionTable : ProjectPageContents, IComparer<ProductionTableFlow>, IElementGroup<RecipeRow> {
2223
private static readonly ILogger logger = Logging.GetLogger<ProductionTable>();
24+
private readonly ProductionTable rootTable;
2325
[SkipSerialization] public Dictionary<IObjectWithQuality<Goods>, IProductionLink> linkMap { get; } = [];
2426
List<RecipeRow> IElementGroup<RecipeRow>.elements => recipes;
2527
[NoUndo]
@@ -43,12 +45,26 @@ public sealed partial class ProductionTable : ProjectPageContents, IComparer<Pro
4345
public ModuleFillerParameters? modules { get; } // If you add a setter for this, ensure it calls RecipeRow.ModuleFillerParametersChanging().
4446
public bool containsDesiredProducts { get; private set; }
4547

46-
public ProductionTable(ModelObject owner) : base(owner) {
47-
if (owner is ProjectPage) {
48+
// For deserialization
49+
private ProductionTable(ModelObject owner) : base(owner) {
50+
if (owner is RecipeRow row) {
51+
rootTable = row.owner.rootTable;
52+
}
53+
else {
4854
modules = new ModuleFillerParameters(this);
55+
rootTable = this;
4956
}
5057
}
5158

59+
// For the ProjectPage constructor
60+
public ProductionTable(ProjectPage owner) : base(owner) {
61+
modules = new ModuleFillerParameters(this);
62+
rootTable = this;
63+
}
64+
65+
// For creating nested tables
66+
public ProductionTable(RecipeRow owner) : base(owner) => rootTable = owner.owner.rootTable;
67+
5268
protected internal override void ThisChanged(bool visualOnly) {
5369
RebuildLinkMap();
5470
if (owner is ProjectPage page) {
@@ -775,5 +791,45 @@ public int Compare(ProductionTableFlow x, ProductionTableFlow y) {
775791

776792
return amt1.CompareTo(amt2);
777793
}
778-
}
779794

795+
/// <summary>
796+
/// Returns <see langword="true"/> if this table (including its parent recipe, if applicable) contains the specified recipe at the specified quality.
797+
/// </summary>
798+
public bool Contains(IObjectWithQuality<RecipeOrTechnology> obj) {
799+
if (owner is RecipeRow { recipe: IObjectWithQuality<RecipeOrTechnology> recipe } && recipe == obj) {
800+
return true;
801+
}
802+
return recipes.Any(r => r.recipe == obj);
803+
}
804+
805+
/// <summary>
806+
/// Returns <see langword="true"/> if this table (including its parent recipe, if applicable) contains the specified recipe at any quality.
807+
/// </summary>
808+
/// <remarks>This is most commonly used for deciding whether to draw a green checkmark.</remarks>
809+
public bool Contains(RecipeOrTechnology obj) {
810+
if (owner is RecipeRow { recipe.target: RecipeOrTechnology recipe } && recipe == obj) {
811+
return true;
812+
}
813+
return recipes.Any(r => r.recipe.target == obj);
814+
}
815+
816+
/// <summary>
817+
/// Returns <see langword="true"/> if the specified recipe appears at the specified quality anywhere on this table's <see cref="ProjectPage"/>.
818+
/// </summary>
819+
public bool ContainsAnywhere(IObjectWithQuality<RecipeOrTechnology> obj) => rootTable.GetAllRecipes().Any(r => r.recipe == obj);
820+
821+
/// <summary>
822+
/// Returns <see langword="true"/> if the specified recipe appears at any quality anywhere on this table's <see cref="ProjectPage"/>.
823+
/// </summary>
824+
/// <remarks>This is most commonly used for deciding whether to draw a yellow checkmark.</remarks>
825+
public bool ContainsAnywhere(RecipeOrTechnology obj) => rootTable.GetAllRecipes().Any(r => r.recipe.target == obj);
826+
827+
public bool CreateLink(IObjectWithQuality<Goods> goods) {
828+
if (linkMap.GetValueOrDefault(goods) is ProductionLink || !goods.target.isLinkable) {
829+
return false;
830+
}
831+
832+
this.RecordUndo().links.Add(new(this, goods.target.With(goods.quality)));
833+
return true;
834+
}
835+
}

Yafc.Model/Model/ProductionTableContent.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -481,14 +481,14 @@ public ModuleTemplate? modules {
481481
public HashSet<FactorioObject> variants { get; } = [];
482482
[SkipSerialization] public ProductionTable linkRoot => subgroup ?? owner;
483483

484-
public RecipeRowIngredient FuelInformation => new(fuel, fuelUsagePerSecond, links.fuel, (fuel?.target as Fluid)?.variants?.ToArray());
484+
public RecipeRowIngredient FuelInformation => new(fuel, fuelUsagePerSecond, links.fuel);
485485
public IEnumerable<RecipeRowIngredient> Ingredients {
486486
get {
487487
if (hierarchyEnabled) {
488488
return BuildIngredients(false).Select(RecipeRowIngredient.FromSolver);
489489
}
490490
else {
491-
return Enumerable.Repeat(new RecipeRowIngredient(null, 0, null, null), recipe.target.ingredients.Length);
491+
return Enumerable.Repeat(new RecipeRowIngredient(null, 0, null), recipe.target.ingredients.Length);
492492
}
493493
}
494494
}
@@ -909,18 +909,26 @@ public IEnumerable<string> LinkWarnings {
909909
}
910910
}
911911
}
912+
913+
public bool Destroy() {
914+
if (owner.links.Contains(this)) {
915+
_ = owner.RecordUndo().links.Remove(this);
916+
return true;
917+
}
918+
return false;
919+
}
912920
}
913921

914922
/// <summary>
915923
/// An ingredient for a recipe row, as reported to the UI.
916924
/// </summary>
917-
public record RecipeRowIngredient(IObjectWithQuality<Goods>? Goods, float Amount, ProductionLink? Link, Goods[]? Variants) {
925+
public record RecipeRowIngredient(IObjectWithQuality<Goods>? Goods, float Amount, ProductionLink? Link) {
918926
/// <summary>
919927
/// Convert from a <see cref="SolverIngredient"/> (the form initially generated when reporting ingredients) to a
920928
/// <see cref="RecipeRowIngredient"/>.
921929
/// </summary>
922930
internal static RecipeRowIngredient FromSolver(SolverIngredient value)
923-
=> new(value.Goods, value.Amount, value.Link as ProductionLink, value.Goods.target.fluid?.variants?.ToArray());
931+
=> new(value.Goods, value.Amount, value.Link as ProductionLink);
924932
}
925933

926934
/// <summary>

Yafc.Parser/Data/FactorioDataDeserializer.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,11 @@ public Project LoadData(string projectPath, LuaTable data, LuaTable prototypes,
127127
}
128128

129129
allModules.AddRange(allObjects.OfType<Module>());
130-
progress.Report((LSs.ProgressLoading, LSs.ProgressLoadingTiles));
131-
DeserializePrototypes(raw, "tile", DeserializeTile, progress, errorCollector);
132130
progress.Report((LSs.ProgressLoading, LSs.ProgressLoadingFluids));
133131
DeserializePrototypes(raw, "fluid", DeserializeFluid, progress, errorCollector);
132+
progress.Report((LSs.ProgressLoading, LSs.ProgressLoadingTiles));
133+
// pumpable tiles depend on fluids, so they can read the default fluid temperature
134+
DeserializePrototypes(raw, "tile", DeserializeTile, progress, errorCollector);
134135
progress.Report((LSs.ProgressLoading, LSs.ProgressLoadingRecipes));
135136
DeserializePrototypes(raw, "recipe", DeserializeRecipe, progress, errorCollector);
136137
progress.Report((LSs.ProgressLoading, LSs.ProgressLoadingLocations));
@@ -627,7 +628,9 @@ private void DeserializeTile(LuaTable table, ErrorCollector _) {
627628
var tile = DeserializeCommon<Tile>(table, "tile");
628629

629630
if (table.Get("fluid", out string? fluid)) {
630-
Fluid pumpingFluid = GetObject<Fluid>(fluid);
631+
Fluid baseFluid = GetObject<Fluid>(fluid);
632+
// Explicitly produce this fluid at the default temperature, to ensure the temperature split is created.
633+
Fluid pumpingFluid = GetFluidFixedTemp(fluid, baseFluid.temperatureRange.min);
631634
tile.Fluid = pumpingFluid;
632635

633636
string recipeCategory = SpecialNames.PumpingRecipe + "tile";
@@ -667,9 +670,16 @@ private void DeserializeFluid(LuaTable table, ErrorCollector _) {
667670
return GetObject<Item>(table);
668671
}
669672
else if (type == "fluid") {
670-
if (useTemperature && table.Get("temperature", out int temperature)) {
671-
return GetFluidFixedTemp(name, temperature);
673+
if (useTemperature) {
674+
if (table.Get("temperature", out int temperature)) {
675+
return GetFluidFixedTemp(name, temperature);
676+
}
677+
else {
678+
// If the temperature isn't specified, produce the default (minimum) temperature
679+
return GetFluidFixedTemp(name, GetObject<Fluid>(table).temperatureRange.min);
680+
}
672681
}
682+
673683
return GetObject<Fluid>(table);
674684
}
675685
}

0 commit comments

Comments
 (0)