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
117 lines
3.6 KiB
C++
117 lines
3.6 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "BuildingType.h"
|
|
|
|
// One entry in [[recipe]].inputs — amount units of a named item consumed per
|
|
// production cycle (REQ-MAT-CYCLE).
|
|
struct RecipeIngredient
|
|
{
|
|
std::string item;
|
|
int amount;
|
|
};
|
|
|
|
// One item produced by an output group -- amount units of a named item
|
|
// (REQ-MAT-OUTPUT-GROUP).
|
|
struct RecipeOutput
|
|
{
|
|
std::string item;
|
|
int amount;
|
|
};
|
|
|
|
// One possible result of a production cycle: the items it yields, produced together, and
|
|
// the weight this group is picked with among the recipe's groups (REQ-MAT-OUTPUT-GROUP).
|
|
// A recipe with a single group always produces it, so the weight is meaningful only where
|
|
// there are several -- which is the only difference between what used to be called a
|
|
// deterministic and a probabilistic recipe.
|
|
struct RecipeOutputGroup
|
|
{
|
|
std::vector<RecipeOutput> items;
|
|
std::optional<double> probability;
|
|
};
|
|
|
|
struct RecipeDef
|
|
{
|
|
std::string id; // Unique recipe id; used by UI for selection.
|
|
BuildingType building; // Which BuildingType can run this recipe.
|
|
std::vector<RecipeIngredient> inputs;
|
|
// Never empty: one group is the ordinary recipe (REQ-MAT-OUTPUT-GROUP).
|
|
std::vector<RecipeOutputGroup> outputGroups;
|
|
double durationSeconds;
|
|
// Assembler only. When true, this recipe is available from game start
|
|
// regardless of the implicit item graph — used for base recipes that no
|
|
// schematic's materials reach (e.g. building blocks). See REQ-LOCK-IMPLICIT.
|
|
// Otherwise an assembler recipe is either explicitly gated (granted by an
|
|
// unlock group, REQ-LOCK-EXPLICIT) or implicitly gated via the item graph.
|
|
bool unlockedAtStart = false;
|
|
};
|
|
|
|
// Every distinct item any group of this recipe can produce, in config order. Most callers
|
|
// only want to know what a recipe can make at all -- which items it has buffers for, which
|
|
// recipes produce an item -- and not which group yields what.
|
|
inline std::vector<std::string> getProducibleItems(const RecipeDef& recipe)
|
|
{
|
|
std::vector<std::string> items;
|
|
for (const RecipeOutputGroup& group : recipe.outputGroups)
|
|
{
|
|
for (const RecipeOutput& out : group.items)
|
|
{
|
|
if (std::find(items.begin(), items.end(), out.item) == items.end())
|
|
{
|
|
items.push_back(out.item);
|
|
}
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
// True when some group of this recipe yields the given item.
|
|
inline bool producesItem(const RecipeDef& recipe, const std::string& itemId)
|
|
{
|
|
for (const RecipeOutputGroup& group : recipe.outputGroups)
|
|
{
|
|
for (const RecipeOutput& out : group.items)
|
|
{
|
|
if (out.item == itemId) { return true; }
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct RecipesConfig
|
|
{
|
|
std::vector<RecipeDef> recipes;
|
|
|
|
// Returns the definition for the given recipe id, or nullptr if the id has
|
|
// no entry in recipes.toml.
|
|
const RecipeDef* findRecipeDef(const std::string& id) const
|
|
{
|
|
for (const RecipeDef& recipe : recipes)
|
|
{
|
|
if (recipe.id == id)
|
|
{
|
|
return &recipe;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Same, but additionally requires the recipe to belong to the given building
|
|
// type — recipe ids are only unique per building type.
|
|
const RecipeDef* findRecipeDef(const std::string& id, BuildingType building) const
|
|
{
|
|
for (const RecipeDef& recipe : recipes)
|
|
{
|
|
if (recipe.id == id && recipe.building == building)
|
|
{
|
|
return &recipe;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
};
|