extract the production rules as free functions over config and building
gatherCandidateRecipes, recipeInputsAvailable, computeShipyardRequiredMaterials, hasInputsToStart and getProductionStatus read no factory state — they answer "what can this building produce, and can it start" from the config and the Building alone. They move to ProductionRules.h as free functions, and the ProductionStatus enum goes with them since it is that group's return type. Two of the five are pure in their arguments; the other three need GameConfig through gatherCandidateRecipes and computeShipyardRequiredMaterials, so config is a parameter rather than the group being split across two headers. Only two callers outside BuildingSystem existed — the status light in GameWorldView and one test lambda — so this is nearly all internal. Verified with a golden-checksum capture before and after — all four sample ticks identical. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include <set>
|
||||
|
||||
#include "FactoryQueries.h"
|
||||
#include "ProductionRules.h"
|
||||
#include "PortGeometry.h"
|
||||
#include "StateChecksum.h"
|
||||
#include "SurfaceMask.h"
|
||||
@@ -1008,7 +1009,7 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
||||
// are satisfied (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). Other buildings
|
||||
// try only their selected recipe.
|
||||
const std::vector<const RecipeDef*> candidates =
|
||||
gatherCandidateRecipes(building);
|
||||
gatherCandidateRecipes(m_config, building);
|
||||
|
||||
for (const RecipeDef* recipe : candidates)
|
||||
{
|
||||
@@ -1112,7 +1113,7 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
|
||||
|
||||
// Build combined materials list (base + modules).
|
||||
const std::map<std::string, int> requiredMaterials =
|
||||
computeShipyardRequiredMaterials(building);
|
||||
computeShipyardRequiredMaterials(m_config, building);
|
||||
|
||||
// Idle: check if all combined materials are available.
|
||||
bool inputsOk = true;
|
||||
@@ -1257,146 +1258,10 @@ void BuildingSystem::forEachIncomingItem(
|
||||
// Queries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::vector<const RecipeDef*>
|
||||
BuildingSystem::gatherCandidateRecipes(const Building& b) const
|
||||
{
|
||||
std::vector<const RecipeDef*> candidates;
|
||||
if (isAutoRecipeBuildingType(b.type))
|
||||
{
|
||||
for (const RecipeDef& r : m_config.recipes.recipes)
|
||||
{
|
||||
if (r.building == b.type && !r.inputs.empty())
|
||||
{
|
||||
candidates.push_back(&r);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const RecipeDef* recipe = m_config.recipes.findRecipeDef(b.recipeId, b.type);
|
||||
if (recipe)
|
||||
{
|
||||
candidates.push_back(recipe);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
bool BuildingSystem::recipeInputsAvailable(const Building& b,
|
||||
const RecipeDef& recipe) const
|
||||
{
|
||||
for (const RecipeIngredient& ing : recipe.inputs)
|
||||
{
|
||||
const std::map<ItemType, int>::const_iterator it =
|
||||
b.inputBuffer.counts.find(ItemType{ing.item});
|
||||
const int have = (it != b.inputBuffer.counts.end()) ? it->second : 0;
|
||||
if (have < ing.amount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<std::string, int>
|
||||
BuildingSystem::computeShipyardRequiredMaterials(const Building& b) const
|
||||
{
|
||||
std::map<std::string, int> requiredMaterials;
|
||||
const ShipDef* shipDef = m_config.ships.findShipDef(b.recipeId);
|
||||
if (!shipDef)
|
||||
{
|
||||
return requiredMaterials;
|
||||
}
|
||||
for (const RecipeIngredient& ing : shipDef->schematic.materials)
|
||||
{
|
||||
requiredMaterials[ing.item] += ing.amount;
|
||||
}
|
||||
if (b.shipLayout.has_value())
|
||||
{
|
||||
for (const PlacedModule& pm : b.shipLayout->placedModules)
|
||||
{
|
||||
const ModuleDef* modDef = m_config.modules.findModuleDef(pm.moduleId);
|
||||
if (!modDef)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (const RecipeIngredient& ing : modDef->materials)
|
||||
{
|
||||
requiredMaterials[ing.item] += ing.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return requiredMaterials;
|
||||
}
|
||||
|
||||
bool BuildingSystem::hasInputsToStart(const Building& b) const
|
||||
{
|
||||
if (b.type == BuildingType::Shipyard)
|
||||
{
|
||||
const std::map<std::string, int> required =
|
||||
computeShipyardRequiredMaterials(b);
|
||||
for (const std::pair<const std::string, int>& req : required)
|
||||
{
|
||||
const std::map<ItemType, int>::const_iterator it =
|
||||
b.inputBuffer.counts.find(ItemType{req.first});
|
||||
const int have = (it != b.inputBuffer.counts.end()) ? it->second : 0;
|
||||
if (have < req.second)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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 and its
|
||||
// only idle reason is a full output buffer.
|
||||
for (const RecipeDef* recipe : gatherCandidateRecipes(b))
|
||||
{
|
||||
if (recipeInputsAvailable(b, *recipe))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<ProductionStatus>
|
||||
BuildingSystem::getProductionStatus(const Building& building) const
|
||||
{
|
||||
// Salvage Bay has no recipe or production cycle (REQ-BLD-SALVAGE-BAY): it is
|
||||
// "producing" while it holds scrap to push out, and starved when empty.
|
||||
if (building.type == BuildingType::SalvageBay)
|
||||
{
|
||||
return building.getOutputItemCount() >= 1 ? ProductionStatus::Producing
|
||||
: ProductionStatus::Starved;
|
||||
}
|
||||
|
||||
// Only the five recipe/cycle production types show a status light besides the
|
||||
// Salvage Bay; belts, splitters, tunnels, HQ, and stations show none.
|
||||
if (!isProductionBuildingType(building.type))
|
||||
{
|
||||
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())
|
||||
{
|
||||
return ProductionStatus::Unconfigured;
|
||||
}
|
||||
|
||||
if (building.production.has_value())
|
||||
{
|
||||
return ProductionStatus::Producing;
|
||||
}
|
||||
|
||||
// Idle: missing inputs (red) take precedence over a full output buffer
|
||||
// (yellow). If inputs are present yet the building is idle, the only remaining
|
||||
// reason it could not start a cycle is a full output buffer (REQ-MAT-CYCLE).
|
||||
return hasInputsToStart(building) ? ProductionStatus::Blocked
|
||||
: ProductionStatus::Starved;
|
||||
}
|
||||
|
||||
std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() const
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user