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
175 lines
6.2 KiB
C++
175 lines
6.2 KiB
C++
#include "SchematicChoiceDialog.h"
|
|
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "DisplayName.h"
|
|
#include "RecipeLineRow.h"
|
|
#include "RecipesConfig.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
std::vector<RecipeLineRow::Amount> toAmounts(
|
|
const std::vector<RecipeIngredient>& ingredients)
|
|
{
|
|
std::vector<RecipeLineRow::Amount> amounts;
|
|
amounts.reserve(ingredients.size());
|
|
for (const RecipeIngredient& ingredient : ingredients)
|
|
{
|
|
amounts.push_back(RecipeLineRow::Amount{ ingredient.item, ingredient.amount });
|
|
}
|
|
return amounts;
|
|
}
|
|
|
|
|
|
QString grantKindLabel(SchematicType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SchematicType::Ship: return SchematicChoiceDialog::tr("Ship");
|
|
case SchematicType::Module: return SchematicChoiceDialog::tr("Module");
|
|
case SchematicType::Building: return SchematicChoiceDialog::tr("Building");
|
|
case SchematicType::Recipe: return SchematicChoiceDialog::tr("Recipe");
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
SchematicChoiceDialog::SchematicChoiceDialog(
|
|
const std::vector<SchematicChoiceOption>& options,
|
|
const RecipesConfig& recipes, ItemIconCache* itemIcons,
|
|
BuildingIconCache* buildingIcons,
|
|
QWidget* parent)
|
|
: QDialog(parent)
|
|
, m_chosenIndex(0)
|
|
{
|
|
setWindowTitle(tr("Schematic Drop"));
|
|
setWindowFlags(windowFlags() & ~Qt::WindowCloseButtonHint);
|
|
setModal(true);
|
|
|
|
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
|
|
|
QLabel* titleLabel = new QLabel(tr("Choose a schematic to unlock:"), this);
|
|
QFont titleFont = titleLabel->font();
|
|
titleFont.setPointSize(titleFont.pointSize() + 2);
|
|
titleFont.setBold(true);
|
|
titleLabel->setFont(titleFont);
|
|
titleLabel->setAlignment(Qt::AlignCenter);
|
|
mainLayout->addWidget(titleLabel);
|
|
|
|
QHBoxLayout* optionsLayout = new QHBoxLayout();
|
|
mainLayout->addLayout(optionsLayout);
|
|
|
|
for (int i = 0; i < static_cast<int>(options.size()); ++i)
|
|
{
|
|
const SchematicChoiceOption& option = options[static_cast<std::size_t>(i)];
|
|
|
|
QWidget* card = new QWidget(this);
|
|
QVBoxLayout* cardLayout = new QVBoxLayout(card);
|
|
// Scoped to the card by object name. An unscoped "QWidget" selector cascades to
|
|
// every descendant, which boxed each label and each icon of the recipe lines
|
|
// inside it as well.
|
|
card->setObjectName(QStringLiteral("schematicChoiceCard"));
|
|
card->setStyleSheet(QStringLiteral(
|
|
"QWidget#schematicChoiceCard { border: 1px solid gray; padding: 8px; }"));
|
|
|
|
QLabel* nameLabel = new QLabel(QString::fromStdString(option.displayName), card);
|
|
QFont nameFont = nameLabel->font();
|
|
nameFont.setPointSize(nameFont.pointSize() + 1);
|
|
nameFont.setBold(true);
|
|
nameLabel->setFont(nameFont);
|
|
nameLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(nameLabel);
|
|
|
|
if (option.isArtifact)
|
|
{
|
|
QLabel* artifactTypeLabel = new QLabel(tr("Artifact"), card);
|
|
artifactTypeLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(artifactTypeLabel);
|
|
}
|
|
else
|
|
{
|
|
QLabel* grantsHeaderLabel = new QLabel(tr("Unlocks:"), card);
|
|
QFont grantsHeaderFont = grantsHeaderLabel->font();
|
|
grantsHeaderFont.setBold(true);
|
|
grantsHeaderLabel->setFont(grantsHeaderFont);
|
|
grantsHeaderLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(grantsHeaderLabel);
|
|
|
|
for (const GrantedSchematic& grant : option.grantedItems)
|
|
{
|
|
QLabel* grantLabel = new QLabel(
|
|
grantKindLabel(grant.type) + ": "
|
|
+ QString::fromStdString(grant.displayName),
|
|
card);
|
|
grantLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(grantLabel);
|
|
}
|
|
|
|
QLabel* unlocksHeaderLabel = new QLabel(tr("Unlocks recipes:"), card);
|
|
QFont unlocksHeaderFont = unlocksHeaderLabel->font();
|
|
unlocksHeaderFont.setBold(true);
|
|
unlocksHeaderLabel->setFont(unlocksHeaderFont);
|
|
unlocksHeaderLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(unlocksHeaderLabel);
|
|
|
|
if (option.newlyUnlockedRecipeIds.empty())
|
|
{
|
|
QLabel* noneLabel = new QLabel(tr("None"), card);
|
|
noneLabel->setAlignment(Qt::AlignCenter);
|
|
cardLayout->addWidget(noneLabel);
|
|
}
|
|
else
|
|
{
|
|
for (const std::string& recipeId : option.newlyUnlockedRecipeIds)
|
|
{
|
|
const RecipeDef* def = recipes.findRecipeDef(recipeId);
|
|
if (def == nullptr) { continue; }
|
|
|
|
// The recipe drawn as it is everywhere else, led by the building
|
|
// that runs it and its name, so the line says everything there is
|
|
// to say about it and carries no tooltip (REQ-DEF-SCHEMATIC-DROP).
|
|
RecipeLineRow::Spec spec;
|
|
spec.building = def->building;
|
|
spec.name = QString::fromStdString(toDisplayName(def->id));
|
|
spec.inputs = toAmounts(def->inputs);
|
|
spec.outputGroups = RecipeLineRow::toOutputGroups(*def);
|
|
spec.durationSeconds = def->durationSeconds;
|
|
|
|
RecipeLineRow* line =
|
|
new RecipeLineRow(itemIcons, buildingIcons, card);
|
|
line->setCardChrome(true);
|
|
cardLayout->addWidget(line);
|
|
line->setLine(spec);
|
|
}
|
|
}
|
|
}
|
|
|
|
QPushButton* selectButton = new QPushButton(tr("Select"), card);
|
|
cardLayout->addWidget(selectButton);
|
|
|
|
const int index = i;
|
|
connect(selectButton, &QPushButton::clicked, this, [this, index]()
|
|
{
|
|
onOptionClicked(index);
|
|
});
|
|
|
|
optionsLayout->addWidget(card);
|
|
}
|
|
}
|
|
|
|
int SchematicChoiceDialog::getChosenIndex() const
|
|
{
|
|
return m_chosenIndex;
|
|
}
|
|
|
|
void SchematicChoiceDialog::onOptionClicked(int index)
|
|
{
|
|
m_chosenIndex = index;
|
|
accept();
|
|
}
|