Loot System
Summary: Data-driven loot generation using weighted random selection from ULootTableDataAsset. Items are filtered by context (level, tags), generated with appropriate properties, and spawned as interactable AItemActor pickups. All generation is server-authoritative.
Table of Contents
Architecture Overview
ULootTableDataAsset (Design-Time)
│
├── FLootTableEntry[] (Item Drops)
│ ├── UItemManifestDataAsset
│ ├── DropWeight (probability distribution)
│ ├── DropChance (0.0 - 1.0)
│ ├── LevelOffsetRange
│ └── Context Filters (tags, level range)
│
├── FCurrencyDropEntry[] (Currency)
│ ├── StackRange
│ └── DropWeight, DropChance
│
└── ParentTables[] (Inheritance)
ULootComponent (Runtime - on enemies/containers)
│
├── LootTable reference
├── SourceContextTags
└── QuantityMultiplier
│
▼
ULootGenerator::GenerateLoot(LootTable, Context)
│
├── Filter valid entries (level, tags)
├── Weighted random selection
├── Item level calculation
└── FLootGenerationResult
│
▼
UItemSpawner::SpawnNewItemAtLocation()
│
└── AItemActor (World Pickup)
├── UItemComponent
└── UItemDropWidget
Key Design Principles
| Principle |
Implementation |
| Data-Driven |
Loot tables as DataAssets, no hardcoded drops |
| Weighted Random |
Drop weights create probability distribution |
| Context Filtering |
Tags and level ranges filter valid entries |
| Server-Authoritative |
All generation on server only |
| World Pickups |
Items spawn as interactable actors |
Core Concepts
Why Data-Driven?
Hardcoded loot:
- Requires recompilation for balance changes
- No designer autonomy
- Difficult A/B testing
Data-driven loot:
- Designers edit DataAssets directly
- Hot-reloadable in editor
- Easy to create themed loot tables (boss, treasure, trash)
Two-Phase Drop Chance
Each item has two chances to fail:
Roll 1: Base Drop Chance (LootTable.BaseDropChance)
│
├─ [Fail] → No item this roll
│
└─ [Pass] → Select weighted entry
│
▼
Roll 2: Entry Drop Chance (Entry.DropChance)
│
├─ [Fail] → No item this roll
│
└─ [Pass] → Generate item
This allows:
- Base chance: "Does anything drop?"
- Entry chance: "If something drops, does THIS specific item drop?"
Guaranteed vs Random Items
| Type |
Use Case |
LootTable drops |
Random selection from pool |
GuaranteedItemManifest |
Always drops (quest items, boss loot) |
Which sources fire is gated per component by ELootDropMode (LootTableOnly,
GuaranteedOnly, Both) — a container can be a pure treasure chest, a pure
quest-item holder, or both at once.
Naming caveat: the Currency* names (FCurrencyDropEntry, CurrencyEntries,
GeneratedCurrency) are legacy struct naming. The currency concept was removed from
the item taxonomy — crafting materials are Cores and Fragments
(GameItems.Types.Core / GameItems.Types.Fragment); these entries drop stackable
material items, not money.
Loot Tables
ULootTableDataAsset Structure
LootTable: DA_Enemy_Skeleton
├── TableID: "skeleton_common"
├── BaseDropCountRange: (1, 3)
├── BaseDropChance: 0.8
│
├── ItemEntries:
│ ├── [0] Bone Shard (Weight: 100, Chance: 1.0)
│ ├── [1] Rusty Sword (Weight: 20, Chance: 0.8)
│ └── [2] Gold Ring (Weight: 5, Chance: 0.5)
│
├── CurrencyEntries:
│ └── [0] Gold (Weight: 100, StackRange: 5-25)
│
└── ParentTables:
└── DA_Common_Drops
FLootTableEntry Properties
| Property |
Purpose |
ItemManifest |
The item to drop |
DropWeight |
Relative probability (higher = more common) |
DropChance |
Additional roll after selection |
ItemLevelOffsetRange |
Level variance from source |
RequiredContextTags |
Must have these tags to appear |
ForbiddenContextTags |
Cannot appear with these tags |
MinSourceLevel |
Minimum source level |
MaxSourceLevel |
Maximum source level (0 = no limit) |
Table Inheritance
Parent tables allow shared drop pools:
DA_Common_Drops (Parent)
├── Health Potion
└── Mana Potion
DA_Enemy_Skeleton
├── ParentTables: [DA_Common_Drops]
├── Bone Shard
└── Rusty Sword
Result: Skeleton can drop Health/Mana Potions + Bone/Sword
FLootSourceContext
Context passed to generation:
| Property |
Purpose |
SourceLevel |
Level of drop source (enemy/chest) |
QuantityModifier |
Multiplier for drop count |
ContextTags |
Tags for filtering (e.g., "boss", "elite") |
bGuaranteedDrop |
Skip base drop chance |
InstigatingPlayer |
Player who triggered drop |
Generation Algorithm
Selection Process
GenerateLoot(LootTable, Context)
│
├─ 1. Collect all valid entries
│ └─ Include parent table entries
│
├─ 2. Filter by context
│ ├─ Level within MinSourceLevel..MaxSourceLevel
│ ├─ RequiredContextTags present
│ └─ ForbiddenContextTags absent
│
├─ 3. Calculate drop count
│ ├─ BaseCount = RNG(BaseDropCountRange.X, BaseDropCountRange.Y)
│ └─ FinalCount = BaseCount * Context.QuantityModifier
│
├─ 4. For each drop roll:
│ ├─ Check base drop chance (skip if guaranteed)
│ ├─ Select weighted random entry
│ ├─ Check entry drop chance
│ ├─ Calculate item level
│ └─ Add to result
│
└─ 5. Generate currency drops separately
Weighted Selection
Entries with weights: [Bone: 100, Sword: 20, Ring: 5]
Total weight: 125
Roll random 0-124:
├── 0-99 → Bone (80% chance)
├── 100-119 → Sword (16% chance)
└── 120-124 → Ring (4% chance)
Item Level Calculation
SourceLevel: 10
Entry.ItemLevelOffsetRange: (-2, +2)
ItemLevel = SourceLevel + RNG(-2, +2)
ItemLevel = Max(1, ItemLevel) ← Clamp to minimum 1
Possible levels: 8, 9, 10, 11, 12
World Pickups
AItemActor Purpose
- Represents item in world
- Displays item name widget
- Handles player interaction
- Replicates to all clients
Spawn Flow
FItemManifest::SpawnPickupActor(World, Location, Rotation)
│
├─ Spawn AItemActor from PickupActorClass
│
├─ Get UItemComponent from actor
│
├─ InitItemManifest(Manifest) on component
│
└─ Multicast_SetInteractText(ItemName)
UItemSpawner Batching
Multiple items spawn with spacing to prevent overlap:
SpawnLootInWorld(LootResult)
│
├─ Create UItemSpawner
│
├─ For each item:
│ ├─ FindValidSpawnLocation(BaseLocation, Radius, ExistingLocations, Spacing)
│ ├─ Add to ExistingLocations
│ └─ SpawnPickupActor at valid location
│
└─ Destroy spawner
Pickup Interaction
Player approaches AItemActor
│
├─ Interaction prompt shown
│
▼
Player interacts
│
├─ Server_TryAddItem(ItemComponent)
│
├─ [Server] Manifest item → add to inventory
│
├─ [Success?] ItemComponent->PickedUp() → destroy actor
│
└─ [Fail?] Item remains in world
LootComponent Integration
Purpose
ULootComponent attaches to actors that drop loot:
- Enemies (drop on death)
- Chests (drop on open)
- Destructibles (drop on break)
Configuration
| Property |
Purpose |
DropMode |
ELootDropMode: which sources fire (table / guaranteed / both) |
LootTable |
Primary drop table |
LevelOverride |
Override source level (0 = use actor level) |
SourceContextTags |
Additional context tags |
QuantityMultiplier |
Scale drop count |
bGuaranteedDrop |
Always drop something |
GuaranteedItemManifest |
Always drops this specific item |
Enemy Death Integration
AEternalEnemy::Multicast_HandleDeath()
│
├─ [HasAuthority()?]
│ └─ LootComponent->DropLoot(InstigatingController)
│
└─ SetLifeSpan(5.f) ← Corpse cleanup
DropLoot Flow
DropLoot(InstigatingController)
│
├─ Build FLootSourceContext
│ ├─ SourceLevel = GetSourceLevel()
│ ├─ ContextTags = SourceContextTags
│ └─ InstigatingPlayer = InstigatingController
│
├─ [LootTable?]
│ ├─ GenerateLoot(LootTable, Context)
│ └─ SpawnLootInWorld(Result)
│
└─ [GuaranteedItemManifest?]
└─ SpawnGuaranteedItem()
Public Contracts
ULootTableDataAsset
| Property |
Purpose |
TableID |
Unique identifier |
TableName |
Display name |
BaseDropCountRange |
Min/max drops per roll |
BaseDropChance |
Probability of any drop |
ItemEntries |
Item drop definitions |
CurrencyEntries |
Currency drop definitions |
ParentTables |
Inherited loot tables |
| Method |
Purpose |
GetAllEntries() |
All entries including parents |
GetValidEntries(Context) |
Filtered by context |
GetTotalWeight(Entries) |
Sum of weights |
ULootGenerator
| Method |
Parameters |
Returns |
GenerateLoot |
(LootTable, Context, Seed) |
FLootGenerationResult |
FLootGenerationResult
| Property |
Purpose |
GeneratedItems |
Array of generated FItemManifest |
GeneratedCurrency |
Array of currency FItemManifest |
TotalRolls |
Number of drop attempts |
bSuccess |
Generation completed |
ULootComponent
| Property |
Purpose |
LootTable |
Drop table reference |
LevelOverride |
Override source level |
SourceContextTags |
Context tag container |
QuantityMultiplier |
Drop count multiplier |
bGuaranteedDrop |
Skip base chance |
GuaranteedItemManifest |
Always-drop item |
| Method |
Purpose |
DropLoot(Controller) |
Trigger loot generation and spawn |
UItemSpawner
| Method |
Purpose |
SpawnNewItem(Manifest, Count, Controller) |
Spawn near player |
SpawnNewItemAtLocation(Manifest, Count, Location) |
Spawn at position |
SpawnExistingItem(Manifest, Count, Controller) |
Spawn dropped item |
AItemActor
| Property |
Purpose |
ItemComponent |
Holds item manifest |
ItemWidgetComponent |
Displays item name |
InteractText |
Display name (replicated) |
| Method |
Purpose |
GetItemComponent() |
Access item data |
Multicast_SetInteractText(Text) |
Sync name to clients |
Source Reference
Core Classes
| File |
Location |
Purpose |
LootTableDataAsset.h |
Loot/LootTableDataAsset.h |
Drop table definition |
LootGenerator.h |
Loot/LootGenerator.h |
Generation algorithm |
LootComponent.h |
Loot/Components/LootComponent.h |
Actor attachment |
ItemSpawner.h |
Inventory/Items/ItemSpawner.h |
World spawning |
ItemActor.h |
Inventory/Items/ItemActor.h |
World pickup actor |
ItemComponent.h |
Inventory/Items/ItemComponent.h |
Item data on actor |
Key Functions
| Function |
File |
Purpose |
GenerateLoot() |
LootGenerator.cpp |
Main generation |
SelectWeightedEntry() |
LootGenerator.cpp |
Weighted selection |
GetValidEntries() |
LootTableDataAsset.cpp |
Context filtering |
DropLoot() |
LootComponent.cpp |
Trigger drops |
SpawnLootInWorld() |
LootComponent.cpp |
Batch spawning |
SpawnPickupActor() |
ItemManifest.cpp |
Single item spawn |
Recent Changes
| Date |
Change |
Impact |
| 2026-08 |
FItemManifest.bNotifyOnPickup |
Notable items drive a persistent unseen-item badge on the inventory button |
| 2026-07 |
Era registry reward pools |
UEraRegistryDataAsset maps Era tag → Remnant reward pool; HoldGround events resolve drops through it |
| 2026-07 |
Currency taxonomy removed |
Material drops are Cores/Fragments; Currency* struct names are legacy |
| 2026-03 |
ELootDropMode |
Per-container gating of table vs guaranteed drops |
| - |
Initial weighted loot system |
Data-driven drop tables |
| - |
Context filtering |
Level and tag-based entry filtering |
| - |
Currency drops |
Separate currency generation |
| - |
Table inheritance |
Shared drop pools via ParentTables |
| - |
Guaranteed items |
Always-drop items alongside random |