Vitals: Shield, Barrier & Reservation

The defensive vocabulary ARPGs actually ship: a shield pool that absorbs before health and refills after a quiet period, a temporary barrier that can exceed maximum health and expires on a timer, and aura-style reservation that makes part of the health pool permanently unusable.

Prerequisites
A pawn with a UCrimsonAbilitySystemComponent and a UCrimsonHealthComponent - both are created for you by ACrimsonCharacter. See Character Pipeline. The bar itself is UCrimsonPoolBarWidget from CrimsonUI; see CrimsonUI -> How-To: Layered Pool Bars for the rendering side.

What lives where

PieceShips inPurpose
UCrimsonHealthSetCrimsonCoreHealth, MaxHealth, and RESERVATION.
UCrimsonShieldSetCrimsonCoreBoth absorb pools: Shield and Overhealth.
UCrimsonHealthComponentCrimsonCoreThe single bind point for all of it.
UCrimsonVitalsViewModelCrimsonCoreField-notify mirror of the component.
UCrimsonCoreVitalsWidgetCrimsonCoreThe player's HUD bar.
UCrimsonPoolBarWidgetCrimsonUIThe generic layered renderer it derives from.

Attributes

SetAttributeMeaning
UCrimsonHealthSetHealthReservedFlat, HealthReservedPercentWhat auras and similar permanent costs reserve. Percent is 0..1.
UCrimsonHealthSetHealthReservedDerived final, replicated. Capped one point below MaxHealth.
UCrimsonShieldSetShield, MaxShieldA standing pool. MaxShield is tiered like MaxHealth.
UCrimsonShieldSetMaxShieldBase / Flat / Additive / MultiplierThe tiers gear and buffs modify.
UCrimsonShieldSetShieldRechargeRate, ShieldRechargeDelayPoints per second, and seconds of quiet before recharge starts.
UCrimsonShieldSetShieldDecayPerSecondOptional bleed-off for a ward build. 0 disables.
UCrimsonShieldSetOverhealth, MaxOverhealthThe barrier. May exceed MaxHealth.
Everything clamps to the EFFECTIVE max
GetEffectiveMaxHealth() is MaxHealth - HealthReserved, and every clamp on Health uses it - not MaxHealth. That is the entire behavioral contract of reservation. MaxHealth stays the untaxed total because the bar needs both numbers to draw the dead zone.

1. Grant the shield set

UCrimsonShieldSet is optional by design. Without it, UCrimsonHealthComponent reports zero for every absorb value, UCrimsonHealthSet skips the absorption chain entirely, and damage lands on health exactly as before. Most NPCs never need it.

Add it to the Granted Attributes array of the ability set your pawn uses, and fill in Default Values - order matters, because Shield is clamped to MaxShield and MaxShield is only derived once MaxShieldBase is written.

OrderAttributeWhy
1MaxShieldBaseDerives MaxShield through the tiered calculation.
2ShieldStart full, or leave at 0 to watch it charge on spawn.
3ShieldRechargeRatePoints per second.
4ShieldRechargeDelaySeconds of no damage before recharge begins.
Reference implementation
AbilitySet_CrimsonCore (/CrimsonCore/AbilitySystem/) grants UCrimsonHealthSet, UCrimsonMovementSet, UCrimsonCombatSet, UCrimsonHasteSet and UCrimsonShieldSet together. Study its Granted Attributes rows - each one pairs a set with its starting values.
Verify
PIE, then showdebug abilitysystem. MaxShield reads your number, not 0. Reservation needs no grant - it lives on UCrimsonHealthSet, which is already granted everywhere.

2. Absorption order

Incoming damage is spent Overhealth, then Shield, then Health, in UCrimsonHealthSet::PostGameplayEffectExecute. The expiring resource goes first: a barrier that times out unused is worse for the player than a shield that has to wait to recharge.

text
before hit Health 328.12 Shield 150.0 Overhealth 140.0
250 damage
after hit Health 328.12 Shield 40.0 Overhealth 0.0
barrier absorbed 140, shield absorbed 110, health took 0

