Loot — Public API¶
The supported surface. Anything not listed here is internal and may change without a major version — including the shipped adapter components, which are wired in the inspector rather than constructed.
This page is kept against Tests/EditMode/ApiSnapshots/Lib/Goldens/RevFramework.Loot.PublicApi.txt. If the two disagree, the golden is right and this page is stale.
Supported surface at a glance¶
| Type | Kind | What it is |
|---|---|---|
LootTable | ScriptableObject | The asset: entries, mode, roll count |
LootEntry | struct | One row of a table |
LootEntryKind | enum | Item, Currency, Table, Nothing |
LootTableMode | enum | Weighted, IndependentChance |
LootRoller | static class | Pure rolling |
LootResult | class | What was won |
LootItemGrant / LootCurrencyGrant | readonly struct | One award |
LootService | MonoBehaviour | Rolling plus delivery |
ILootInventoryAdapter / ILootCurrencyAdapter / ILootPickupSpawner | interface | Delivery seams |
ILootModifier | interface | Adjust an award before delivery |
LootPickupPayload | MonoBehaviour | Carries a rolled award on a spawned pickup |
LootTable¶
IReadOnlyList<LootEntry> Entries { get; }
LootTableMode Mode { get; }
int RollsMin { get; }
int RollsMax { get; }
bool AllowDuplicates { get; }
float TotalWeight { get; }
static LootTable Create(LootTableMode mode, int rollsMin, int rollsMax,
bool allowDuplicates, params LootEntry[] entries);
Read-only once built. Nothing in the supported surface mutates an existing table.
Create — building a table in code¶
Authoring a .asset is the recommended route, and this does not compete with it — odds that live in code are odds a designer cannot open. It exists because the alternative denies the capability altogether: with no public construction route, a table cannot be built outside the framework at all, and "verify drop rates without a scene or Play Mode" would hold only for code with internals access.
Use it for deterministic tests of your own tables, editor tooling, and procedural generation:
var table = LootTable.Create(
LootTableMode.Weighted, rollsMin: 1, rollsMax: 1, allowDuplicates: true,
new LootEntry { kind = LootEntryKind.Item, itemGuid = "sword", weight = 1f },
new LootEntry { kind = LootEntryKind.Nothing, weight = 3f });
var result = LootRoller.Roll(table, new MySeededRng(12345));
Three things worth knowing:
- The instance is runtime-only. It is marked
HideFlags.DontSaveand is not an asset. Destroy it when you are done, as you would anyScriptableObjectyou created. - Values are stored as given, not normalised. Bounds are applied on read —
RollsMinfloors at zero andRollsMaxnever reports belowRollsMin, so inverted bounds roll the minimum. - The entry array is copied. Mutating your array afterwards does not change the table.
A cycle cannot be built this way: a LootEntry holds a table reference, so the table pointed at must exist first, and nothing public edits a built table. Cycles are reachable only through authored assets, which is what LootRoller's guard is for.
TotalWeight is the sum across entries and exists for tooling and display. It is not a probability denominator you can reason with across nesting — a nested table rolls its own weights independently.
LootRoller¶
static LootResult Roll(LootTable table, IRandomProvider rng);
static void Roll(LootTable table, IRandomProvider rng,
List<LootItemGrant> items, List<LootCurrencyGrant> currency);
const int MaxNestingDepth = 8;
Pure. Grants nothing, touches no scene. The list overload exists so a caller rolling in a loop — the debugger sampling thousands of times — can reuse buffers instead of allocating a LootResult per roll.
MaxNestingDepth caps recursion; a table nesting deeper warns and stops. Cycles are detected by the path walked, so the same table appearing in two different branches is legal while a table reaching itself is not.
LootResult¶
IReadOnlyList<LootItemGrant> Items { get; }
IReadOnlyList<LootCurrencyGrant> Currency { get; }
bool IsEmpty { get; }
static LootResult Empty { get; }
A value. Holding one after a roll is safe and is exactly how pickups defer delivery.
LootService¶
LootResult Roll(LootTable table, GameObject owner);
LootResult RollAndGrant(LootTable table, GameObject owner,
string container = null, Vector3? spawnAt = null);
bool Grant(LootResult result, GameObject owner, string container = null);
bool TryGetInventoryAdapter(out ILootInventoryAdapter adapter);
bool TryGetCurrencyAdapter(out ILootCurrencyAdapter adapter);
void UseDeterministicRng(int seed);
void UseUnityRng();
string DefaultContainer { get; }
bool SpawnPickups { get; }
event Action<GameObject, LootResult> Rolled;
event Action<GameObject, LootResult> Granted;
event Action<GameObject, LootResult> Spawned;
event Action<GameObject, LootResult> Undelivered;
RollAndGrant¶
Rolls, then delivers. Returns what the table produced, whether or not every award could be delivered — so the return value tells you a roll happened and nothing about where the awards went.
To know what landed, use the events. This is not a subtlety: it is the difference between a correct "you received" toast and one that lies.
Grant¶
Delivers a result rolled earlier. Returns true when at least one award actually reached the owner — not merely that there was something to deliver.
Never spawns pickups, whatever SpawnPickups is set to, because the path a collected pickup grants through must not produce another pickup.
The four events¶
| Event | Fires with | When |
|---|---|---|
Rolled | The full roll | After rolling, before anything is granted |
Granted | Only what the owner received | After delivery, when at least one award reached them |
Spawned | Only what went to the world | When an award was dropped instead of handed over |
Undelivered | Only what was lost | When an award reached neither the owner nor the world |
More than one can fire for a single call: delivery is per-award, so a roll can be partly received, partly dropped and partly lost.
Drive a "you received" feed from Granted alone. A dropped award is announced by Spawned and then by Granted when the player collects it — the collect path grants through the same service, so treating a drop as a receipt counts every spawned award twice.
ILootModifier¶
int Priority { get; }
LootItemGrant ModifyItem(GameObject owner, in LootItemGrant grant);
LootCurrencyGrant ModifyCurrency(GameObject owner, in LootCurrencyGrant grant);
Discovered on the owner's parent chain, applied lowest Priority first, run against the rolled result. See Integration Surfaces.
LootPickupPayload¶
Carries a rolled award on a spawned pickup and applies it on collect. Must be present on the prefab, not added after Instantiate — see Integration Surfaces for the Awake ordering reason.
Explicitly not supported¶
- The shipped adapters (
LootInventoryAdapter,LootCurrencyAdapter) areinternal. Add them from the component menu; do not reference the types. - The RNG providers (
SeededRandomProvider,UnityRandomProvider) are internal. UseUseDeterministicRng/UseUnityRng, or supply your ownIRandomProvidertoLootRollerdirectly. LootEntry's fields are public and mutable because the struct is serialized for the inspector.LootTable.Entrieshands out copies, so mutating one changes nothing — do not build on it.- Reproducibility across builds. A seed repeats within a build, not across versions of a table.