Pickable talents
What a “pickable talent” is
A custom-code talent whose effect arms only when the player chooses its card
in the level-up book — versus R.talent.define without pickable, which is an
always-on passive. Choosing a card fires the gameplay-bus event
POWER_UP_COLLECT_REQUEST (oCDtNamedEventPowerUpCollectRequest), proven live
on the oCGameNamedEvent bus.
Pipeline
- Player picks a card → game dispatches
POWER_UP_COLLECT_REQUESTat the hero dispatcher. gameplay_dispatch_detourrepublishes it to Lua asgameplay:POWER_UP_COLLECT_REQUEST, attaching the tentative decoded identityev.card(+0x50/+0x58GUID,"<lo>:<hi>") plus a confirm window (ev.p38..ev.p58).R.talent.on_pick(card, cb)filters bycardand runscb(ev)on the main thread;R.talent.define{pickable=true, card=...}flips a per-talent armed flag so itseffectruns only after the matching pick. Arming resets atGAME_START.
Event identity offset
The shared oCGameNamedEvent GIVE sibling ctor NamedEvent_GiveMagicalObject_Ctor
(FUN_14030f430) puts its def GUID at +0x50/+0x58 in a 0x60 object. The
pick event’s reward-def GUID is the prime suspect at the same +0x50/+0x58,
so the loader publishes ev.card tentatively and string-id matching works today.
Not yet confirmed: the pick class registrar (RTTI oCDtNamedEventPowerUpCollectRequest
@ 0x140f1d940, ref’d from undefined code at 0x1402fdb6c) is unanalysed, and
the pick event may omit the oCGameNamedEventNetwork layer GIVE inserts (which
would shift the payload toward +0x38).
Open — confirm the identity offset
Run mods/TalentPickProbe (RSMM_ENABLE_GAMEPLAY_EVENTS=1), level up, pick
cards. In mods/_log.txt the pickprobe: lines show ev.card + the p38..p58
window. The identity is the field that changes with the chosen card and repeats
when the same card is picked again. If that field is p50/p58, ev.card
is already correct. The read is bounded to +0x58; a smaller no-Network event
reads a few bytes of adjacent mapped heap — benign, never an unmapped fault.
Second thing to confirm: POWER_UP_COLLECT_REQUEST fires for every power-up
collect (level-up cards and world orbs/globes), so per-card targeting needs the
GUID (ev.card); "*"/no-card arms on ANY power-up.
Additive pickable — the magical-object route
The cleanest additive (replaces-nothing) pickable that works with proven primitives: a NEW magical object. Unlike the hero skill list (fixed 28-row schema — a 29th bricks the hero), the magical-object / reward pool is variable-length and additive:
- Reward definitions are
oCTLibrary<oCDtRewardDefinition>, file-loaded from*.rewarddef.otand registered inUsedRscList.ot. Tables are variable-length (camp 9 entries, refugee 5) — count is data-driven, not a global constant. - The custom magical-object pipeline is proven additive end-to-end (a new item shows in the compendium and can be granted/picked).
Binding gap closed. A custom item’s GUID is cook-derived (not recomputable in
Lua), so the loader resolver resolve_item_guid(id) (hook_items.cpp) +
R.item.guid(id) / R.item.on_guid(id, cb) resolve it at runtime by scanning
the pool source list for the entry spawned from the item’s EntitySettingsResource*
(GUID at def+0x88/+0x90). Demo: mods/AdditivePickableTalent — kind="item"
emits the card, init.lua arms an ownership-gated effect once the GUID resolves.
See also
- Event systems — the gameplay bus this rides.
- Skills system — why the herodef route is walled.
- Custom skills guide — the SDK
skillkind.