Concept: Providers and Identity
A slot stores two things: which provider understands it and an opaque payload that provider defined. Everything else - what it looks like, whether it is ready, what pressing it does - is rebuilt from those every time, by the provider. This is what lets one bar hold abilities, potions, mounts, emotes and macros without the bar knowing any of those words.
Providers array is the shared vocabulary for every bar in the layout, but each bar can narrow it: AllowedProviders (empty = all) lists the ProviderIds the bar takes, so one asset serves the whole HUD - the main bar takes abilities while the consumables strip takes only objects. Enforced by the write validator on the server and in the client pre-flight ("This bar does not take that kind of thing"), skipped by TryAutoPlace, warned about by IsDataValid when a default binding violates it, and queryable by pickers via UCrimsonActionBarComponent::IsProviderAllowed.The provider contract
| Verb | Called | Answers |
|---|---|---|
GatherBindableEntries | picker opens | What may the player choose right now? (FCrimsonBindableEntry: binding + display + filter tags) |
ResolveBinding | layout rebuild, stale re-resolve, every 2 s | Saved binding -> FCrimsonSlotBindingDisplay (+ the live object). false = stale. |
QueryBindingState | every poll (10 Hz) | FCrimsonSlotBindingLiveState: enabled, reason, cooldown, global cooldown, active, charges, count, cost, failure tags. |
HandleBindingInput | key / click edge | Press or release. Default: press -> ExecuteBinding. |
ExecuteBinding | wheel confirm | Do the thing (a full tap). |
GetBindingFilterTags | server validates a write | The tags a stored binding carries, for AcceptedEntries. |
BindingMatches | highlights, dedupe | Do two bindings mean the same thing? |
GetPayloadStruct | server validates a write | The one USTRUCT this provider accepts inside Data. |
Providers are Instanced on the layout asset and shared by every player using it, so they hold no per-player state. Everything per-player arrives in FCrimsonSlotBindingContext: the owning player, the layout's provider table (so a composite provider can reach its siblings), and an optional cached ResolvedObject the caller keeps per slot - a hint, never a promise.
Identity, not pointers
The shipped payloads carry an FCrimsonSlotBindingIdentity { TypeTag, Key } rather than an object reference, because the object may not exist when the save is read back, or may be replaced under the binding while playing:
| Thing | TypeTag | Key | Why that key |
|---|---|---|---|
Ability implementing ICrimsonSlotBindingPresentable | SlotBinding.Identity.Ability | whatever Get Slot Binding Identity returns - a gameplay-event trigger tag is the durable choice, because the provider sends that tag as an event when it fires the slot | Survives the ability class behind the trigger being swapped out; the same key CrimsonCore's authored radial action dispatches on. |
Stock UGameplayAbility | SlotBinding.Identity.Ability | class path (compared by name, never loaded) | No richer identity exists. |
| Inventory item | SlotBinding.Identity.Object | item definition class path | Bind Healing Potion, not this stack: drink the last one, buy more, the slot resolves to the new stack. |
The bridges
The generic providers include nothing from your ability, item or input systems. They talk to whatever is there through two interfaces and an adapter, all owned by this plugin, and fall back gracefully when nothing implements them:
| Interface | Implemented by | Gives | Without it |
|---|---|---|---|
ICrimsonSlotBindingPresentable | Your ability classes, your item instances - anything with a name and an icon | identity, display (name/icon/description), count, filter tags | class name, no icon, class-path identity |
ICrimsonSlotBindingObjectResolver | Your inventory / bag / stash component - any container of yours | identity -> live object + total count; what is bindable | the Objects provider resolves nothing |
UCrimsonSlotBindingAbilityAdapter (subclass) | A subclass you set on the Ability provider's Adapter, or as FCrimsonActionBarIntegrationHooks::DefaultAbilityAdapterClass | Tap / Hold / Toggle input semantics, charges, global cooldown, instance-safe cost check | gameplay event / TryActivateAbility; no charge badge; engine CheckCost |
_Tag, _Ability, _Object and _Sequence all live here in CrimsonActionBar; CrimsonCore (free to download, compiles only with all Crimson plugins installed) adds UCrimsonCoreSlotBindingProvider_Equipment. A picker of yours can list the same providers in its own Providers array.