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
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.
| Getter | Reads | How augments change it |
|---|---|---|
GetConfigurableValue / Int / Bool / Vector | The property cache | Property 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 Slots | A Class mutation replaces the default outright. |
GetConfigurableObject<T> | The contract's Object Slots | An Object mutation replaces the default outright. |
GetConfigurableStruct<T> | The contract's Configuration | A Config mutation folds a delta in. Unlike Class and Object, every contributing augment applies, in ascending Priority order. |
// 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);
Priority wins, and a mutation's Minimum Level and Application Requirements gate it exactly as they do for a property.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