Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Yafc.Model/Model/ProductionTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ public void AddRecipe(IObjectWithQuality<RecipeOrTechnology> recipe, IComparer<G
}

recipeRow.AutoApplyModuleTemplate(Project.current.sharedModuleTemplates);
recipeRow.AutoApplySingleCategoryModule();
}

private static EntityCrafter? GetSelectedFuelCrafter(RecipeOrTechnology recipe, IObjectWithQuality<Goods>? selectedFuel) =>
Expand Down
62 changes: 62 additions & 0 deletions Yafc.Model/Model/ProductionTableContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,68 @@ public void AutoApplyModuleTemplate(List<ProjectModuleTemplate> moduleTemplates)
}
}

/// <summary>
/// Apply a default module when this row uses a single-category module building and no template was selected.
/// The chosen module is based upon the unlock order and what milestones are currently unlocked. No secondary sort on prod/speed to keep the code simpler
/// </summary>
public void AutoApplySingleCategoryModule() {
//Don't change anything if existing modules are set
if (modules != null) {
return;
}

//Basic safety checks
if (recipe is null || entity is null || entity.target.moduleSlots <= 0) {
return;
}

if (recipe.target is not Recipe targetRecipe) {
return;
}

if (entity.target.allowedModuleCategories is not [string moduleCategory]) {
return;
}

Bits recipeUnlockOrder = DataUtils.GetMilestoneOrder(targetRecipe.id);

IObjectWithQuality<Module>? bestModule = null;
Bits bestUnlockOrder = default;

//Loop over all modules finding the one that is usable, unlocked, and unlocks latest according to milestones
foreach (Module module in Database.allModules) {
//Check module is in the building's allowed category
if (!string.Equals(module.moduleSpecification.category, moduleCategory, StringComparison.Ordinal)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!string.Equals(module.moduleSpecification.category, moduleCategory, StringComparison.Ordinal)) {
if (module.moduleSpecification.category != moduleCategory) {

As far as I can tell, operator != is the same as doing an explicit ordinal comparison.

continue;
}

//Check the recipe allows this module
if (!entity.target.CanAcceptModule(module.moduleSpecification) || !recipe.target.CanAcceptModule(module)) {
continue;
}

//Check if this module is currently available for this recipe with the active milestones
Bits moduleUnlockOrder = DataUtils.GetMilestoneOrder(module.id);
if (moduleUnlockOrder.CompareTo(recipeUnlockOrder) > 0) {
Comment on lines +852 to +853
Copy link
Copy Markdown
Collaborator

@DaleStan DaleStan May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Bits moduleUnlockOrder = DataUtils.GetMilestoneOrder(module.id);
if (moduleUnlockOrder.CompareTo(recipeUnlockOrder) > 0) {
if (!module.IsAccessibleWithCurrentMilestones()) {

Based on the comment, I think this is the test you want. This way the relative unlock order isn't relevant, just whether the module is unlocked.

continue;
}

//Update if this module is better (milestone order)
Bits moduleMilestoneOrder = Milestones.Instance.GetMilestoneResult(module.id);
if (bestModule == null || moduleMilestoneOrder.CompareTo(bestUnlockOrder) > 0) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (bestModule == null || moduleMilestoneOrder.CompareTo(bestUnlockOrder) > 0) {
if (bestModule == null || moduleMilestoneOrder > bestUnlockOrder) {

It might just be me, but I always have to spend a lot of effort interpreting calls to CompareTo

bestModule = module.With(Quality.Normal);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Quality.Normal correct here, or should it be Quality.MaxAccessible?

bestUnlockOrder = moduleMilestoneOrder;
}
}

//We have a working module, fill all the module slots
if (bestModule != null) {
modules = new ModuleTemplateBuilder {
list = [(bestModule, 0)]
}.Build(this);
}
}

public float DetermineFlow(IObjectWithQuality<Goods> goods) {
if (recipe is null) {
throw new InvalidOperationException("Cannot determine flow when no recipe is selected.");
Expand Down
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Version:
Date:
Features:
- Improve early-startup error logging
- Automatically apply single use modules to recipes. e.g. Autoamtically fill PyAL buildings with appropriate module for the current milestone
Fixes:
----------------------------------------------------------------------------------------------------------------------
Version: 2.18.1
Expand Down