Conditional Modifiers
Summary: Conditional modifiers are equipment affixes that contribute only when a condition is met. Two flavors exist: wearer-state conditionals ("+30 Armor while at Full Health") that toggle a dynamic GE on/off as the wearer's state tags change, and target-state conditionals ("+20% damage vs Bleeding enemies") that fold a flat bonus into the damage calculation only when the hit target carries a matching status tag. All evaluation is server-authoritative. Wearer-state tags are driven by UStateTagComponent; target-state damage rides four new attributes resolved in ExecCalc_Damage.
Table of Contents
Architecture Overview
┌──────────────────────────────────────────────────────────────┐
│ WEARER-STATE PATH ("+30 Armor while at Full Health") │
│ │
│ Health changes (server) │
│ │ │
│ ▼ │
│ UStateTagComponent ── sets loose tag State.Health.Full │
│ │ on the wearer's ASC │
│ ▼ │
│ UTargetTagRequirementsGameplayEffectComponent re-evaluates │
│ │ (GAS callback on owner-tag change) │
│ ▼ │
│ Conditional GE toggles inhibited ↔ active │
│ → Armor aggregate updates → replicates │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ TARGET-STATE PATH ("+20% damage vs Bleeding") │
│ │
│ Item mod (Flat → ConditionalDamageVsBleeding attribute) │
│ │ applied as plain unconditional GE on equip │
│ ▼ │
│ Hit lands → UExecCalc_Damage (server) │
│ │ │
│ ▼ │
│ GetHitConditionalVsStatusSum() sums each ConditionalDamageVs*│
│ │ whose Status.* tag is on the target │
│ ▼ │
│ Sum folded into per-type Increased line: │
│ Damage *= max(0, 1 + (Increased + CondSum) / 100) │
└──────────────────────────────────────────────────────────────┘
Key Design Principles
| Principle |
Implementation |
| Server authority |
Tag writes, recently-window timers, GE inhibition, and ExecCalc evaluation all run server-side |
| Reuse GAS inhibition |
Wearer conditionals use the engine's UTargetTagRequirementsGameplayEffectComponent — no custom polling |
| No new apply path |
Conditional mods flow through the same FEquipmentFragment::ApplyModifierEffect() helper as Global mods; empty requirements = plain GE |
| Two distinct gates |
Wearer tags toggle a GE; target tags gate a damage multiplier inline. They never mix. |
| Client reads tags for UI only |
Loose tags replicate via the ASC tag-count map so tooltips can show condition state; gameplay outcome rides replicated attributes |
Core Concepts
Why Conditional Modifiers Exist
They unlock the conditional power itemization identity channel (GDD Progression & Itemization pillar): affixes that reward skillful play — staying at full HP, maintaining bleeds, dodge-weaving — instead of flat always-on stats. Because the condition is deterministic and server-authoritative, the bonus is honest and trade-valuable.
The Two Gates
|
Wearer-State |
Target-State |
| Example |
"+30 Armor while at Full Health" |
"+20% damage vs Bleeding enemies" |
| Condition source |
Wearer's own ASC tags |
Hit target's status tags |
| Representation |
FGameplayTagRequirements on the modifier def |
FGameplayTag (Status.*) checked at exec time |
| Mechanism |
GE inhibited until tags satisfied |
Flat attribute summed, folded into damage |
| Evaluated |
On every wearer tag change |
Once per hit, in ExecCalc_Damage |
WearerTagRequirements |
Non-empty |
Empty (plain GE) |
Wearer-State Conditionals
A wearer-state conditional carries an FGameplayTagRequirements (FModifierDefinition::WearerTagRequirements). On equip, FEquipmentFragment::ApplyModifierEffect() builds the dynamic GE through UEternalAbilitySystemLibrary::CreateAttributeEffect(..., WearerTagRequirements), which attaches a UTargetTagRequirementsGameplayEffectComponent whenever the requirements are non-empty.
WearerTagRequirements satisfied? → GE active (modifier contributes)
WearerTagRequirements unmet? → GE inhibited (modifier contributes 0)
GAS re-evaluates automatically whenever the wearer's owned tags change — no tick, no manual polling. RequireTags must all be present; IgnoreTags must all be absent.
| Tag |
Meaning |
Source |
State.Health.Full |
Health ≈ MaxHealth |
UStateTagComponent health eval |
State.Health.Above70 |
Health ratio ≥ 0.70 |
UStateTagComponent health eval |
State.Health.Below35 |
Health ratio ≤ 0.35 |
UStateTagComponent health eval |
State.Recently.Kill |
Wearer killed an enemy (4s refreshable window) |
CombatComponent fatal branch |
State.Recently.Crit |
Wearer landed a crit (4s refreshable window) |
CombatComponent crit branch |
State.Recently.Dodge |
Wearer dodged (4s refreshable window) |
DodgeAbility (authority) |
Target-State Conditionals
Target-state conditionals are flat damage bonuses keyed to the target's status. The modifier (scope Conditional, operation Flat, target property a Stats.Offensive.ConditionalDamage.Vs* tag) flat-adds into a source attribute — there is no WearerTagRequirements, so the GE is plain and always active.
The condition is evaluated at damage time. UExecCalc_Damage::GetHitConditionalVsStatusSum() sums every ConditionalDamageVs* attribute whose matching Status.* tag is present on the target, then folds the sum into the per-damage-type Increased additive line (one combined multiplier per damage type, applied before resistance). See Damage Execution for how the Increased line resolves.
| Attribute |
Status tag checked |
Native attribute tag |
ConditionalDamageVsBleeding |
Status.Bleed |
Stats.Offensive.ConditionalDamage.VsBleeding |
ConditionalDamageVsIgnited |
Status.Ignite |
Stats.Offensive.ConditionalDamage.VsIgnited |
ConditionalDamageVsPoisoned |
Status.Poison |
Stats.Offensive.ConditionalDamage.VsPoisoned |
ConditionalDamageVsShocked |
Status.Shock |
Stats.Offensive.ConditionalDamage.VsShocked |
Because they sum into the additive Increased line, multiple "vs status" sources stack additively (PoE "Increased" semantics), not multiplicatively.
State Tag Component
UStateTagComponent (on AEternalCharacter) is the authoritative broadcaster of transient wearer-state tags that drive wearer-state conditionals. It holds no replicated properties of its own — the loose tags it writes replicate via the ASC's tag-count map.
PossessedBy() → StateTagComponent->InitializeWithAbilitySystem(ASC)
│ binds Health / MaxHealth change delegates, runs initial eval
│
├── Health changes → EvaluateHealthTags()
│ → SetStateTag(State.Health.Full / Above70 / Below35, on/off)
│
└── NotifyRecentlyKill / Crit / Dodge()
→ ApplyRecentlyTag(Tag, 4s refreshable timer)
| Responsibility |
How |
| Health threshold tags |
Bound to ASC Health/MaxHealth deltas; recomputed on change |
| Recently-event windows |
Notify* from combat/dodge hooks set a tag + a refreshable 4s timer |
| Idempotent tag writes |
SetStateTag(Tag, bool) adds/removes a loose tag once |
| Initialization |
InitializeWithAbilitySystem() from PossessedBy() after InitAbilityActorInfo() |
Hook points (server-side): UCombatComponent::HandleDamageReceived() notifies the source's component on crit (bWasCriticalHit) and on the fatal branch (kill); UDodgeAbility::ActivateAbility() notifies the avatar's component after a committed dodge on authority.
The component lives under 03_Combat/Components/ because it is pawn-lifecycle combat state, but its sole consumer today is the conditional-modifier system — hence it is documented here.
Authoring Guide
Wearer-Condition Modifier
| Field |
Value |
ModifierScope |
Conditional |
ModifierOperation |
Flat or Increased (whatever the stat needs) |
TargetProperty |
The attribute tag being boosted (e.g. Stats.Defensive.Armor) |
WearerTagRequirements.RequireTags |
The gate (e.g. State.Health.Full) |
DescriptionTemplate |
e.g. "+{0} Armor while at Full Health" |
Target-State Damage Modifier
| Field |
Value |
ModifierScope |
Conditional |
ModifierOperation |
Flat (attribute value is the summed %) |
TargetProperty |
Stats.Offensive.ConditionalDamage.Vs* |
WearerTagRequirements |
Empty |
DescriptionTemplate |
e.g. "{0}% increased Damage against Bleeding enemies" |
Replication & Authority
| Operation |
Server |
Client |
| State tag writes (health, recently) |
✅ UStateTagComponent |
Reads replicated loose tags (UI only) |
| Recently-window timers |
✅ |
❌ |
| GE inhibition toggle |
✅ via tag-change callback |
Receives resulting attribute value |
| Target-state damage sum |
✅ in ExecCalc_Damage |
Receives resulting IncomingDamage |
Clients never gate gameplay locally — they read replicated loose tags purely to render condition state in tooltips. Designed for dedicated-server co-op.
Public Contracts
FModifierDefinition (new field)
| Field |
Type |
Purpose |
WearerTagRequirements |
FGameplayTagRequirements |
Wearer-side gate for Conditional-scope mods. Empty = unconditional. |
UEternalAbilitySystemLibrary::CreateAttributeEffect (new overload)
| Parameters |
Purpose |
(ASC, AttributeTag, Value, Operation, OngoingWearerRequirements) |
Builds a dynamic GE; attaches UTargetTagRequirementsGameplayEffectComponent only when requirements are non-empty |
UStateTagComponent
| Method |
Purpose |
InitializeWithAbilitySystem(ASC) |
Bind attribute delegates + initial eval (call from PossessedBy) |
NotifyRecentlyKill / Crit / Dodge() |
Open a 4s refreshable state-tag window |
EvaluateHealthTags() |
Recompute health-threshold tags |
SetStateTag(Tag, bool) |
Idempotent loose-tag setter |
New Attributes (UEternalAttributeSet)
ConditionalDamageVsBleeding, ConditionalDamageVsIgnited, ConditionalDamageVsPoisoned, ConditionalDamageVsShocked — full replication, mapped in TagsToAttributes.
Source References
| Component |
Location |
UStateTagComponent |
Public/Combat/Components/StateTagComponent.h |
FModifierDefinition::WearerTagRequirements |
Public/Inventory/Modifiers/ModifierDefinitions.h |
CreateAttributeEffect overload |
Public/Utils/EternalAbilitySystemLibrary.h |
ApplyModifierEffect helper |
Private/Inventory/Items/Fragments/ItemFragment.cpp |
ConditionalDamageVs* attributes |
Public/AbilitySystem/EternalAttributeSet.h |
| Target-state sum |
Private/AbilitySystem/ExecCalc/ExecCalc_Damage.cpp → GetHitConditionalVsStatusSum() |
Native tags (State.*, ConditionalDamage.*) |
Public/EternalGameplayTags.h / Private/EternalGameplayTags.cpp |
| StateTag init |
Private/Character/EternalCharacter.cpp → PossessedBy() |
| Recently hooks |
Private/Combat/Components/CombatComponent.cpp, Private/Abilities/DodgeAbility.cpp |
| Editor authoring |
Source/ProjectEternalEditor/Private/ModifierManager/SModifierDetailsPanel.cpp |
Recent Changes
| Date |
Change |
Impact |
| 2026-06-11 |
Conditional modifiers (wearer-state + target-state) |
FModifierDefinition::WearerTagRequirements gates GEs via UTargetTagRequirementsGameplayEffectComponent; new UStateTagComponent drives State.Health.* / State.Recently.* loose tags; 4 ConditionalDamageVs* attributes fold into the per-type Increased line in ExecCalc_Damage |