Files
dota_factory/src/lib/config/RecipesConfig.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

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