A hit that was absorbed stamps Damage.Result.Absorbed onto the effect context, and the hit that empties the shield also stamps Damage.Result.ShieldBroken, so combat text reports both without extra plumbing. The pop still shows the full incoming damage - the player wants to know what was swung at them, and the absorb tag is what says the pools ate it.

Server only
PostGameplayEffectExecute is authority-side. Clients see the resulting attribute values replicate; they never run the absorption chain themselves.

3. Barriers have no timer

MaxOverhealth is the sum of the currently active barrier effects. UCrimsonShieldSet::PostAttributeChange mirrors a rise onto Overhealth - barriers arrive full - and clamps it back down on a fall - barriers leave empty. A duration gameplay effect adding capacity is therefore the entire lifetime mechanism.

Leave barrier stacking at None
Two applications are then two separate active effects with two separate durations, which is exactly what makes independent expiry work. Any other stacking mode merges them onto one clock and they expire together.
Verify
Apply two barriers with different durations. Overhealth steps down twice, once per expiry.

4. The recharge delay is a tag, not a timer

UCrimsonHealthComponent applies the effect in its Shield Recharge Block Effect property to the victim on every damaging hit. That effect grants Status.Shield.RechargeBlocked, and the recharge effect refuses to run while the target owns it. That pairing is the whole delay - no C++ anywhere has to know what time it is.

EffectShapeKey detail
Recharge blockHas Duration, no modifiersDuration is Attribute Based on ShieldRechargeDelay, Source Target, Snapshot OFF. Grants the blocked tag.
RechargeInfinite, Period 0.25Shield Add (Base), Attribute Based on ShieldRechargeRate, Coefficient 0.25.
Decay (optional)Infinite, Period 0.25Same, but Coefficient -0.25 against ShieldDecayPerSecond.
Must Not Have Tags, not Must Have
On the recharge effect the blocked tag belongs in Ongoing Tag Requirements -> Must Not Have Tags. Put it in Must Have Tags and recharge runs ONLY during the delay window, so the shield refills by exactly ShieldRechargeRate * ShieldRechargeDelay after each hit and then stops forever. It looks like a cap and is not one. This is the single easiest field in the system to invert.
Snapshot off on the block duration
So a 'faster recharge' buff shortens a block that is already running, rather than keeping the value it happened to see when it was applied.
Verify
Take a hit and watch Shield in showdebug abilitysystem. It sits flat for exactly ShieldRechargeDelay, then climbs at ShieldRechargeRate per second all the way to MaxShield. If it stops short, check the tag requirement direction.

5. Reservation

An aura applies an infinite effect that adds to HealthReservedPercent via SetByCaller SetByCaller.Health.ReservePercent, as a fraction 0..1. Use Add (Base), never Override - several auras have to reserve at once, and HealthReserved is derived from the running total. Removing the effect frees the capacity immediately.

Verify
Reserve 25% and heal to full. The bar shows a hatched dead zone at the end of the track, health clamps to the new effective max, and the readout shows the same number twice - a fully-healed character is at full, not permanently wounded.

6. Put the bar on the HUD

UCrimsonCoreVitalsWidget does the binding, the retry-until-the-ability-system-replicates loop, repossession handling, the trail band and the eased fill. A Blueprint child needs only the visuals.

Reference implementation
Open the shipped W_CrimsonCoreVitals (/CrimsonCore/UserInterface/HUD/). Its tree is SizeBox_Root -> Box_Root -> (SizeBox_Bar -> Image_Bar) + (Box_Readout -> Text_Value + Text_Barrier), and it has zero graph nodes - Text_Value and Text_Barrier are BindWidgetOptional and are filled in by C++.
Widget nameRequiredPurpose
Image_BarFor the bar to drawBrush set to MI_UI_CrimsonPoolBar_Player, with an explicit brush Image Size.
Text_ValueOptionalHealth over EFFECTIVE max. Format on Value Format.
Text_BarrierOptionalBarrier badge. Collapses whenever Overhealth is 0.

