Skip to content

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.DontSave and is not an asset. Destroy it when you are done, as you would any ScriptableObject you created.
  • Values are stored as given, not normalised. Bounds are applied on read — RollsMin floors at zero and RollsMax never reports below RollsMin, 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) are internal. Add them from the component menu; do not reference the types.
  • The RNG providers (SeededRandomProvider, UnityRandomProvider) are internal. Use UseDeterministicRng / UseUnityRng, or supply your own IRandomProvider to LootRoller directly.
  • LootEntry's fields are public and mutable because the struct is serialized for the inspector. LootTable.Entries hands 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.