Radial Slot Bindings

CrimsonActionBar owns the binding store, the provider contract and the generic ability and object providers; CrimsonUI's player-editable wheel has its own binding model. CrimsonCore contributes what has to know about several plugins at once: the ability/object adapters and the inventory resolver, UCrimsonCoreRadialBindingProvider_SlotBinding (action-bar providers on a wheel), the equipment provider, and the save wiring (UCrimsonCoreSlotBindingComponent, UCrimsonCoreRadialBindingComponent).

Read the contract first
CrimsonUI -> Concept: Slot Bindings explains what a binding is and what a provider does. CrimsonUI -> How-To: Build a Player-Editable Wheel is the recipe. This page only covers what CrimsonCore adds on top.

The providers

ProviderBinding payloadIdentity, and why that one
UCrimsonSlotBindingProvider_Ability (CrimsonActionBar)FCrimsonSlotBindingData_Ability { Identity }A Crimson ability's identity is its gameplay-event trigger tag (via ICrimsonSlotBindingPresentable) - the same key UCrimsonCoreRadialAction_GameplayAbility settled on. Asset tags matched several abilities at once; a class reference breaks the moment the ability is granted by a different set.
UCrimsonSlotBindingProvider_Object (CrimsonActionBar)FCrimsonSlotBindingData_Object { Identity }For an item, the item definition class path, which is exactly what FCrimsonInventorySaveEntry already persists. Not a stack: bind a potion, drink the last one, buy more an hour later, and the slot still works.
UCrimsonCoreSlotBindingProvider_Equipment (CrimsonCore)FCrimsonBinding_Equipment { SlotTag, AssignedSlotIndex }The identity triple FCrimsonEquipmentSaveEntry persists. Not the instance - those are re-created on load and on replication. Not the definition either: the player is binding 'my off-hand', so swapping the axe for a sword should follow the slot.

The equipment provider (and, by default, the Object provider) dispatches through a gameplay event rather than acting directly - a configurable tag carrying the instance as OptionalObject. The ability provider routes press/release edges through the ASC (ICrimsonAbilityInputRouter), exactly like a bound key. Drinking a potion, wielding a weapon and quick-firing are different games' answers to the same gesture, so the provider raises the intent and an ability decides what it means.

Shared with the authored action
Dispatch, cooldown reading and ability introspection for the authored radial action live in UserInterface/Bindings/CrimsonCoreAbilityTriggerUtils.h. (UCrimsonCoreRadialAction_GameplayAbility calls it; the bindable ability provider now lives in CrimsonActionBar and uses the same trigger-tag identity, so the two cannot drift apart.

Setting one up

On your CrimsonRadialLayoutDefinition, add a Providers entry and pick Gameplay Abilities / Objects (CrimsonActionBar) or Equipment Slots (CrimsonCore). Set Provider Id to a tag unique to that provider.

For items and equipment, also set the Use Event Tag / Select Event Tag that your ability listens for. Leaving it unset makes the wheel purely informational, which is a legitimate read-only setup.

Verify
Enter edit mode and open the picker on an empty slot. Every consumable the player is carrying is listed once, with its icon and a live count - once per definition, not once per stack.
Reference implementation
Open the shipped DA_RadialLayout_Test (/CrimsonCore/UserInterface/Radial/) - two pages of eight slots, an ability provider and the CrimsonUI tag provider authored inline, a locked slot pre-bound to an ability, an ability-filtered slot, and one editable slot that starts pre-bound. W_CrimsonCoreRadialMenu_BindableTest in the same folder is the wheel pointed at it, and exercises every branch without any game code.

Where the display comes from

ProviderName and iconNote
AbilityUCrimsonAbilityDisplayData on the granted abilityAn ability with no display data is not offered. Authoring it is how an ability opts in to being bindable.
Inventory itemDisplayName plus the UCrimsonInventoryItemFragment_Visuals iconThe count badge sums every stack the player holds, not just one.
EquipmentThe source object, when that is an inventory itemEquipment definitions carry no display data of their own; the fallback is the slot tag's leaf name.

Reading from the ability's own display data is what makes renaming a spell or changing its icon update every wheel that offers it, with nothing re-typed anywhere.

Richer equipment display
If your project has a proper equipment display model, override ResolveBinding on the equipment provider and supply it there. The fallback exists so a slice is never nameless, not because it is the right answer for every game.

Availability

ProviderGrays out whenBinding kept?
AbilityOn cooldown, or no longer grantedYes
Inventory itemStack count reaches zeroYes
EquipmentThe bound slot is emptyYes

Ability cooldown is read off the ability system's own effect state, never by asking the ability. Spec.Ability is the class default object, and CanActivateAbility on a CDO walks into GetOwningActorFromActorInfo() and ensure-fails. The ability system already knows exactly what is active - the same answer, with no instance required.

An out-of-stock item and an unequipped slot are not stale bindings. The player will restock and re-equip, so they resolve normally and are grayed out instead. Only an ability that is no longer granted reports stale, and even then the layout's ECrimsonStaleBindingPolicy has the final say.


Persistence

UCrimsonCoreSlotBindingComponent is UCrimsonSlotBindingComponent plus ICrimsonSaveableSystem. Use it in place of the CrimsonUI component and bindings persist with no code from you.

cpp
// There is no fragment class and no gather code. The save manager always serializes a
// system's UPROPERTY(SaveGame) fields whether or not it returns a custom fragment, and the
// base component marks its layout container and presets SaveGame at every nesting level.
FString GetFragmentName_Implementation() const; // -> SaveFragmentName
ECrimsonSaveScope GetSaveScope_Implementation() const; // -> Player
FGuid GetProfileId_Implementation() const; // -> ResolveProfileIdForActor(owner)
DetailBehavior
Where to add itPlayerController, PlayerState or Pawn - whichever your project persists.
GetSaveScope / GetProfileIdResolves the controller from any of the three, so moving the component later does not orphan existing saves.
SaveFragmentNameEditable, so two independent stores (wheels and a hotbar, say) do not overwrite each other.
RegistrationServer only. A client registering would race its own replicated copy against the restore.
Verify
Bind a consumable, save, quit to the main menu, load. The binding is where you left it. Then Play As Client with two players and confirm the wheels are independent.

See also

  • CrimsonUI -> How-To: Build a Player-Editable Wheel
  • CrimsonUI -> Concept: Slot Bindings
  • Radial Menu - the authored CrimsonCore wheel and its hold-to-open ability