Register it at the HUD.Slot.Health extension point through a UCrimsonGameFeatureAction_AddWidget action, and make sure the HUD layout actually contains a CrimsonUIExtensionPoint carrying that tag - without one the widget registers successfully and appears nowhere, with no error.

Verify
PIE. The bar appears, the readout matches the character's health, and taking damage moves it. Then Play As Client with two players - a late-joining client must show correct values rather than 0 / 0.

7. Nameplates get the same bar

UCrimsonNameplateWidget looks for an optional child named Bar_Vitals of type CrimsonPoolBarWidget and pushes the layered snapshot straight into it - no graph wiring, and enemy plates pick up shield and barrier for free. Team color still applies: the widget calls SetHealthColor with the target's team tint.

Reference implementation
The shipped W_CrimsonNameplateWidget (/CrimsonCore/UserInterface/Nameplates/) is SizeBox_0 -> Bar_Vitals, using MI_UI_CrimsonPoolBar_Nameplate. Use that instance, not the player one - it compiles four decorative features out of the shader, and it is the instance drawn dozens of times a frame.

OnVitalsVisual still fires alongside Bar_Vitals if you want a different treatment.

Binding is retried, not assumed

Attribute sets replicate as ability-system subobjects and routinely land several frames after the pawn. UCrimsonVitalsViewModel::TryBind is idempotent and returns false until the data is really there; the widget retries on a short timer and gives up after Rebind Timeout Seconds with a warning rather than spinning forever.

The classic client-side bug
Binding once on possession and trusting it is the single most common way a client-side health bar ends up frozen at zero. Treat a non-positive MaxHealth as 'not replicated yet'.

Testing

Console cheats on UCrimsonCheatManager. They are server-authoritative, so run them from the host window - a client has no cheat manager of its own (see below).

CommandEffect
VitalsDamage <n>Applies damage through the full pipeline, so absorption and combat text run.
VitalsHeal <n>Applies healing through the Healing meta attribute.
VitalsSetShield <n>Sets MaxShieldBase and fills Shield to it.
VitalsBarrier <amount> <duration>Grants a barrier for that many seconds.
VitalsReserve <percent>Reserves that percent of MaxHealth.
VitalsSetMaxHealth <n>Sets MaxHealthBase.
VitalsReportPrints the values AND the fractions the bar should be drawing.
"Command not recognized" means no cheat manager
That message means the manager does not exist, not that the command is missing - the console only reaches cheat functions through the player controller's CheatManager, so a null one makes every cheat name unrecognized. Two things have to be true:

1. The player controller's Cheat Class points at UCrimsonCheatManager. ACrimsonPlayerController sets this in C++ and B_PlayerController sets it in class defaults, so this is already done.
2. The game mode allows cheats. Epic's AGameModeBase::AllowCheats returns true only for NM_Standalone or the editor, which silently removes cheats from exactly the setup you need them in - a listen server or client launched outside the editor. ACrimsonGameMode overrides it to allow cheats in every net mode outside shipping builds.

Clients still get no manager: APlayerController::AddCheats only consults the game mode through GetAuthGameMode(), which is null on a client. Run cheats from the host, where the server holds authority over every player's state anyway.
VitalsReport is the arbiter
It runs the numbers through UCrimsonPoolBarWidget::ComputeFractions - the same function the widget uses - so if the printed fractions and the pixels disagree, the bug is in the material, not the maths.

Multiplayer

  • Absorption, reservation recalculation and the recharge-block application are server only - they run from PostGameplayEffectExecute, which is authority-side.
  • Every attribute here is replicated; clients receive final values and never re-derive them. RecalculateHealthReserved returns early off authority for exactly that reason.
  • UCrimsonVitalsViewModel and the bar are local and read-only. They are safe on a simulated proxy and never mutate gameplay.
  • The trail band, the eased fill and the flash are cosmetic and local. They are driven by replicated value changes, so each client animates its own.

See also

  • CrimsonUI -> How-To: Layered Pool Bars - the renderer, its style and its motion.
  • Nameplates - floating plates and boss bars.
  • Character Pipeline - where the health component comes from.
  • Testing Dummy - a target to hit while checking absorption.