Skip to main content

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.

PropertyMeaning
OffersWhat it sells: a definition, a price, an optional stock count.
CurrencyThe item definition used as money.
Buy / Sell MultipliersThe 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 / AdjustSellPriceBlueprintNativeEvent 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.

The client's price is advisory

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.

FieldMeaning
EntriesDefinition, weight, count range, and a budget cost.
RollsHow many times to draw.
GuaranteedEntries 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.

Both halves must use the same stream

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