Skip to content

Items (magical objects)

The chain

oCDtRewardDefinition data class
| referenced-by
v
oCDtRewardEntitySelectorToSpawnEntityCpntSettings per-drop selector
| owns
v
oCCustomFlagList filter tags
|
v
oCDtEntityCpntMagicalObject runtime component (pickup entity)
|
v
oCDtEntityCpntMagicalObjectsDropSettings drop-table config (0xa50 bytes)

The “settings” type is the authored container referencing each magical object’s prefab; the runtime component is the instance the engine spawns for one pickup.

Class registry

The schema addresses are schema-declarer callbacks, not deserialization factories: each calls MetaClass::SetDisplayName(name), writes flags into the declarer state, then registers the field groups. Ctor/dtor/size live in a separate registrar that walks oCMetaClass_FindByKey then oCMetaClass_Alloc.

ClassUIDSizeRegistrarSchema cbctordtor
oCDtRewardDefinition0x176f164e0x298FUN_140237f40FUN_14031a040FUN_1401e3ca0FUN_1401e3d50
oCDtEntityCpntMagicalObjectsDropSettings0x168afca60xa500x140277790FUN_1402d2290FUN_1402d2310FUN_1402d0cc0
oCDtEntityCpntMagicalObject (runtime)0x2b0— (component, not registry-keyed)FUN_1401e0e10FUN_1401e10f0
oCDtRewardEntitySelectorToSpawnEntityCpntSettingsFUN_1401e3e90
oCDtEntityCpntMagicalObjectSettings (authoring)0xc00FUN_1402ce2b0

oCDtRewardDefinition (size 0x298)

OffsetFieldNotes
+0x00vftable
+0x08..+0x20oISerializable slotszeroed by ctor
+0x30 low byteoIResource flags& 0xe0 at ctor — high 3 bits preserved by caller
+0x34refcountset to 1
+0x3cresource state0
+0x40oCResourcePath::namethe logical id lives here once loaded
+0x48oCResourcePath::hashu32
+0x50 / +0x58 / +0x5cresource-list prev / next / count
+0x60..+0x260oCDtDefinition bodysub-records
+0x270oCDtDefinition ptrreleased in the dtor — likely display name / description
+0x284oCDtDefinition flags0x0101
+0x288entries ptrqword array, freed slot-by-slot in the dtor
+0x290 / +0x294entries length / capacity

The named slot at +0x40/+0x48 is what oIResourceManager::FindOrLoad keys on.

For the fully typed rewarddef grammar (the section directory, per-class version gating, oCItem rows) see Rewards.

oCDtEntityCpntMagicalObject (size ≥ 0x2b0)

OffsetField
+0x00vftable
+0x18EntityCpntValueSignal<bool> — “is_active”
+0x38EntityCpntValueSignal<bool> — “ever_spawned”
+0x58..+0x68small int / flag block (low nibble of +0x64 masked)
+0x90..+0xf8three closure slots (signal binder header, ctx + del)
+0x1f8EntityCpntValueSignal<int>rarity (provisional)
+0x218EntityCpntValueSignal<int>count (provisional)
+0x238EntityCpntValueSignal<int>level (provisional)
+0x290oCCustomFlagList::vftable — tag list
+0x298..+0x2a8flag-list body
+0x2ac0xffffffff sentinel

The rarity / count / level labels come from ordering alone — no schema string in the binary proves the mapping. Treat them as provisional until a save-game diff confirms which signal carries which int.

oCDtEntityCpntMagicalObjectsDropSettings (size 0xa50)

High level only — the body is a 0x80-byte stride array built by _eh_vector_constructor_iterator_.

OffsetField
+0x00vftable
+0x1f8array of 12 × 0x80-byte entries (drop-table rows)
+0x3f8, +0x478, …further rows, pattern repeating
+0x9d0oCEntityCpntPicker 1
+0xa10oCEntityCpntPicker 2
+0xa48tail

The SDK does not synthesize new drop-table rows — it threads a new reward through an existing one.

oCDtRewardEntitySelectorToSpawnEntityCpntSettings

OffsetField
+0x00 / +0x08vftable (oIEntitySelectorToSpawnEntityCpntSettings first, then specialized) / secondary vftable
+0xf8 / +0x100 / +0x104target name ptr (empty sentinel) / hash 0x80000000 / resolved flag
+0x108 / +0x110 / +0x114parent path ptr / hash / resolved flag
+0x118resolved oCMetaClass* = DAT_141447bd0 (oCDtRewardDefinition meta)
+0x120 low byteenable flag (1)
+0x130oCCustomFlagList::vftable
+0x138..+0x148flag-list body

So a selector pins to one named reward definition, its parent resource group, and a tag filter.

Engine-side insertion

Following the oIResourceManager slot-3 by-name lookup ABI:

  1. Register UID — the oCMetaClass for oCDtRewardDefinition is DAT_141447bd0; its registrar static-inits before main(), so nothing is needed for the existing class. A new UID would require running the same oCMetaClass_FindByKey → oCMetaClass_Alloc → set sizeof+align → SetDisplayName → register factory sequence at boot.
  2. Construct an oCDtRewardDefinition (or clone a vanilla one) and fill +0x40 = "rwd_<mod>_<id>", +0x48 = name hash, the oCDtDefinition fields.
  3. Insert into oCTLibrary<oCDtRewardDefinition> (singleton 0x141412e00) via vftable slot 3 (+0x18, FindOrLoad) with an oCResourcePath. The library allocates an entry, links it into the +0x150 head / +0x148 tail list under the +0x118 critical section, and returns a refcounted pointer.
  4. Selector — for every drop site, the selector’s target name must match the new reward’s name, and its tag list must contain at least one tag matching the dropper’s oCCustomFlagFilter.
  5. Magical object — the pickup entity’s own flag list at +0x290 must overlap the drop-settings row’s filter.