extract the production rules as free functions over config and building
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include "FactoryQueries.h"
|
#include "FactoryQueries.h"
|
||||||
|
#include "ProductionRules.h"
|
||||||
#include "PortGeometry.h"
|
#include "PortGeometry.h"
|
||||||
#include "StateChecksum.h"
|
#include "StateChecksum.h"
|
||||||
#include "SurfaceMask.h"
|
#include "SurfaceMask.h"
|
||||||
@@ -1008,7 +1009,7 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
|||||||
// are satisfied (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). Other buildings
|
// are satisfied (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). Other buildings
|
||||||
// try only their selected recipe.
|
// try only their selected recipe.
|
||||||
const std::vector<const RecipeDef*> candidates =
|
const std::vector<const RecipeDef*> candidates =
|
||||||
gatherCandidateRecipes(building);
|
gatherCandidateRecipes(m_config, building);
|
||||||
|
|
||||||
for (const RecipeDef* recipe : candidates)
|
for (const RecipeDef* recipe : candidates)
|
||||||
{
|
{
|
||||||
@@ -1112,7 +1113,7 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
|
|||||||
|
|
||||||
// Build combined materials list (base + modules).
|
// Build combined materials list (base + modules).
|
||||||
const std::map<std::string, int> requiredMaterials =
|
const std::map<std::string, int> requiredMaterials =
|
||||||
computeShipyardRequiredMaterials(building);
|
computeShipyardRequiredMaterials(m_config, building);
|
||||||
|
|
||||||
// Idle: check if all combined materials are available.
|
// Idle: check if all combined materials are available.
|
||||||
bool inputsOk = true;
|
bool inputsOk = true;
|
||||||
@@ -1257,146 +1258,10 @@ void BuildingSystem::forEachIncomingItem(
|
|||||||
// Queries
|
// 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
|
std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "BeltSystem.h"
|
#include "BeltSystem.h"
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "FactoryState.h"
|
#include "FactoryState.h"
|
||||||
|
#include "ProductionRules.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
@@ -27,18 +28,6 @@
|
|||||||
|
|
||||||
class Hasher;
|
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
|
// Manages building placement, construction queuing, and the per-tick
|
||||||
// production loop (belt→building pull, production, building→belt push).
|
// production loop (belt→building pull, production, building→belt push).
|
||||||
// All types including Belt and Splitter are stored as Building instances;
|
// 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,
|
// nullopt for building types that show no light (belts, splitters, tunnels,
|
||||||
// HQ, defence stations). The Salvage Bay is a two-state special case:
|
// HQ, defence stations). The Salvage Bay is a two-state special case:
|
||||||
// Producing while its output buffer holds scrap, Starved when empty.
|
// Producing while its output buffer holds scrap, Starved when empty.
|
||||||
std::optional<ProductionStatus> getProductionStatus(const Building& building) const;
|
|
||||||
std::vector<BeltTileInfo> getAllBeltTiles() const;
|
std::vector<BeltTileInfo> getAllBeltTiles() const;
|
||||||
|
|
||||||
// Visits every item currently emerging from a building output port on its
|
// 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
|
// building (Smelter, Reprocessing Plant) offers every recipe of its type with
|
||||||
// inputs; other buildings offer only their selected recipe. Shared by
|
// inputs; other buildings offer only their selected recipe. Shared by
|
||||||
// tickProduction and the status classifier (REQ-MAT-CYCLE, REQ-UI-STATUS-LIGHT).
|
// 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
|
// True if every input of `recipe` is present in `b`'s input buffers in the
|
||||||
// required per-cycle amount (REQ-MAT-CYCLE input check).
|
// 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).
|
// 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
|
// True if the building currently has all inputs/materials to start a cycle
|
||||||
// (ignoring output-buffer space); drives the Starved/Blocked distinction of
|
// (ignoring output-buffer space); drives the Starved/Blocked distinction of
|
||||||
// the status light (REQ-UI-STATUS-LIGHT).
|
// the status light (REQ-UI-STATUS-LIGHT).
|
||||||
bool hasInputsToStart(const Building& b) const;
|
|
||||||
|
|
||||||
void initBuffers(Building& b, const RecipeDef& recipe) const;
|
void initBuffers(Building& b, const RecipeDef& recipe) const;
|
||||||
// Buffers for an auto-recipe building (Smelter, Reprocessing Plant): input
|
// 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}/BuildingGrid.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h
|
${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.h
|
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ProductionRules.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h
|
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h
|
||||||
@@ -41,6 +42,7 @@ SET(SRCS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ProductionRules.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsCalculator.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);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
#include "FactoryQueries.h"
|
#include "FactoryQueries.h"
|
||||||
|
#include "ProductionRules.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <random>
|
#include <random>
|
||||||
@@ -1562,7 +1563,7 @@ TEST_CASE("BuildingSystem: getProductionStatus classifies production state", "[b
|
|||||||
}
|
}
|
||||||
REQUIRE_FALSE(shipId.empty());
|
REQUIRE_FALSE(shipId.empty());
|
||||||
|
|
||||||
const auto statusOf = [&f](const Building& b) { return f.bs.getProductionStatus(b); };
|
const auto statusOf = [&f](const Building& b) { return getProductionStatus(f.cfg, b); };
|
||||||
|
|
||||||
SECTION("non-production buildings show no status light")
|
SECTION("non-production buildings show no status light")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "GameWorldView.h"
|
#include "GameWorldView.h"
|
||||||
#include "FactoryQueries.h"
|
#include "FactoryQueries.h"
|
||||||
|
#include "ProductionRules.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -1341,7 +1342,7 @@ void GameWorldView::drawBuildings(QPainter& painter)
|
|||||||
// building footprints are rectangular, so a corner of the axis-aligned
|
// building footprints are rectangular, so a corner of the axis-aligned
|
||||||
// bounding box is the true corner.
|
// bounding box is the true corner.
|
||||||
if (const std::optional<ProductionStatus> status =
|
if (const std::optional<ProductionStatus> status =
|
||||||
m_sim->getBuildings().getProductionStatus(b))
|
getProductionStatus(m_sim->getConfig(), b))
|
||||||
{
|
{
|
||||||
const float px = getTilePx();
|
const float px = getTilePx();
|
||||||
const float r = px * 0.18f;
|
const float r = px * 0.18f;
|
||||||
|
|||||||
Reference in New Issue
Block a user