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
125 lines
5.4 KiB
C++
125 lines
5.4 KiB
C++
#pragma once
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <QString>
|
|
#include <QWidget>
|
|
|
|
#include "BuildingType.h"
|
|
#include "RecipesConfig.h"
|
|
|
|
class BuildingIconCache;
|
|
class ItemIconCache;
|
|
class QHBoxLayout;
|
|
class QVBoxLayout;
|
|
|
|
// One recipe drawn: what it consumes, an arrow, what it produces, and how long a cycle
|
|
// takes (REQ-UI-RECIPE-SUMMARY), under a header naming the building that runs it and the
|
|
// recipe itself where the recipe has to identify itself. Every place the UI states what
|
|
// something makes draws this same line, so a recipe reads alike wherever it is shown:
|
|
//
|
|
// * beneath the selection button, for the recipe a building is running
|
|
// (REQ-UI-RECIPE-SUMMARY);
|
|
// * on a selection dialog's option buttons, under the option's name
|
|
// (REQ-UI-SELECT-OPTIONS);
|
|
// * in an item's production tooltip and in the unlock-choice dialog, there led by the
|
|
// icon of the building that runs the recipe and its name
|
|
// (REQ-UI-ITEM-TOOLTIP, REQ-DEF-SCHEMATIC-DROP);
|
|
// * on a module button and in the layout dialog's build cost, where there is a price
|
|
// and a time but nothing produced (REQ-MOD-UI-DIALOG, REQ-MOD-UI-STATS-PANEL).
|
|
class RecipeLineRow : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
// One item and how many of it the line states.
|
|
struct Amount
|
|
{
|
|
std::string itemId;
|
|
int amount = 0;
|
|
|
|
bool operator==(const Amount& other) const
|
|
{
|
|
return itemId == other.itemId && amount == other.amount;
|
|
}
|
|
};
|
|
|
|
// What the line says. Everything but the inputs is optional, which is what lets the
|
|
// one widget serve the summary, the option buttons, the tooltip lines and the cost
|
|
// lines.
|
|
struct Spec
|
|
{
|
|
// The chip of the building running the recipe, on the header line, where the
|
|
// recipe has to say which building that is -- an item may have several producers
|
|
// (REQ-UI-ITEM-TOOLTIP). Unset wherever the surrounding widget already
|
|
// establishes the building, and the header line is then left out entirely.
|
|
std::optional<BuildingType> building;
|
|
// The recipe's name, beside the building chip on the header line. Empty where the
|
|
// name is the caption of the widget around this one instead.
|
|
QString name;
|
|
std::vector<Amount> inputs;
|
|
// 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).
|
|
bool durationIsAddition = false;
|
|
|
|
bool operator==(const Spec& other) const
|
|
{
|
|
return building == other.building && name == other.name
|
|
&& inputs == other.inputs && outputGroups == other.outputGroups
|
|
&& durationSeconds == other.durationSeconds
|
|
&& durationIsAddition == other.durationIsAddition;
|
|
}
|
|
|
|
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.
|
|
RecipeLineRow(ItemIconCache* itemIcons, BuildingIconCache* buildingIcons,
|
|
QWidget* parent = nullptr);
|
|
|
|
// Hides the row when the spec has nothing to state, which is how a building with no
|
|
// recipe shows no summary at all (REQ-UI-RECIPE-SUMMARY).
|
|
void setLine(const Spec& spec);
|
|
|
|
// Boxes the line in a card of its own, for the two places that stack several of them
|
|
// (REQ-UI-ITEM-TOOLTIP, REQ-DEF-SCHEMATIC-DROP): a run of bare lines reads as one
|
|
// field of icons and numbers, and the box is what separates one recipe from the next.
|
|
// Off by default -- a line drawn inside a button or a panel section is already framed
|
|
// by what holds it.
|
|
void setCardChrome(bool enabled);
|
|
|
|
private:
|
|
void rebuild(const Spec& spec);
|
|
void addAmounts(const std::vector<Amount>& amounts);
|
|
|
|
ItemIconCache* m_itemIcons;
|
|
BuildingIconCache* m_buildingIcons;
|
|
QVBoxLayout* m_outerLayout;
|
|
// The two lines, each a row widget of its own so a rebuild only has to empty their
|
|
// layouts -- no nested layout to take apart. The header is hidden where the spec
|
|
// names neither a building nor a recipe.
|
|
QWidget* m_headerRow;
|
|
QHBoxLayout* m_headerLayout;
|
|
QWidget* m_amountsRow;
|
|
QHBoxLayout* m_amountsLayout;
|
|
// What the row currently shows, so a refresh at tick rate rebuilds it only when the
|
|
// recipe actually changed.
|
|
Spec m_spec;
|
|
};
|