Auto-process smelter and reprocessing plant (no recipe selection)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <random>
|
#include <random>
|
||||||
@@ -9,6 +10,18 @@
|
|||||||
#include "SurfaceMask.h"
|
#include "SurfaceMask.h"
|
||||||
#include "tracing.h"
|
#include "tracing.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Smelter and Reprocessing Plant have no player-selected recipe
|
||||||
|
// (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). They auto-process whatever inputs
|
||||||
|
// they receive, matching against every recipe of their building type.
|
||||||
|
bool isAutoRecipeBuildingType(BuildingType type)
|
||||||
|
{
|
||||||
|
return type == BuildingType::Smelter
|
||||||
|
|| type == BuildingType::ReprocessingPlant;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
BuildingSystem::BuildingSystem(const GameConfig& config,
|
BuildingSystem::BuildingSystem(const GameConfig& config,
|
||||||
BeltSystem& belts,
|
BeltSystem& belts,
|
||||||
std::function<BuildingId()> allocateBuildingId,
|
std::function<BuildingId()> allocateBuildingId,
|
||||||
@@ -118,6 +131,56 @@ void BuildingSystem::initBuffers(Building& b, const RecipeDef& recipe) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BuildingSystem::initAutoBuffers(Building& b) const
|
||||||
|
{
|
||||||
|
b.inputBuffer.counts.clear();
|
||||||
|
b.inputBuffer.caps.clear();
|
||||||
|
|
||||||
|
// Union the inputs of every recipe of this building type; the cap for each
|
||||||
|
// item is twice the largest per-cycle requirement across those recipes.
|
||||||
|
// Output capacity follows the same rules as initBuffers: the Reprocessing
|
||||||
|
// Plant holds one cycle's max output (REQ-MAT-OUTPUT-BUFFER-REPROCESSING),
|
||||||
|
// other auto buildings hold twice the largest per-cycle output.
|
||||||
|
int outputCapacity = 0;
|
||||||
|
for (const RecipeDef& recipe : m_config.recipes.recipes)
|
||||||
|
{
|
||||||
|
if (recipe.building != b.type)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const RecipeIngredient& ing : recipe.inputs)
|
||||||
|
{
|
||||||
|
const ItemType type{ing.item};
|
||||||
|
b.inputBuffer.counts[type] = 0;
|
||||||
|
b.inputBuffer.caps[type] =
|
||||||
|
std::max(b.inputBuffer.caps[type], 2 * ing.amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.type == BuildingType::ReprocessingPlant)
|
||||||
|
{
|
||||||
|
int maxAmount = 0;
|
||||||
|
for (const RecipeOutput& out : recipe.outputs)
|
||||||
|
{
|
||||||
|
maxAmount = std::max(maxAmount, out.amount);
|
||||||
|
}
|
||||||
|
outputCapacity = std::max(outputCapacity, maxAmount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int totalAmount = 0;
|
||||||
|
for (const RecipeOutput& out : recipe.outputs)
|
||||||
|
{
|
||||||
|
totalAmount += out.amount;
|
||||||
|
}
|
||||||
|
outputCapacity = std::max(outputCapacity, 2 * totalAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.outputBuffer.items.clear();
|
||||||
|
b.outputBuffer.capacity = outputCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
void BuildingSystem::initShipyardBuffers(Building& b) const
|
void BuildingSystem::initShipyardBuffers(Building& b) const
|
||||||
{
|
{
|
||||||
b.inputBuffer.counts.clear();
|
b.inputBuffer.counts.clear();
|
||||||
@@ -419,6 +482,12 @@ void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
|
|||||||
{
|
{
|
||||||
if (site.id == id)
|
if (site.id == id)
|
||||||
{
|
{
|
||||||
|
// Auto-recipe buildings have no player-selected recipe
|
||||||
|
// (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING); ignore any attempt to set one.
|
||||||
|
if (isAutoRecipeBuildingType(site.type))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
// No-op if the recipe is unchanged, so a redundant selection does
|
// No-op if the recipe is unchanged, so a redundant selection does
|
||||||
// not wipe an already-configured ship layout.
|
// not wipe an already-configured ship layout.
|
||||||
if (site.recipeId == recipeId)
|
if (site.recipeId == recipeId)
|
||||||
@@ -436,6 +505,12 @@ void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
|
|||||||
{
|
{
|
||||||
if (building.id == id)
|
if (building.id == id)
|
||||||
{
|
{
|
||||||
|
// Auto-recipe buildings have no player-selected recipe
|
||||||
|
// (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING); ignore any attempt to set one.
|
||||||
|
if (isAutoRecipeBuildingType(building.type))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
// No-op if the recipe is unchanged, so a redundant selection does
|
// No-op if the recipe is unchanged, so a redundant selection does
|
||||||
// not wipe an already-configured ship layout or reset buffers.
|
// not wipe an already-configured ship layout or reset buffers.
|
||||||
if (building.recipeId == recipeId)
|
if (building.recipeId == recipeId)
|
||||||
@@ -603,6 +678,12 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
|||||||
{
|
{
|
||||||
initSalvageBayBuffer(building);
|
initSalvageBayBuffer(building);
|
||||||
}
|
}
|
||||||
|
else if (isAutoRecipeBuildingType(building.type))
|
||||||
|
{
|
||||||
|
// Smelter/Reprocessing Plant need no recipe selection; buffers are set
|
||||||
|
// up from all recipes of the type (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING).
|
||||||
|
initAutoBuffers(building);
|
||||||
|
}
|
||||||
else if (!building.recipeId.empty())
|
else if (!building.recipeId.empty())
|
||||||
{
|
{
|
||||||
if (building.type == BuildingType::Shipyard)
|
if (building.type == BuildingType::Shipyard)
|
||||||
@@ -684,6 +765,11 @@ void BuildingSystem::tickBeltPull()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-recipe buildings (Smelter, Reprocessing Plant) accept any item
|
||||||
|
// that is an input to one of their recipes; their caps already span the
|
||||||
|
// union of those inputs (initAutoBuffers), so no recipe lookup is needed.
|
||||||
|
if (!isAutoRecipeBuildingType(building.type))
|
||||||
|
{
|
||||||
if (building.recipeId.empty())
|
if (building.recipeId.empty())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -697,6 +783,7 @@ void BuildingSystem::tickBeltPull()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const Port& port : building.inputPorts)
|
for (const Port& port : building.inputPorts)
|
||||||
{
|
{
|
||||||
@@ -752,18 +839,15 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (building.recipeId.empty())
|
const bool autoRecipe = isAutoRecipeBuildingType(building.type);
|
||||||
|
if (!autoRecipe && building.recipeId.empty())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RecipeDef* recipe = findRecipe(building.recipeId, building.type);
|
// If a production cycle is active, check for completion. Completion only
|
||||||
if (!recipe)
|
// needs the already-decided outputs, so it does not depend on which
|
||||||
{
|
// recipe is selected or auto-chosen.
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a production cycle is active, check for completion.
|
|
||||||
if (building.production)
|
if (building.production)
|
||||||
{
|
{
|
||||||
if (currentTick >= building.production->completesAt)
|
if (currentTick >= building.production->completesAt)
|
||||||
@@ -779,8 +863,33 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Idle: check if a new cycle can start.
|
// Idle: gather the candidate recipes to try. Auto-recipe buildings
|
||||||
|
// (Smelter, Reprocessing Plant) have no selected recipe and try every
|
||||||
|
// recipe of their type in config order, running the first whose inputs
|
||||||
|
// are satisfied (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). Other buildings
|
||||||
|
// try only their selected recipe.
|
||||||
|
std::vector<const RecipeDef*> candidates;
|
||||||
|
if (autoRecipe)
|
||||||
|
{
|
||||||
|
for (const RecipeDef& r : m_config.recipes.recipes)
|
||||||
|
{
|
||||||
|
if (r.building == building.type && !r.inputs.empty())
|
||||||
|
{
|
||||||
|
candidates.push_back(&r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const RecipeDef* recipe = findRecipe(building.recipeId, building.type);
|
||||||
|
if (recipe)
|
||||||
|
{
|
||||||
|
candidates.push_back(recipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const RecipeDef* recipe : candidates)
|
||||||
|
{
|
||||||
// 1. All required inputs present?
|
// 1. All required inputs present?
|
||||||
bool inputsOk = true;
|
bool inputsOk = true;
|
||||||
for (const RecipeIngredient& ing : recipe->inputs)
|
for (const RecipeIngredient& ing : recipe->inputs)
|
||||||
@@ -835,10 +944,12 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Production prod;
|
Production prod;
|
||||||
prod.recipeId = building.recipeId;
|
prod.recipeId = recipe->id;
|
||||||
prod.completesAt = currentTick + secondsToTicks(recipe->durationSeconds);
|
prod.completesAt = currentTick + secondsToTicks(recipe->durationSeconds);
|
||||||
prod.chosenOutputs = std::move(chosen);
|
prod.chosenOutputs = std::move(chosen);
|
||||||
building.production = std::move(prod);
|
building.production = std::move(prod);
|
||||||
|
break; // At most one cycle starts per tick.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,6 +169,10 @@ private:
|
|||||||
const ShipDef* findShipDef(const std::string& id) const;
|
const ShipDef* findShipDef(const std::string& id) const;
|
||||||
const ModuleDef* findModuleDef(const std::string& id) const;
|
const ModuleDef* findModuleDef(const std::string& id) 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
|
||||||
|
// caps span the union of every recipe of the building's type; no player
|
||||||
|
// recipe is selected (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING).
|
||||||
|
void initAutoBuffers(Building& b) const;
|
||||||
void initShipyardBuffers(Building& b) const;
|
void initShipyardBuffers(Building& b) const;
|
||||||
void initSalvageBayBuffer(Building& b) const;
|
void initSalvageBayBuffer(Building& b) const;
|
||||||
std::vector<Port> computeInputPorts(const Building& b) const;
|
std::vector<Port> computeInputPorts(const Building& b) const;
|
||||||
|
|||||||
@@ -474,7 +474,8 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
|
|||||||
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)), tick);
|
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)), tick);
|
||||||
REQUIRE(bs.productionBuildingCount() == 2);
|
REQUIRE(bs.productionBuildingCount() == 2);
|
||||||
|
|
||||||
// Neither has a recipe selected, so neither has an active cycle.
|
// Neither is producing yet: the miner has no recipe selected, and the
|
||||||
|
// smelter (auto-recipe, REQ-BLD-SMELTER) has no input feeding it.
|
||||||
REQUIRE(bs.activeProductionBuildingCount() == 0);
|
REQUIRE(bs.activeProductionBuildingCount() == 0);
|
||||||
|
|
||||||
bs.setRecipe(minerId, "mine_iron_ore");
|
bs.setRecipe(minerId, "mine_iron_ore");
|
||||||
@@ -542,7 +543,8 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
|
|||||||
// Smelter mask ["AA ","AA>"] → body (0,0),(1,0),(0,1),(1,1).
|
// Smelter mask ["AA ","AA>"] → body (0,0),(1,0),(0,1),(1,1).
|
||||||
// Output port (2,1) East. Input port example: (2,0) West.
|
// Output port (2,1) East. Input port example: (2,0) West.
|
||||||
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
|
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
|
||||||
bs.setRecipe(sid, "iron_ingot");
|
// Smelters have no recipe selection (REQ-BLD-SMELTER); they auto-accept any
|
||||||
|
// ore/scrap that is an input to a smelter recipe.
|
||||||
|
|
||||||
// Complete construction (15s → tick 450+1 = 451 ticks).
|
// Complete construction (15s → tick 450+1 = 451 ticks).
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
@@ -563,6 +565,105 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
|
|||||||
REQUIRE(it->second >= 1);
|
REQUIRE(it->second >= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A smelter auto-selects the matching recipe for whatever it is fed, with no
|
||||||
|
// player recipe selection (REQ-BLD-SMELTER).
|
||||||
|
TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
|
||||||
|
"[building]")
|
||||||
|
{
|
||||||
|
const GameConfig cfg = loadConfig();
|
||||||
|
BeltSystem belts(static_cast<double>(kTickRateHz));
|
||||||
|
int stock = 0;
|
||||||
|
std::mt19937 rng(0);
|
||||||
|
BuildingId nextBuildingId = 1;
|
||||||
|
BuildingSystem bs(cfg, belts,
|
||||||
|
[&nextBuildingId]() { return nextBuildingId++; },
|
||||||
|
[&stock](int n) { stock += n; },
|
||||||
|
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||||
|
[](const std::string&) -> bool { return true; },
|
||||||
|
rng);
|
||||||
|
|
||||||
|
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
|
||||||
|
|
||||||
|
Tick tick = 0;
|
||||||
|
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||||
|
|
||||||
|
// Feed 2 iron_ore (the test-config iron_ingot recipe needs 2) via a
|
||||||
|
// west-flowing belt at input port (2,0).
|
||||||
|
belts.placeBelt(QPoint(2, 0), Rotation::West);
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
|
||||||
|
belts.tick();
|
||||||
|
bs.tickBeltPull();
|
||||||
|
}
|
||||||
|
|
||||||
|
// iron_ingot recipe cycle is 2s; run to completion.
|
||||||
|
runTicks(bs, belts, static_cast<int>(secondsToTicks(2.0)) + 2, tick);
|
||||||
|
|
||||||
|
const Building* b = bs.findBuilding(sid);
|
||||||
|
REQUIRE(b != nullptr);
|
||||||
|
bool hasIronIngot = false;
|
||||||
|
for (const Item& item : b->outputBuffer.items)
|
||||||
|
{
|
||||||
|
if (item.type.id == "iron_ingot") { hasIronIngot = true; }
|
||||||
|
}
|
||||||
|
REQUIRE(hasIronIngot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// With mixed inputs, the smelter runs whichever recipe is currently satisfiable
|
||||||
|
// and leaves an incomplete batch of another input waiting (see the union-of-
|
||||||
|
// inputs caps in initAutoBuffers).
|
||||||
|
TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete batch waits",
|
||||||
|
"[building]")
|
||||||
|
{
|
||||||
|
const GameConfig cfg = loadConfig();
|
||||||
|
BeltSystem belts(static_cast<double>(kTickRateHz));
|
||||||
|
int stock = 0;
|
||||||
|
std::mt19937 rng(0);
|
||||||
|
BuildingId nextBuildingId = 1;
|
||||||
|
BuildingSystem bs(cfg, belts,
|
||||||
|
[&nextBuildingId]() { return nextBuildingId++; },
|
||||||
|
[&stock](int n) { stock += n; },
|
||||||
|
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||||
|
[](const std::string&) -> bool { return true; },
|
||||||
|
rng);
|
||||||
|
|
||||||
|
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
|
||||||
|
|
||||||
|
Tick tick = 0;
|
||||||
|
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||||
|
|
||||||
|
// Feed 1 iron_ore (iron_ingot needs 2 — incomplete) then 2 copper_ore
|
||||||
|
// (copper_ingot needs 2 — satisfiable) via the west-flowing input belt.
|
||||||
|
belts.placeBelt(QPoint(2, 0), Rotation::West);
|
||||||
|
const char* fed[] = { "iron_ore", "copper_ore", "copper_ore" };
|
||||||
|
for (const char* id : fed)
|
||||||
|
{
|
||||||
|
belts.tryPutItem(QPoint(2, 0), makeItem(id));
|
||||||
|
belts.tick();
|
||||||
|
bs.tickBeltPull();
|
||||||
|
}
|
||||||
|
|
||||||
|
// copper_ingot cycle is 2.5s; run to completion.
|
||||||
|
runTicks(bs, belts, static_cast<int>(secondsToTicks(2.5)) + 2, tick);
|
||||||
|
|
||||||
|
const Building* b = bs.findBuilding(sid);
|
||||||
|
REQUIRE(b != nullptr);
|
||||||
|
|
||||||
|
// Copper was smelted; the lone iron_ore still waits for a second unit.
|
||||||
|
bool hasCopperIngot = false;
|
||||||
|
for (const Item& item : b->outputBuffer.items)
|
||||||
|
{
|
||||||
|
if (item.type.id == "copper_ingot") { hasCopperIngot = true; }
|
||||||
|
}
|
||||||
|
REQUIRE(hasCopperIngot);
|
||||||
|
|
||||||
|
const std::map<ItemType, int>::const_iterator ironIt =
|
||||||
|
b->inputBuffer.counts.find(ItemType{"iron_ore"});
|
||||||
|
REQUIRE(ironIt != b->inputBuffer.counts.end());
|
||||||
|
REQUIRE(ironIt->second == 1);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Belt push → belt tile
|
// Belt push → belt tile
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -664,7 +765,8 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max
|
|||||||
|
|
||||||
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
|
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
|
||||||
QPoint(0, 0), Rotation::East, 0);
|
QPoint(0, 0), Rotation::East, 0);
|
||||||
bs.setRecipe(id, "reprocessing_cycle");
|
// Reprocessing plants have no recipe selection (REQ-BLD-REPROCESSING); the
|
||||||
|
// single reprocessing recipe is applied automatically on completion.
|
||||||
|
|
||||||
// Complete construction (25s).
|
// Complete construction (25s).
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
@@ -695,7 +797,8 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
|
|||||||
|
|
||||||
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
|
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
|
||||||
QPoint(0, 0), Rotation::East, 0);
|
QPoint(0, 0), Rotation::East, 0);
|
||||||
bs.setRecipe(id, "reprocessing_cycle");
|
// Reprocessing plants have no recipe selection (REQ-BLD-REPROCESSING); the
|
||||||
|
// single reprocessing recipe is applied automatically on completion.
|
||||||
|
|
||||||
// Complete construction (25s).
|
// Complete construction (25s).
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
|
|||||||
@@ -78,6 +78,25 @@ bool isProductionBuilding(BuildingType type)
|
|||||||
|| type == BuildingType::Shipyard;
|
|| type == BuildingType::Shipyard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buildings that expose a player recipe/schematic selection control
|
||||||
|
// (REQ-UI-SELECT-BUTTON): Miner ore type, Assembler recipe, Shipyard schematic.
|
||||||
|
// The Smelter and Reprocessing Plant auto-process and offer no selection
|
||||||
|
// (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING).
|
||||||
|
bool hasRecipeSelection(BuildingType type)
|
||||||
|
{
|
||||||
|
return type == BuildingType::Miner
|
||||||
|
|| type == BuildingType::Assembler
|
||||||
|
|| type == BuildingType::Shipyard;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-recipe buildings have no selected recipe; their production is driven by
|
||||||
|
// whatever inputs they receive.
|
||||||
|
bool isAutoRecipeBuilding(BuildingType type)
|
||||||
|
{
|
||||||
|
return type == BuildingType::Smelter
|
||||||
|
|| type == BuildingType::ReprocessingPlant;
|
||||||
|
}
|
||||||
|
|
||||||
bool isBeltLike(BuildingType type)
|
bool isBeltLike(BuildingType type)
|
||||||
{
|
{
|
||||||
return type == BuildingType::Belt || type == BuildingType::Splitter
|
return type == BuildingType::Belt || type == BuildingType::Splitter
|
||||||
@@ -265,7 +284,7 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
|||||||
m_titleLabel->show();
|
m_titleLabel->show();
|
||||||
m_buffersLabel->show();
|
m_buffersLabel->show();
|
||||||
|
|
||||||
if (isProductionBuilding(type))
|
if (hasRecipeSelection(type))
|
||||||
{
|
{
|
||||||
const std::vector<RecipeSelectionOption> options =
|
const std::vector<RecipeSelectionOption> options =
|
||||||
buildRecipeSelectionOptions(type, *m_sim, *m_config);
|
buildRecipeSelectionOptions(type, *m_sim, *m_config);
|
||||||
@@ -383,6 +402,21 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|||||||
? findShipDef(b->recipeId)
|
? findShipDef(b->recipeId)
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
|
// Auto-recipe buildings (Smelter, Reprocessing Plant) have no selected
|
||||||
|
// recipe; while a cycle runs, resolve the recipe actually in production so
|
||||||
|
// the cycle time and progress can be shown (REQ-UI-PRODUCTION-PROGRESS).
|
||||||
|
if (!recipe && isAutoRecipeBuilding(b->type) && b->production.has_value())
|
||||||
|
{
|
||||||
|
for (const RecipeDef& r : m_config->recipes.recipes)
|
||||||
|
{
|
||||||
|
if (r.id == b->production->recipeId && r.building == b->type)
|
||||||
|
{
|
||||||
|
recipe = &r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString bufText;
|
QString bufText;
|
||||||
|
|
||||||
if (!b->inputBuffer.counts.empty())
|
if (!b->inputBuffer.counts.empty())
|
||||||
@@ -469,7 +503,10 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProductionBuilding(b->type) && (recipe || shipDef))
|
if (isProductionBuilding(b->type)
|
||||||
|
&& (recipe || shipDef || isAutoRecipeBuilding(b->type)))
|
||||||
|
{
|
||||||
|
if (recipe || shipDef)
|
||||||
{
|
{
|
||||||
double durationSeconds = recipe
|
double durationSeconds = recipe
|
||||||
? recipe->durationSeconds
|
? recipe->durationSeconds
|
||||||
@@ -507,6 +544,13 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
|||||||
bufText += tr("Progress: idle\n");
|
bufText += tr("Progress: idle\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Auto-recipe building with no active cycle: no single recipe to
|
||||||
|
// show a cycle time for.
|
||||||
|
bufText += tr("Progress: idle\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_buffersLabel->setText(bufText);
|
m_buffersLabel->setText(bufText);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user