How-To: Constraint Actions
Goal: turn an inventory capacity value into gameplay. You will make a carry-weight cap that can be exceeded, then have going over it apply a gameplay effect that slows the player in proportion to how overloaded they are - without writing a line of encumbrance code.
CrimsonGameFeatureAction_AddInventory wiring) and an ability system with an attribute set on the same owner (Character Pipeline). Read CrimsonInventory -> How-To: Add Constraints first if constraint tags are new to you - especially its Observe capacity values step, which is the signal everything here rides on.What a constraint action is
CrimsonInventory announces every settled constraint value through UCrimsonInventoryManagerComponent::OnConstraintValueChanged and deliberately does nothing with it - a capacity is a number, not a rule. A constraint action is one reaction to that number. You list actions against a constraint tag in an asset, hand the asset to the component on the player state, and each action runs whenever that tag's value settles. Nothing in the system is weight-specific; weight is just the example.
| Type | Role |
|---|---|
UCrimsonCoreConstraintAction | Abstract, Blueprintable, EditInlineNew. Two BlueprintNativeEvents: OnConstraintChanged(Context) (once when bound with the current value, then after every change of the bound tag) and OnUnbound(Context) (the owner is going away - undo anything persistent). The base does nothing, so implement only the one you need. |
FCrimsonCoreConstraintActionContext | What the action is handed: Owner (the actor the component lives on), Inventory, AbilitySystem (null in a project without GAS - always check it), and Capacity, the FCrimsonInventoryCapacityView including the unclamped Fraction. Built fresh per announce; cache nothing from it. |
UCrimsonCoreConstraintAction_ApplyGameplayEffect | Shows in the picker as Apply Gameplay Effect While Over Threshold. Effect, ActivateAtFraction, SetByCallerTag, SetByCallerScale. Keeps one infinite effect applied while Fraction is above the threshold and writes (Fraction - ActivateAtFraction) * SetByCallerScale onto the spec. |
UCrimsonCoreConstraintActionSet | The asset. Bindings: each is one exact ConstraintTag (no child matching) plus an Instanced array of action templates. The component duplicates every template per owner, so an action may hold runtime state such as an effect handle. |
UCrimsonCoreConstraintActionComponent | The runner, already present as the default subobject ConstraintActions on ACrimsonPlayerState. Fill its ActionSets. Authority only. |
1. Make the weight cap a soft threshold
A weight constraint normally refuses the add that would cross the cap - and a cap you can never exceed can never be a penalty. bAllowOverflow on UCrimsonInventoryConstraint_FloatTag turns that off: the rule stops vetoing and stops clamping quantities, and only counts. Fraction then climbs past 1.0 and there is something for an action to react to.
Content Browser -> right-click -> Blueprint Class, expand All Classes, pick CrimsonInventoryConstraint_FloatTag. Name it BPC_WeightConstraint_Overflow. In Class Defaults tick Allow Overflow. Leave Tracked Tag at Crimson.Inventory.Constraint.Weight and Read From Footprint Fragment on, so each item contributes the Weight from its Footprint fragment.
2. Seed the weight cap and attach the constraint
Float caps are seeded once, on the server, by InitializeInventory - there is no float-max setter to call later. In the experience wiring that means the AddInventory action carries both the cap and the rule.
Open your experience's action set, find the CrimsonGameFeatureAction_AddInventory entry for your player state, and on that InventoryList row add Default Float Constraints + -> Tag = Crimson.Inventory.Constraint.Weight, Stack Count = 50.0, then Runtime Constraints + -> BPC_WeightConstraint_Overflow. Leave the existing slot constraint alone; an inventory can carry both.
Get Constraint Capacity for Crimson.Inventory.Constraint.Weight returns bHasConstraint true with Max 50, Current 0, bIsFloat true.3. Give an item some weight
The constraint reads each item's weight from its Footprint fragment, so the item definitions decide what counts. Pick something you can collect three of.
Open an item definition - ItemDef_IronOre (/CrimsonCore/Inventory/Items/) is a good candidate, or duplicate it as ItemDef_HeavyRock. In Fragments, add or open CrimsonInventoryItemFragment_Footprint and set Weight to 20. Three of them is 60 against the 50 cap: Fraction 1.2.
Get Constraint Capacity reports Current 60, Max 50, Fraction 1.2 - and the third add succeeds, which is what bAllowOverflow bought you.4. Author the gameplay effect
The penalty is a designer-authored GameplayEffect asset, not C++ - the action only applies it and feeds it a magnitude. Create a Blueprint of GameplayEffect, name it GE_Overweight, and set:
| Field | Value | Why |
|---|---|---|
| Duration Policy | Infinite | The action decides when it ends by removing it; a duration would expire on its own. |
| Modifier attribute | CrimsonMovementSet.MovementSpeedAdditive | Walk speed is tiered as (Base + Flat) * Max(0, 1 + Additive) * Max(0, Multiplier), so an additive of -0.1 is a 10% slow whatever the base speed is. |
| Modifier operation | Add | Stacks with any other additive source instead of overwriting it. |
| Modifier magnitude | Set By Caller, Data Tag SetByCaller.Encumbrance.Overflow | The action writes the overflow here. The tag is declared in CrimsonCoreTags.h, so it resolves in the picker. |
| Granted tag | Status.Encumbered | Also declared in CrimsonCoreTags.h. Gives UI and other abilities something to ask about. |
| Stacking | None | One instance at a time; the action removes and re-applies when the magnitude changes. |
5. Bind the tag to an action in a set asset
right-click -> Miscellaneous -> Data Asset, choose CrimsonCoreConstraintActionSet, name it DA_ConstraintActions_Player. Add one Bindings entry: Constraint Tag = Crimson.Inventory.Constraint.Weight. Under its Actions, click + and pick Apply Gameplay Effect While Over Threshold, then expand it and set Effect = GE_Overweight, Activate At Fraction = 1.0, Set By Caller Tag = SetByCaller.Encumbrance.Overflow, Set By Caller Scale = -0.5.
ConstraintTag matches exactly - a parent tag will not catch its children, so list each tag you want to react to. To write your own reaction with no C++, make a Blueprint child of CrimsonCoreConstraintAction and override On Constraint Changed (and On Unbound if it leaves anything behind); it appears in the same picker.
Effect populated.6. Point the player state's component at the set
ACrimsonPlayerState already creates the component as the default subobject ConstraintActions, so there is nothing to add - only to fill in.
Open your player state Blueprint (B_CrimsonPlayerState, /CrimsonCore/Player/, or your own child of CrimsonPlayerState). Select ConstraintActions in the Components panel and add DA_ConstraintActions_Player to Action Sets. Several sets are fine - every binding in all of them is bound.
7. Check the attribute set is granted
MovementSpeedAdditive only exists if UCrimsonMovementSet is actually on the owner's ability system. If it is not, the effect applies and changes nothing - there is no error to read. Open the ability set your PawnData grants (AbilitySet_CrimsonCore, /CrimsonCore/AbilitySystem/, in the shipped wiring) and confirm CrimsonMovementSet is in its attribute sets. See Character Pipeline for how PawnData ability sets are granted.
- The capacity reads
Current 60, Max 50, Fraction 1.2.- Overflow is
1.2 - 1.0 = 0.2; times SetByCallerScale -0.5 that is -0.1 on MovementSpeedAdditive.-
showdebug abilitysystem lists GE_Overweight active with Status.Encumbered granted, and MaxWalkSpeed is 10% below its normal value.- A fourth item takes
Fraction to 1.6: the effect is removed and re-applied at -0.3. Drop back to two items and it is removed outright.- Save while overweight and reload: the effect comes back from the component's prime pass, because the restore recounts the weight and announces it.
Where it runs
| Piece | Runs on | Notes |
|---|---|---|
UCrimsonCoreConstraintActionComponent | Server (authority) only | It replicates nothing and does nothing at all on a client. Actions mutate authoritative state, so only the authority may run them. |
| The bind | Server | The inventory manager arrives from a game feature action and the owner's attribute sets are granted later still, so neither exists in BeginPlay. The component waits on two ModularGameplay feature states instead of searching for the component: CrimsonInventory::SystemReadyFeatureName and, when the owner has an ability system component, CrimsonAbilitySystem::SystemReadyFeatureName. The ability system only announces once its attribute sets exist, and an effect applied before its attribute set is present silently loses every modifier that targets it. Once both are in, the authored actions are duplicated and subscribed. |
| The prime pass | Server | Every bound tag is read once on arrival, so a restored save that is already over the cap is penalized before the first change announce. |
| The effect's result | Owning client, one hop later | UCrimsonMovementSet recalculates its tiered finals in PostAttributeChange on every machine and pushes MaxWalkSpeed to the character movement component, so the slow arrives with the attribute replication. A predicted version would have to be an ability. |
| Another player's capacity | Nowhere on your client | A player inventory's capacity containers replicate owner-only. If a system needs a teammate's derived stat, compute it on the server and replicate that stat. |
See also
- CrimsonInventory -> How-To: Add Constraints
- Inventory Showcases
- Character Pipeline