extract the production rules as free functions over config and building
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
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "BeltSystem.h"
|
||||
#include "Building.h"
|
||||
#include "FactoryState.h"
|
||||
#include "ProductionRules.h"
|
||||
#include "BuildingType.h"
|
||||
#include "BuildingId.h"
|
||||
#include "GameConfig.h"
|
||||
@@ -27,18 +28,6 @@
|
||||
|
||||
class Hasher;
|
||||
|
||||
// Production state of a building for the UI status light (REQ-UI-STATUS-LIGHT).
|
||||
// The simulation owns the classification so it stays in sync with the
|
||||
// production-cycle predicates (REQ-MAT-CYCLE); the UI maps each value to a fill
|
||||
// color.
|
||||
enum class ProductionStatus
|
||||
{
|
||||
Unconfigured, // no recipe/schematic selected (grey)
|
||||
Producing, // a production cycle is active (green)
|
||||
Starved, // idle: a required input is missing / Salvage Bay empty (red)
|
||||
Blocked, // idle: output buffer full, inputs otherwise present (yellow)
|
||||
};
|
||||
|
||||
// Manages building placement, construction queuing, and the per-tick
|
||||
// production loop (belt→building pull, production, building→belt push).
|
||||
// All types including Belt and Splitter are stored as Building instances;
|
||||
@@ -150,7 +139,6 @@ public:
|
||||
// nullopt for building types that show no light (belts, splitters, tunnels,
|
||||
// HQ, defence stations). The Salvage Bay is a two-state special case:
|
||||
// Producing while its output buffer holds scrap, Starved when empty.
|
||||
std::optional<ProductionStatus> getProductionStatus(const Building& building) const;
|
||||
std::vector<BeltTileInfo> getAllBeltTiles() const;
|
||||
|
||||
// Visits every item currently emerging from a building output port on its
|
||||
@@ -245,17 +233,12 @@ private:
|
||||
// building (Smelter, Reprocessing Plant) offers every recipe of its type with
|
||||
// inputs; other buildings offer only their selected recipe. Shared by
|
||||
// tickProduction and the status classifier (REQ-MAT-CYCLE, REQ-UI-STATUS-LIGHT).
|
||||
std::vector<const RecipeDef*> gatherCandidateRecipes(const Building& b) const;
|
||||
// True if every input of `recipe` is present in `b`'s input buffers in the
|
||||
// required per-cycle amount (REQ-MAT-CYCLE input check).
|
||||
bool recipeInputsAvailable(const Building& b,
|
||||
const RecipeDef& recipe) const;
|
||||
// Combined base + module materials a shipyard needs per ship (REQ-BLD-SHIPYARD).
|
||||
std::map<std::string, int> computeShipyardRequiredMaterials(const Building& b) const;
|
||||
// True if the building currently has all inputs/materials to start a cycle
|
||||
// (ignoring output-buffer space); drives the Starved/Blocked distinction of
|
||||
// the status light (REQ-UI-STATUS-LIGHT).
|
||||
bool hasInputsToStart(const Building& b) const;
|
||||
|
||||
void initBuffers(Building& b, const RecipeDef& recipe) const;
|
||||
// Buffers for an auto-recipe building (Smelter, Reprocessing Plant): input
|
||||
|
||||
@@ -15,6 +15,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ProductionRules.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h
|
||||
@@ -41,6 +42,7 @@ SET(SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ProductionRules.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsCalculator.cpp
|
||||
|
||||
142
src/lib/sim/ProductionRules.cpp
Normal file
142
src/lib/sim/ProductionRules.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "ProductionRules.h"
|
||||
|
||||
#include "BuildingType.h"
|
||||
#include "ItemType.h"
|
||||
#include "ModulesConfig.h"
|
||||
#include "ShipsConfig.h"
|
||||
|
||||
std::vector<const RecipeDef*>
|
||||
gatherCandidateRecipes(const GameConfig& config, const Building& b)
|
||||
{
|
||||
std::vector<const RecipeDef*> candidates;
|
||||
if (isAutoRecipeBuildingType(b.type))
|
||||
{
|
||||
for (const RecipeDef& r : config.recipes.recipes)
|
||||
{
|
||||
if (r.building == b.type && !r.inputs.empty())
|
||||
{
|
||||
candidates.push_back(&r);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const RecipeDef* recipe = config.recipes.findRecipeDef(b.recipeId, b.type);
|
||||
if (recipe)
|
||||
{
|
||||
candidates.push_back(recipe);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
bool recipeInputsAvailable(const Building& b, const RecipeDef& recipe)
|
||||
{
|
||||
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>
|
||||
computeShipyardRequiredMaterials(const GameConfig& config, const Building& b)
|
||||
{
|
||||
std::map<std::string, int> requiredMaterials;
|
||||
const ShipDef* shipDef = 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 = config.modules.findModuleDef(pm.moduleId);
|
||||
if (!modDef)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (const RecipeIngredient& ing : modDef->materials)
|
||||
{
|
||||
requiredMaterials[ing.item] += ing.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return requiredMaterials;
|
||||
}
|
||||
bool hasInputsToStart(const GameConfig& config, const Building& b)
|
||||
{
|
||||
if (b.type == BuildingType::Shipyard)
|
||||
{
|
||||
const std::map<std::string, int> required =
|
||||
computeShipyardRequiredMaterials(config, 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(config, b))
|
||||
{
|
||||
if (recipeInputsAvailable(b, *recipe))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::optional<ProductionStatus>
|
||||
getProductionStatus(const GameConfig& config, const Building& building)
|
||||
{
|
||||
// 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(config, building) ? ProductionStatus::Blocked
|
||||
: ProductionStatus::Starved;
|
||||
}
|
||||
47
src/lib/sim/ProductionRules.h
Normal file
47
src/lib/sim/ProductionRules.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Building.h"
|
||||
#include "GameConfig.h"
|
||||
#include "RecipesConfig.h"
|
||||
|
||||
// Production state of a building for the UI status light (REQ-UI-STATUS-LIGHT).
|
||||
// The simulation owns the classification so it stays in sync with the
|
||||
// production-cycle predicates (REQ-MAT-CYCLE); the UI maps each value to a fill
|
||||
// color.
|
||||
enum class ProductionStatus
|
||||
{
|
||||
Unconfigured, // no recipe/schematic selected (grey)
|
||||
Producing, // a production cycle is active (green)
|
||||
Starved, // idle: a required input is missing / Salvage Bay empty (red)
|
||||
Blocked, // idle: output buffer full, inputs otherwise present (yellow)
|
||||
};
|
||||
|
||||
// The rules deciding what a building can produce and whether it can start.
|
||||
// Pure functions of the config and the building itself — they read no factory
|
||||
// state, so they are free functions rather than BuildingSystem members.
|
||||
|
||||
// Recipes this building could run: every recipe of its type for an auto-recipe
|
||||
// building (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING), otherwise just its selected one.
|
||||
std::vector<const RecipeDef*> gatherCandidateRecipes(const GameConfig& config,
|
||||
const Building& b);
|
||||
|
||||
// True when the building's input buffer holds every ingredient the recipe needs.
|
||||
bool recipeInputsAvailable(const Building& b, const RecipeDef& recipe);
|
||||
|
||||
// Total materials a shipyard needs for its schematic plus its placed modules
|
||||
// (REQ-BLD-SHIPYARD), keyed by item id.
|
||||
std::map<std::string, int> computeShipyardRequiredMaterials(const GameConfig& config,
|
||||
const Building& b);
|
||||
|
||||
// True when a production cycle could start right now, ignoring output-buffer space.
|
||||
bool hasInputsToStart(const GameConfig& config, const Building& b);
|
||||
|
||||
// Status light for a building, or nullopt for types that show none — belts,
|
||||
// splitters, tunnels, HQ and defence stations (REQ-UI-STATUS-LIGHT).
|
||||
std::optional<ProductionStatus> getProductionStatus(const GameConfig& config,
|
||||
const Building& building);
|
||||
Reference in New Issue
Block a user