Concept: Ability Contract

A contract is the ability's public API, declared as data. It lists which property tags the ability reads, so tooling can validate against it instead of trusting free text.

The problem it solves

Augment mutations target properties by tag. Without a declaration, nothing verifies the tag exists or that the ability reads it - the mutation compiles, ships, and does nothing. With a contract, the augment editor offers a dropdown of exactly the properties that ability honors.

The flow

text
UCrimsonAbilityContract declares Ability.Property.Damage (float)
-> ability reads it via GetConfigurableValue(Ability.Property.Damage, Default)
-> augment mutation targets it, chosen from a validated dropdown
-> at read time the base value is resolved, then mutations for THIS spec applied

The plugin ships Ability.Property.Cooldown and Ability.Property.PrimaryTargetCount; the rest are yours.

Reading the values back

An ability reads everything its contract publishes through the GetConfigurable* family. Each returns what the ability currently has, with equipped augments already accounted for - so ability code never asks whether an augment is equipped.

GetterReadsHow augments change it
GetConfigurableValue / Int / Bool / VectorThe property cacheProperty mutations, folded in when the cache is recalculated (grant, equip/unequip, replication receive). Numeric reads cross-convert: a Float property read as an integer is floored, an Integer property read as a float converts.
GetConfigurableClass<T>The contract's Class SlotsA Class mutation replaces the default outright.
GetConfigurableObject<T>The contract's Object SlotsAn Object mutation replaces the default outright.
GetConfigurableStruct<T>The contract's ConfigurationA Config mutation folds a delta in. Unlike Class and Object, every contributing augment applies, in ascending Priority order.
cpp
// The slot tag is authored on YOUR contract - the plugin ships no Class Slot tags.
// Returns the augment's replacement when one is equipped, the default otherwise.
static const FGameplayTag ProjectileSlot =
FGameplayTag::RequestGameplayTag(FName("Ability.Slot.Projectile"));
TSubclassOf<AActor> ProjectileClass = GetConfigurableClass<AActor>(ProjectileSlot);
Class and Object slots resolve at read time
They are not part of the property cache, because there is nothing numeric to accumulate - a swap is simply the winning augment's asset. Equipping or unequipping therefore takes effect on the next read with no recalculation. When several equipped augments target the same slot the highest Priority wins, and a mutation's Minimum Level and Application Requirements gate it exactly as they do for a property.
Blueprint: pass the Expected Type
Get Configurable Class and Get Configurable Object take an Expected Type. The resolved asset is checked against it, and the node returns null - with a warning naming what it actually found - if the slot holds something else. A mis-authored slot surfaces at the call site instead of downstream.
Blueprint: one node for all four property types
The palette has a single Get Configurable Value. Set its Property Tag and the node looks the tag up in the contract, reads which type the property was authored as, and retypes its Default Value and Return Value pins to float, integer, boolean or vector - then compiles into the matching getter above.

When no contract can answer - the Property Tag pin is wired, the Blueprint is not a Crimson Gameplay Ability, the ability has no contract, or the tag is declared by an augment rather than published by the contract - the pins take the Value Type field on the node instead, and the compiler names the case that applied. An augment-declared property is the one you will meet in practice: set Value Type to match what the augment declares.

See also

  • How-To: Declare an Ability Contract
  • Concept: Augment System