make recipe selection and buffers of smelter and reprocessing plant behave like other buildings, except that a recipe may be chosen automatically

This commit is contained in:
2026-08-12 23:24:31 +02:00
parent 4ee6438405
commit 9bbade2420
18 changed files with 363 additions and 335 deletions

View File

@@ -8,29 +8,37 @@
#include "ModulesConfig.h"
#include "ShipsConfig.h"
std::vector<const RecipeDef*>
gatherCandidateRecipes(const GameConfig& config, const Building& b)
const RecipeDef* getSelectedRecipe(const GameConfig& config, const Building& b)
{
std::vector<const RecipeDef*> candidates;
if (isAutoRecipeBuildingType(b.type))
if (b.recipeId.empty())
{
for (const RecipeDef& r : config.recipes.recipes)
return nullptr;
}
return config.recipes.findRecipeDef(b.recipeId, b.type);
}
const RecipeDef* findAutoRecipeFor(const GameConfig& config, BuildingType type,
const ItemType& item)
{
if (!isAutoRecipeBuildingType(type))
{
return nullptr;
}
// Config order decides where a material feeds more than one recipe of the type, so
// the same offer always picks the same recipe (REQ-BLD-AUTO-RECIPE).
for (const RecipeDef& recipe : config.recipes.recipes)
{
if (recipe.building != type) { continue; }
for (const RecipeIngredient& ing : recipe.inputs)
{
if (r.building == b.type && !r.inputs.empty())
if (ItemType{ing.item} == item)
{
candidates.push_back(&r);
return &recipe;
}
}
}
else
{
const RecipeDef* recipe = config.recipes.findRecipeDef(b.recipeId, b.type);
if (recipe)
{
candidates.push_back(recipe);
}
}
return candidates;
return nullptr;
}
bool recipeInputsAvailable(const Building& b, const RecipeDef& recipe)
{
@@ -130,17 +138,11 @@ bool hasInputsToStart(const GameConfig& config, const Building& b)
return true;
}
// Recipe buildings: startable if any candidate recipe's inputs are satisfied.
// A Miner recipe has no inputs, so an idle Miner is always startable here and its
// only idle reason is an output buffer without room for the next cycle.
for (const RecipeDef* recipe : gatherCandidateRecipes(config, b))
{
if (recipeInputsAvailable(b, *recipe))
{
return true;
}
}
return false;
// Recipe buildings: startable if the selected recipe's inputs are satisfied. A Miner
// recipe has no inputs, so an idle Miner is always startable here and its only idle
// reason is an output buffer without room for the next cycle.
const RecipeDef* recipe = getSelectedRecipe(config, b);
return recipe != nullptr && recipeInputsAvailable(b, *recipe);
}
bool outputBufferHasRoom(const Building& b, const ItemType& type, int itemCount)
@@ -192,14 +194,9 @@ bool canStartCycle(const GameConfig& config, const Building& b)
return hasInputsToStart(config, b);
}
for (const RecipeDef* recipe : gatherCandidateRecipes(config, b))
{
if (recipeInputsAvailable(b, *recipe) && recipeOutputsFit(b, *recipe))
{
return true;
}
}
return false;
const RecipeDef* recipe = getSelectedRecipe(config, b);
return recipe != nullptr && recipeInputsAvailable(b, *recipe)
&& recipeOutputsFit(b, *recipe);
}
std::optional<ProductionStatus>
@@ -220,9 +217,10 @@ getProductionStatus(const GameConfig& config, const Building& building)
return std::nullopt;
}
// Grey only applies to player-configured types; auto-recipe buildings
// (Smelter, Reprocessing Plant) always run an implicit recipe.
if (!isAutoRecipeBuildingType(building.type) && building.recipeId.empty())
// Every production building can be unconfigured, an auto-recipe building included:
// it holds no recipe until one is offered to it, and the player can hand it back to
// automatic selection (REQ-BLD-AUTO-RECIPE, REQ-UI-STATUS-LIGHT).
if (building.recipeId.empty())
{
return ProductionStatus::Unconfigured;
}