allow the probabilistic recipe output of the reprocessing plant to yield more than 1 item of a type per cycle

This commit is contained in:
2026-08-12 21:45:09 +02:00
parent 0b859bd1a4
commit 4ee6438405
11 changed files with 358 additions and 206 deletions

View File

@@ -1,47 +1,13 @@
#include "ProductionRules.h"
#include <algorithm>
#include <limits>
#include <map>
#include "BuildingType.h"
#include "ItemType.h"
#include "ModulesConfig.h"
#include "ShipsConfig.h"
namespace
{
// Items one cycle of this recipe would deposit into the output buffer. A Reprocessing
// Plant rolls exactly one of its outputs per cycle (REQ-BLD-REPROCESSING) and the roll
// happens in the simulation, so the smallest amount any roll could yield is what decides
// whether a cycle could start at all; a larger roll may still not fit.
int getCycleOutputItemCount(const Building& b, const RecipeDef& recipe)
{
if (recipe.outputs.empty())
{
return 0;
}
if (b.type == BuildingType::ReprocessingPlant)
{
int smallest = std::numeric_limits<int>::max();
for (const RecipeOutput& out : recipe.outputs)
{
smallest = std::min(smallest, out.amount);
}
return smallest;
}
int total = 0;
for (const RecipeOutput& out : recipe.outputs)
{
total += out.amount;
}
return total;
}
} // namespace
std::vector<const RecipeDef*>
gatherCandidateRecipes(const GameConfig& config, const Building& b)
{
@@ -177,9 +143,44 @@ bool hasInputsToStart(const GameConfig& config, const Building& b)
return false;
}
bool outputBufferHasRoom(const Building& b, int outputItemCount)
bool outputBufferHasRoom(const Building& b, const ItemType& type, int itemCount)
{
return b.getOutputItemCount() + outputItemCount <= b.outputBuffer.capacity;
const std::map<ItemType, int>::const_iterator capIt = b.outputBuffer.caps.find(type);
const int cap = (capIt != b.outputBuffer.caps.end()) ? capIt->second : 0;
return b.getOutputItemCount(type) + itemCount <= cap;
}
bool recipeOutputsFit(const Building& b, const RecipeDef& recipe)
{
if (b.type == BuildingType::ReprocessingPlant)
{
// One roll yields one of these, so each is measured on its own -- but all of them
// have to fit, since which one it will be is not known yet.
for (const RecipeOutput& out : recipe.outputs)
{
if (!outputBufferHasRoom(b, ItemType{out.item}, out.amount))
{
return false;
}
}
return true;
}
// A deterministic cycle deposits all of its outputs together. An item listed more
// than once is produced in the sum of those amounts, so it is judged once, as a sum.
std::map<ItemType, int> perCycle;
for (const RecipeOutput& out : recipe.outputs)
{
perCycle[ItemType{out.item}] += out.amount;
}
for (const std::pair<const ItemType, int>& entry : perCycle)
{
if (!outputBufferHasRoom(b, entry.first, entry.second))
{
return false;
}
}
return true;
}
bool canStartCycle(const GameConfig& config, const Building& b)
@@ -193,8 +194,7 @@ bool canStartCycle(const GameConfig& config, const Building& b)
for (const RecipeDef* recipe : gatherCandidateRecipes(config, b))
{
if (recipeInputsAvailable(b, *recipe)
&& outputBufferHasRoom(b, getCycleOutputItemCount(b, *recipe)))
if (recipeInputsAvailable(b, *recipe) && recipeOutputsFit(b, *recipe))
{
return true;
}