Skip to content

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.

Available Wearer Tags

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.cppGetHitConditionalVsStatusSum()
Native tags (State.*, ConditionalDamage.*) Public/EternalGameplayTags.h / Private/EternalGameplayTags.cpp
StateTag init Private/Character/EternalCharacter.cppPossessedBy()
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