Trading and loot
Two modules that answer "where do items come from and go to" — a shop and a drop table.
Vendors
UEDVendorComponent on an actor makes it a shop.
| Property | Meaning |
|---|---|
| Offers | What it sells: a definition, a price, an optional stock count. |
| Currency | The item definition used as money. |
| Buy / Sell Multipliers | The spread between what it charges and what it pays. |
Currency is an item, not a number on the player. Coins live in a container like anything else, which means they weigh something, can be stolen, and can be dropped.
Buying and selling
Vendor->RequestBuy(BuyerContainer, OfferIndex, Count);
Vendor->RequestSell(SellerContainer, Handle, Count); // -1 = the whole stack
Both are safe from a client and both are atomic: coins leave and the item arrives in one transaction, or neither happens. A purchase that cannot fit the item does not take the money.
Prices
const int32 Price = Vendor->GetBuyPrice(OfferIndex, BuyerActor);
const int32 Paid = Vendor->GetSellPrice(Definition, SellerActor);
Both take the actor, and both run through AdjustBuyPrice / AdjustSellPrice — BlueprintNativeEvent
hooks. Override them in a Blueprint subclass for faction discounts, reputation, haggling, or a sale on
Tuesdays.
An item's base worth comes from its Value fragment. An item without one cannot be sold.
A client computes a price to show in the UI; the server recomputes it when the transaction runs. That is deliberate — the displayed number is a prediction, and the authoritative one is the only one that moves coins.
Loot tables
UEDLootTableAsset is a weighted list with a budget.
| Field | Meaning |
|---|---|
| Entries | Definition, weight, count range, and a budget cost. |
| Rolls | How many times to draw. |
| Guaranteed | Entries that always drop. |
const TArray<FEDLootRollResult> Roll = UEDLootStatics::RollLootTable(this, Table, Budget);
const int32 Granted = UEDLootStatics::GiveLoot(Container, Roll);
Budget caps the summed cost of what drops: entries that would overrun the remaining budget are skipped. That is how one table serves a starter chest and a boss chest — same weights, different budget.
Reproducible rolls
Rolls come from the world subsystem's seeded stream, not from global FMath random:
UEDInventorySubsystem::Get(World)->SetRollSeed(12345);
Set the seed and a chest produces the same contents every time. That is what makes loot testable, and
it is the reason RollLootTableWithStream exists as the deterministic core — pass your own stream for
tests or a custom pipeline.
If you roll part of a chest's contents with the seeded stream and part with global random, SetRollSeed
reproduces only half of it — and the half that varies looks like a bug in the table. Everything that
rolls loot should go through the subsystem's stream.
Giving loot
GiveLoot performs stack-aware adds and returns how many entries were granted. It takes a
container, so you decide where loot lands: a chest's own container, a player's inventory, a corpse.
Anything that does not fit is simply not granted — the return value is how you know.
Where next
- Items in the world — chests and pickups
- Placement and routing — where granted loot ends up