Files
dota_factory/src/ui/RecipeLineRow.h
Malte Langkabel 9c275e283c 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
2026-08-17 12:47:09 +02:00

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;
};