Skip to main content

Items and fragments

An item is not a class you subclass. It is a definition asset plus a list of fragments, and the fragments are the only thing that decides what it can do.

The two halves of an item

UEDItemDefinition is the immutable half — a data asset, loaded once and shared by every instance made from it. It never holds runtime state.

FEDItemEntry is the mutable half — a plain struct living in a container. It holds a definition id, a handle, a stack count, per-stack state, dynamic tags and a slot address. No UObject is allocated for it.

DA_Sword  (UEDItemDefinition)          ← one asset, shared
▲ referenced by id

FEDItemEntry { Handle 41, Count 1 } ← one struct per stack, in a container
FEDItemEntry { Handle 42, Count 1 } ← a second sword: same asset, different handle

This is why an inventory of a thousand arrows costs a struct and not a thousand objects.

When an item needs to be an object

Some items genuinely need per-instance behaviour: a weapon with a tick, a container item with its own logic. Set Instance Class on the definition and entries made from it are promoted to a UEDItemInstance of that class, replicated as a subobject. Everything else stays a struct.

Promotion is opt-in and per definition. Nothing else in the framework changes; a promoted entry is still an entry.

Identity

An item is identified by its handle (FEDItemHandle) — an opaque 64-bit value allocated by the server. Not by index, not by name, not by position.

That matters because everything else moves. An item transferred between containers keeps its handle, so a UI holding one, a quest watching one, or an ability granted by one all survive the move.

Never persist an index

GetEntryAtIndex exists for iteration. The index of an entry changes whenever the array behind it changes, and on a client the order is not even guaranteed to match the server's. Store the handle.

Fragments

A fragment is a plain USTRUCT deriving from FEDItemFragment, stored in the definition's Fragments array as an FInstancedStruct. Each answers one orthogonal question.

if (const FEDStackFragment* Stack = Definition->FindFragment<FEDStackFragment>())
{
const int32 Max = Stack->GetMaxStackSize();
}
Get Fragment  (Definition, wildcard out)   ← Blueprint; pick the struct type on the output pin
Has Fragment Of Type (Definition, Struct)

An item with no Stack fragment does not stack. An item with no Size fragment is one cell. Absence is the default, so a rock is one fragment and a sword is eight without either paying for the other.

The rule that makes them work

Fragments have no virtual functions. A vtable inside an FInstancedStruct corrupts serialization, so this is enforced by convention and by review, not by the compiler. Behaviour lives in the systems that read fragments — layouts, rules, transaction executors, observer components.

If you find yourself wanting a virtual on a fragment, what you want is a registry: see Extending.

What ships

The full table is in the fragment reference. The ones you will use first:

FragmentAnswers
DisplayName, description, icon, category label
StackDoes it stack, and how high
SizeHow many cells it covers, and in what shape
RarityTier tag and accent colour
Container PlacementWhere it prefers to go
EquipmentWhich slot it can be worn in
EquipmentWhich slot it is worn in, and how it looks and behaves per state
Nested ContainerThe container it brings with it
Weight, Value, Usable, Durability, Grants (GAS), Prerequisites, Stat Rolls, World Modelone each

Per-stack state

Two swords from one asset can differ. Two mechanisms, and they are not interchangeable:

FEDItemStateContainer — tag-keyed numbers on the entry. Durability, ammo, charges. Replicated with the entry.

DynamicTags — tags describing this stack: stolen, soulbound, quest-reserved. Also replicated.

Both gate stacking: two stacks with different state or different dynamic tags never merge. That is deliberate. Merging a stolen stack into a clean one would launder the mark, and merging a half-worn sword into a fresh one would invent durability.

Rolled stats

FEDRollFragment gives an item stats decided when it is created rather than when it is authored — a sword rolling 12–18 damage. The roll happens once, on the server, and is stored in the entry's state, so it replicates and saves like anything else.

Prerequisites

FEDPrerequisiteFragment holds conditions an item checks before a container will accept it: another item required or forbidden, a GAS attribute above a threshold. They are evaluated at the same single acceptance gate as container rules, so a dry run and the real thing always agree, and the failure comes back as player-readable text rather than a boolean.

Where next