give every recipe one shape: a list of output groups
Implements REQ-MAT-OUTPUT-GROUP. A recipe had two shapes -- outputs produced together, or outputs of which exactly one happened -- and every rule over them was written twice, selected by `building == ReprocessingPlant`: sizing a buffer, deciding whether a cycle fits, resolving what a cycle makes, costing an item. RecipeDef now holds output groups, each a weight and a list of items, and a cycle yields exactly one group. One group is the ordinary recipe, so the old two cases are the same shape with one and with several, and all four rules collapse to one expression apiece with no building-type test left. rollReprocessingOutput becomes rollOutputGroup, where a single group returns without drawing or testing eligibility. That early-out is load-bearing twice over. Drawing there would consume entropy for every ordinary recipe and shift every later random outcome; and eligibility must not apply either, since implicit unlocking is demand-derived, so an ordinary recipe's output can be producible while nothing yet calls for it -- testing it would stop the building producing rather than gate a drop. Past the early-out a group is eligible only when all of its items are unlocked, being produced whole. Threat follows the recipe's shape rather than the building, and the per-unit value now divides by the group's amount as well as its odds. That moves no number today: every item resolved through this path has amount 1, which is why the threat expectations are untouched. Config keeps `outputs = [...]` as the single-group form, so only the two reprocessing recipes change shape. The recipe summary gains "/" between groups and keeps "+" within one, which also fixes the plant reading as though a cycle produced all of its items at once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -7,18 +7,6 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
bool producesItem(const RecipeDef& recipe, const std::string& itemId)
|
||||
{
|
||||
for (const RecipeOutput& output : recipe.outputs)
|
||||
{
|
||||
if (output.item == itemId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAvailable(const RecipeDef& recipe, const Simulation& sim)
|
||||
{
|
||||
if (recipe.building == BuildingType::Miner
|
||||
|
||||
@@ -47,6 +47,24 @@ void clearRow(QHBoxLayout* layout)
|
||||
} // namespace
|
||||
|
||||
|
||||
std::vector<std::vector<RecipeLineRow::Amount>> RecipeLineRow::toOutputGroups(
|
||||
const RecipeDef& recipe)
|
||||
{
|
||||
std::vector<std::vector<Amount>> groups;
|
||||
groups.reserve(recipe.outputGroups.size());
|
||||
for (const RecipeOutputGroup& group : recipe.outputGroups)
|
||||
{
|
||||
std::vector<Amount> amounts;
|
||||
amounts.reserve(group.items.size());
|
||||
for (const RecipeOutput& out : group.items)
|
||||
{
|
||||
amounts.push_back(Amount{ out.item, out.amount });
|
||||
}
|
||||
groups.push_back(std::move(amounts));
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
RecipeLineRow::RecipeLineRow(ItemIconCache* itemIcons, BuildingIconCache* buildingIcons,
|
||||
QWidget* parent)
|
||||
: QWidget(parent)
|
||||
@@ -143,12 +161,21 @@ void RecipeLineRow::rebuild(const Spec& spec)
|
||||
|
||||
// Second line: what the cycle costs, makes and takes.
|
||||
addAmounts(spec.inputs);
|
||||
if (!spec.inputs.empty() && !spec.outputs.empty())
|
||||
if (!spec.inputs.empty() && !spec.outputGroups.empty())
|
||||
{
|
||||
const QChar rightArrow(0x2192); // U+2192 RIGHTWARDS ARROW
|
||||
addAndShow(m_amountsLayout, new QLabel(QString(rightArrow), m_amountsRow));
|
||||
}
|
||||
addAmounts(spec.outputs);
|
||||
for (std::size_t i = 0; i < spec.outputGroups.size(); ++i)
|
||||
{
|
||||
// Between one group and the next, so alternatives read as a choice rather than as
|
||||
// one combined yield -- which is what a run of `+` would say (REQ-UI-RECIPE-SUMMARY).
|
||||
if (i > 0)
|
||||
{
|
||||
addAndShow(m_amountsLayout, new QLabel(QStringLiteral("/"), m_amountsRow));
|
||||
}
|
||||
addAmounts(spec.outputGroups[i]);
|
||||
}
|
||||
|
||||
if (spec.durationSeconds.has_value() && *spec.durationSeconds > 0.0)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "BuildingType.h"
|
||||
#include "RecipesConfig.h"
|
||||
|
||||
class BuildingIconCache;
|
||||
class ItemIconCache;
|
||||
@@ -59,9 +60,12 @@ public:
|
||||
// name is the caption of the widget around this one instead.
|
||||
QString name;
|
||||
std::vector<Amount> inputs;
|
||||
// Empty for a line that produces no item of its own: a ship schematic, or a
|
||||
// module's price. No arrow is drawn then.
|
||||
std::vector<Amount> outputs;
|
||||
// What the recipe produces, one entry per output group (REQ-MAT-OUTPUT-GROUP).
|
||||
// The items of a group are drawn joined by `+` because they come together, and the
|
||||
// groups joined by `/` because only one of them happens. Empty for a line that
|
||||
// produces no item of its own: a ship schematic, or a module's price. No arrow is
|
||||
// drawn then.
|
||||
std::vector<std::vector<Amount>> outputGroups;
|
||||
std::optional<double> durationSeconds;
|
||||
// True where the time is added to something else rather than being a cycle of
|
||||
// its own, and so reads "+3.0 s" (REQ-MOD-UI-DIALOG).
|
||||
@@ -70,14 +74,19 @@ public:
|
||||
bool operator==(const Spec& other) const
|
||||
{
|
||||
return building == other.building && name == other.name
|
||||
&& inputs == other.inputs && outputs == other.outputs
|
||||
&& inputs == other.inputs && outputGroups == other.outputGroups
|
||||
&& durationSeconds == other.durationSeconds
|
||||
&& durationIsAddition == other.durationIsAddition;
|
||||
}
|
||||
|
||||
bool isEmpty() const { return inputs.empty() && outputs.empty(); }
|
||||
bool isEmpty() const { return inputs.empty() && outputGroups.empty(); }
|
||||
};
|
||||
|
||||
// A recipe's output groups as this row states them (REQ-MAT-OUTPUT-GROUP). Shared, so
|
||||
// that every place drawing a recipe -- the summary, the option buttons, the tooltip
|
||||
// lines -- converts it the same way rather than each keeping its own copy.
|
||||
static std::vector<std::vector<Amount>> toOutputGroups(const RecipeDef& recipe);
|
||||
|
||||
// Both caches may be null, which leaves the icons off: an item with no square and
|
||||
// no icon falls back to its id, and a building with no chip to its name alone.
|
||||
// Neither is owned.
|
||||
|
||||
@@ -33,16 +33,6 @@ std::vector<RecipeLineRow::Amount> toAmounts(
|
||||
return amounts;
|
||||
}
|
||||
|
||||
std::vector<RecipeLineRow::Amount> toAmounts(const std::vector<RecipeOutput>& outputs)
|
||||
{
|
||||
std::vector<RecipeLineRow::Amount> amounts;
|
||||
amounts.reserve(outputs.size());
|
||||
for (const RecipeOutput& output : outputs)
|
||||
{
|
||||
amounts.push_back(RecipeLineRow::Amount{ output.item, output.amount });
|
||||
}
|
||||
return amounts;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -90,7 +80,7 @@ std::vector<RecipeSelectionOption> buildRecipeSelectionOptions(
|
||||
|
||||
RecipeLineRow::Spec line;
|
||||
line.inputs = toAmounts(recipe.inputs);
|
||||
line.outputs = toAmounts(recipe.outputs);
|
||||
line.outputGroups = RecipeLineRow::toOutputGroups(recipe);
|
||||
line.durationSeconds = recipe.durationSeconds;
|
||||
|
||||
options.push_back({recipe.id,
|
||||
|
||||
@@ -24,16 +24,6 @@ std::vector<RecipeLineRow::Amount> toAmounts(
|
||||
return amounts;
|
||||
}
|
||||
|
||||
std::vector<RecipeLineRow::Amount> toAmounts(const std::vector<RecipeOutput>& outputs)
|
||||
{
|
||||
std::vector<RecipeLineRow::Amount> amounts;
|
||||
amounts.reserve(outputs.size());
|
||||
for (const RecipeOutput& output : outputs)
|
||||
{
|
||||
amounts.push_back(RecipeLineRow::Amount{ output.item, output.amount });
|
||||
}
|
||||
return amounts;
|
||||
}
|
||||
|
||||
QString grantKindLabel(SchematicType type)
|
||||
{
|
||||
@@ -147,7 +137,7 @@ SchematicChoiceDialog::SchematicChoiceDialog(
|
||||
spec.building = def->building;
|
||||
spec.name = QString::fromStdString(toDisplayName(def->id));
|
||||
spec.inputs = toAmounts(def->inputs);
|
||||
spec.outputs = toAmounts(def->outputs);
|
||||
spec.outputGroups = RecipeLineRow::toOutputGroups(*def);
|
||||
spec.durationSeconds = def->durationSeconds;
|
||||
|
||||
RecipeLineRow* line =
|
||||
|
||||
@@ -103,7 +103,7 @@ void BufferedBuildingContent::refreshConfiguration()
|
||||
const CycleInfo cycle = getCycleInfo(target);
|
||||
RecipeLineRow::Spec summary;
|
||||
summary.inputs = toAmounts(cycle.perCycleInputs);
|
||||
summary.outputs = toAmounts(cycle.perCycleOutputs);
|
||||
summary.outputGroups = cycle.perCycleOutputGroups;
|
||||
summary.durationSeconds = cycle.durationSeconds;
|
||||
m_recipeSummary->setLine(summary);
|
||||
|
||||
@@ -192,9 +192,20 @@ std::vector<ItemChipRow::Entry> BufferedBuildingContent::buildOutputEntries(
|
||||
}
|
||||
}
|
||||
|
||||
// A chip stands for a buffer, and a buffer exists for every item any group can
|
||||
// produce (REQ-MAT-OUTPUT-BUFFER), so the groups are flattened here.
|
||||
std::map<std::string, int> producible;
|
||||
for (const std::vector<RecipeLineRow::Amount>& group : cycle.perCycleOutputGroups)
|
||||
{
|
||||
for (const RecipeLineRow::Amount& amount : group)
|
||||
{
|
||||
producible[amount.itemId] = std::max(producible[amount.itemId], amount.amount);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ItemChipRow::Entry> entries;
|
||||
for (const std::string& itemId :
|
||||
collectItemIds(buffered, cycle.perCycleOutputs, cycle.handledOutputs))
|
||||
collectItemIds(buffered, producible, cycle.handledOutputs))
|
||||
{
|
||||
if (!getContext().sim->isItemUnlocked(itemId)) { continue; }
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "BuildingId.h"
|
||||
#include "ItemChipRow.h"
|
||||
#include "RecipeLineRow.h"
|
||||
#include "SelectionContent.h"
|
||||
|
||||
struct Building;
|
||||
@@ -31,7 +32,11 @@ protected:
|
||||
struct CycleInfo
|
||||
{
|
||||
std::map<std::string, int> perCycleInputs;
|
||||
std::map<std::string, int> perCycleOutputs;
|
||||
// What one cycle produces, one entry per output group (REQ-MAT-OUTPUT-GROUP), so
|
||||
// the summary can state alternatives as such. The output chips are listed from
|
||||
// this too, flattened: a chip stands for a buffer, and a buffer exists for every
|
||||
// item any group can produce.
|
||||
std::vector<std::vector<RecipeLineRow::Amount>> perCycleOutputGroups;
|
||||
|
||||
// Items the card lists whether or not they are currently in the buffers, for a
|
||||
// building whose recipe is implicit and so has nothing to name while it sits
|
||||
|
||||
@@ -35,16 +35,6 @@ std::vector<RecipeLineRow::Amount> toAmounts(
|
||||
return amounts;
|
||||
}
|
||||
|
||||
std::vector<RecipeLineRow::Amount> toAmounts(const std::vector<RecipeOutput>& outputs)
|
||||
{
|
||||
std::vector<RecipeLineRow::Amount> amounts;
|
||||
amounts.reserve(outputs.size());
|
||||
for (const RecipeOutput& output : outputs)
|
||||
{
|
||||
amounts.push_back(RecipeLineRow::Amount{ output.item, output.amount });
|
||||
}
|
||||
return amounts;
|
||||
}
|
||||
|
||||
// The screen the cursor is on, falling back to the primary screen when the position is
|
||||
// on none of them (a cursor between two screens of different heights).
|
||||
@@ -152,7 +142,7 @@ void ItemTooltip::rebuild()
|
||||
spec.building = recipe->building;
|
||||
spec.name = QString::fromStdString(toDisplayName(recipe->id));
|
||||
spec.inputs = toAmounts(recipe->inputs);
|
||||
spec.outputs = toAmounts(recipe->outputs);
|
||||
spec.outputGroups = RecipeLineRow::toOutputGroups(*recipe);
|
||||
spec.durationSeconds = recipe->durationSeconds;
|
||||
|
||||
// Boxed, because an item with several producers stacks several of these and a run
|
||||
|
||||
@@ -41,10 +41,7 @@ BufferedBuildingContent::CycleInfo RecipeProductionContent::getCycleInfo(
|
||||
{
|
||||
info.perCycleInputs[ingredient.item] = ingredient.amount;
|
||||
}
|
||||
for (const RecipeOutput& output : recipe->outputs)
|
||||
{
|
||||
info.perCycleOutputs[output.item] = output.amount;
|
||||
}
|
||||
info.perCycleOutputGroups = RecipeLineRow::toOutputGroups(*recipe);
|
||||
info.runsProduction = true;
|
||||
info.durationSeconds = recipe->durationSeconds;
|
||||
return info;
|
||||
|
||||
@@ -57,9 +57,9 @@ std::vector<std::string> getAllItemIds(const RecipesConfig& recipes)
|
||||
{
|
||||
seen.insert(ingredient.item);
|
||||
}
|
||||
for (const RecipeOutput& output : recipe.outputs)
|
||||
for (const std::string& item : getProducibleItems(recipe))
|
||||
{
|
||||
seen.insert(output.item);
|
||||
seen.insert(item);
|
||||
}
|
||||
}
|
||||
return std::vector<std::string>(seen.begin(), seen.end());
|
||||
|
||||
Reference in New Issue
Block a user