Skip to content

Commit f551956

Browse files
committed
fix: Load the quality filter for entity and item triggers.
1 parent 4316efd commit f551956

File tree

5 files changed

+64
-13
lines changed

5 files changed

+64
-13
lines changed

Yafc.Model/Data/DataClasses.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,8 @@ public class EntityContainer : Entity {
974974
}
975975

976976
public class Technology : RecipeOrTechnology { // Technology is very similar to recipe
977+
private Quality? _triggerMinimumQuality;
978+
977979
public float count { get; internal set; } // TODO support formula count
978980
public Technology[] prerequisites { get; internal set; } = [];
979981
public List<Recipe> unlockRecipes { get; } = [];
@@ -992,6 +994,16 @@ public class Technology : RecipeOrTechnology { // Technology is very similar to
992994
/// or <see cref="RecipeFlags.HasResearchTriggerSendToOrbit"/>.
993995
/// </summary>
994996
public FactorioObject? triggerObject { get; internal set; }
997+
/// <summary>
998+
/// For entity and item triggers, the minimum quality. Factorio tracks more complicated filters (e.g. &lt; rare), but the only thing we care about
999+
/// for accessibility is the minimum acceptable quality. This is not stored as an <see cref="IObjectWithQuality{T}"/> because those don't exist
1000+
/// when this field is loaded from the lua tables.
1001+
/// </summary>
1002+
[AllowNull]
1003+
public Quality triggerMinimumQuality {
1004+
get => _triggerMinimumQuality ?? Quality.Normal;
1005+
internal set => _triggerMinimumQuality = value;
1006+
}
9951007

9961008
/// <summary>
9971009
/// Sets the value used to construct <see cref="triggerEntities"/>.
@@ -1012,6 +1024,11 @@ protected override List<DependencyNode> GetDependenciesHelper() {
10121024
if (flags.HasFlag(RecipeFlags.HasResearchTriggerBuildEntity)) {
10131025
nodes.Add((triggerEntities, DependencyNode.Flags.Source));
10141026
}
1027+
if (flags.HasFlagAny(RecipeFlags.HasResearchTriggerBuildEntity | RecipeFlags.HasResearchTriggerCraft)) {
1028+
if (triggerMinimumQuality > Quality.Normal) {
1029+
nodes.Add(([triggerMinimumQuality], DependencyNode.Flags.Source));
1030+
}
1031+
}
10151032
if (flags.HasFlag(RecipeFlags.HasResearchTriggerCreateSpacePlatform)) {
10161033
var items = Database.items.all.Where(i => i.factorioType == "space-platform-starter-pack");
10171034
nodes.Add((items.Select(i => Database.objectsByTypeName["Mechanics.launch." + i.name]), DependencyNode.Flags.Source));

Yafc.Parser/Data/FactorioDataDeserializer.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,12 @@ public Project LoadData(string projectPath, LuaTable data, LuaTable prototypes,
162162
DeserializePrototypes(raw, "planet", DeserializeLocation, progress, errorCollector);
163163
DeserializePrototypes(raw, "space-location", DeserializeLocation, progress, errorCollector);
164164
rootAccessible.Add(GetObject<Location>("nauvis"));
165-
progress.Report((LSs.ProgressLoading, LSs.ProgressLoadingTechnologies));
166-
DeserializePrototypes(raw, "technology", DeserializeTechnology, progress, errorCollector);
167165
progress.Report((LSs.ProgressLoading, LSs.ProgressLoadingQualities));
168166
DeserializePrototypes(raw, "quality", DeserializeQuality, progress, errorCollector);
169167
Quality.Normal = GetObject<Quality>("normal");
168+
// Qualities must be loaded before technologies, to properly initialize Technology.triggerMinimumQuality
169+
progress.Report((LSs.ProgressLoading, LSs.ProgressLoadingTechnologies));
170+
DeserializePrototypes(raw, "technology", DeserializeTechnology, progress, errorCollector);
170171
rootAccessible.Add(Quality.Normal);
171172
DeserializePrototypes(raw, "asteroid-chunk", DeserializeAsteroidChunk, progress, errorCollector);
172173

Yafc.Parser/Data/FactorioDataDeserializer_RecipeAndTechnology.cs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Linq;
34
using Yafc.I18n;
45
using Yafc.Model;
@@ -139,7 +140,7 @@ private void LoadTechnologyData(Technology technology, LuaTable table, bool forc
139140
recipeCategories.Add(SpecialNames.Labs, technology);
140141
}
141142
else if (table.Get("research_trigger", out LuaTable? researchTriggerTable)) {
142-
LoadResearchTrigger(researchTriggerTable, ref technology, errorCollector);
143+
LoadResearchTrigger(researchTriggerTable, technology, errorCollector);
143144
technology.ingredients ??= [];
144145
recipeCategories.Add(SpecialNames.TechnologyTrigger, technology);
145146
}
@@ -319,7 +320,7 @@ private Ingredient[] LoadResearchIngredientList(LuaTable table) {
319320
}).Where(x => x is not null).ToArray() ?? [];
320321
}
321322

322-
private void LoadResearchTrigger(LuaTable researchTriggerTable, ref Technology technology, ErrorCollector errorCollector) {
323+
private void LoadResearchTrigger(LuaTable researchTriggerTable, Technology technology, ErrorCollector errorCollector) {
323324
if (!researchTriggerTable.Get("type", out string? type)) {
324325
errorCollector.Error($"Research trigger of {technology.typeDotName} does not have a type field", ErrorSeverity.MinorDataLoss);
325326
return;
@@ -337,8 +338,8 @@ private void LoadResearchTrigger(LuaTable researchTriggerTable, ref Technology t
337338

338339
break;
339340
case "craft-item":
340-
if (!researchTriggerTable.Get("item", out string? craftItemName)) {
341-
errorCollector.Error($"Research trigger {type} of {technology.typeDotName} does not have an item field", ErrorSeverity.MinorDataLoss);
341+
if (!loadQualityFromFilter(technology, researchTriggerTable, "item", out string? craftItemName)) {
342+
errorCollector.Error($"Research trigger {type} of {technology.typeDotName} does not have a recognized item field", ErrorSeverity.MinorDataLoss);
342343
break;
343344
}
344345
craftCount = researchTriggerTable.Get("count", 1);
@@ -368,13 +369,11 @@ private void LoadResearchTrigger(LuaTable researchTriggerTable, ref Technology t
368369
break;
369370
case "build-entity":
370371
technology.flags = RecipeFlags.HasResearchTriggerBuildEntity;
371-
if (researchTriggerTable.Get("entity", out entity)
372-
|| (researchTriggerTable.Get("entity", out LuaTable? entityFilter) && entityFilter.Get("name", out entity))) {
373-
372+
if (loadQualityFromFilter(technology, researchTriggerTable, "entity", out entity)) {
374373
technology.getTriggerEntities = new(() => [((Entity)Database.objectsByTypeName["Entity." + entity])]);
375374
}
376375
else {
377-
errorCollector.Error($"Research trigger {type} of {technology.typeDotName} does not have an entity field", ErrorSeverity.MinorDataLoss);
376+
errorCollector.Error($"Research trigger {type} of {technology.typeDotName} does not have a recognized entity field", ErrorSeverity.MinorDataLoss);
378377
}
379378

380379
break;
@@ -405,6 +404,38 @@ private void LoadResearchTrigger(LuaTable researchTriggerTable, ref Technology t
405404
errorCollector.Error(LSs.ResearchHasAnUnsupportedTriggerType.L(technology.typeDotName, type), ErrorSeverity.MinorDataLoss);
406405
break;
407406
}
407+
408+
bool loadQualityFromFilter(Technology technology, LuaTable trigger, string key, [NotNullWhen(true)] out string? objectName) {
409+
if (trigger.Get(key, out objectName)) {
410+
// Basic string value
411+
return true;
412+
}
413+
414+
if (trigger.Get(key, out LuaTable? filter) && filter.Get("name", out objectName)) {
415+
// Load the quality specifiers from the table-based filter
416+
if (!registeredObjects.TryGetValue((typeof(Quality), filter.Get<string>("quality")), out var quality)) {
417+
return true;
418+
}
419+
420+
switch (filter.Get<string>("comparator")) {
421+
// `≠ normal` is the same as `> normal`. `≠ anything-else` is the same as `= normal` for dependency analysis.
422+
case "!=" or "≠" when quality == Quality.Normal:
423+
case ">":
424+
// This filter requires at least the quality after the specified quality.
425+
// I expect `> legendary` is a load error in Factorio, so treating it as "any quality" here should be fine.
426+
technology.triggerMinimumQuality = ((Quality)quality).nextQuality;
427+
break;
428+
case ">=" or "≥" or "=":
429+
technology.triggerMinimumQuality = (Quality)quality;
430+
break;
431+
default:
432+
// Nothing special: Normal quality is acceptable, no quality, or no comparator
433+
break;
434+
}
435+
return true;
436+
}
437+
return false;
438+
}
408439
}
409440

410441
private void LoadRecipeData(Recipe recipe, LuaTable table, bool forceDisable, ErrorCollector errorCollector) {

Yafc/Widgets/ObjectTooltip.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ private static void BuildIconRow(ImGui gui, IReadOnlyList<IFactorioObjectWrapper
145145

146146
private static void BuildItem(ImGui gui, IFactorioObjectWrapper item, string? extraText = null) {
147147
using (gui.EnterRow()) {
148-
gui.BuildFactorioObjectIcon(item.target);
148+
gui.BuildFactorioObjectIcon(item);
149149
gui.BuildText(item.text + extraText, TextBlockDisplayStyle.WrappedText);
150150
}
151151
}
@@ -608,7 +608,8 @@ private static void BuildTechnology(Technology technology, ImGui gui) {
608608
using (gui.EnterGroup(contentPadding)) {
609609
using var grid = gui.EnterInlineGrid(3f);
610610
grid.Next();
611-
_ = gui.BuildFactorioObjectWithAmount(technology.ingredients[0].goods, technology.ingredients[0].amount, ButtonDisplayStyle.ProductionTableUnscaled);
611+
_ = gui.BuildFactorioObjectWithAmount(technology.ingredients[0].goods.With(technology.triggerMinimumQuality),
612+
technology.ingredients[0].amount, ButtonDisplayStyle.ProductionTableUnscaled);
612613
}
613614
}
614615
else if (isResearchTriggerCapture) {
@@ -626,7 +627,7 @@ private static void BuildTechnology(Technology technology, ImGui gui) {
626627
else if (isResearchTriggerBuild) {
627628
BuildSubHeader(gui, LSs.TooltipHeaderTechnologyBuildEntity.L(technology.triggerEntities.Count));
628629
using (gui.EnterGroup(contentPadding)) {
629-
BuildIconRow(gui, technology.triggerEntities, 2);
630+
BuildIconRow(gui, [.. technology.triggerEntities.Select(e => e.With(technology.triggerMinimumQuality))], 2);
630631
}
631632
}
632633
else if (isResearchTriggerPlatform) {

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Version:
2323
Date:
2424
Fixes:
2525
- Support craft-fluid and scripted research triggers.
26+
- Consider quality restrictions when analyzing craft-item or build-item technology triggers.
2627
----------------------------------------------------------------------------------------------------------------------
2728
Version: 2.16.0
2829
Date: October 24th 2025

0 commit comments

Comments
 (0)