GAS Overview¶
Summary: Project Eternal uses the Gameplay Ability System (GAS) as the foundation for all character actions, abilities, status effects, and attributes. This document explains the architectural decisions, component relationships, and data flow patterns.
Why GAS?¶
GAS provides a production-ready framework for: - Predictive Networking: Client-side prediction with server authority - Attribute Replication: Automatic sync of stats across clients - Effect Stacking: Complex buff/debuff interactions out of the box - Tag-Based Filtering: Abilities blocked/allowed by gameplay tags
Project Eternal extends GAS with custom input binding, Blueprint delegates for UI, and a consolidated attribute set.
Architecture¶
ASC Ownership Model¶
+---------------------------+
| AEternalPlayerState | <-- Owner Actor (persists across respawn)
| (Owner) |
+---------------------------+
|
| owns
v
+---------------------------+
| UEternalAbilitySystemComponent |
| - Manages abilities |
| - Tag-based input |
| - UI delegates |
+---------------------------+
|
| contains
v
+---------------------------+
| UEternalAttributeSet |
| - All character stats |
| - Replicated values |
+---------------------------+
Why PlayerState? - Survives pawn death/respawn - Proper multiplayer replication - Consistent ability state across character swaps
Input Flow¶
Enhanced Input Action
|
v
UEternalInputSubsystem
|
| Converts to GameplayTag
v
AbilityInputTagHeld(Tag)
|
| Finds abilities with matching DynamicAbilityTags
v
TryActivateAbility(Handle)
Component API¶
UEternalAbilitySystemComponent¶
| Method | Purpose |
|---|---|
AddCharacterAbilities(TArray<TSubclassOf<UGameplayAbility>>) |
Grants startup abilities with input tags |
AbilityInputTagHeld(FGameplayTag) |
Activates abilities matching the input tag |
AbilityInputTagReleased(FGameplayTag) |
Notifies abilities of input release |
GetSpecByHandle(FGameplayAbilitySpecHandle) |
Retrieves ability spec for UI display |
Delegates¶
| Delegate | Fires When | Use Case |
|---|---|---|
OnAbilityGranted |
Any ability is given | Skillbar updates |
OnAbilityRemoved |
Any ability is removed | Skillbar cleanup |
EffectAssetTags |
GE with asset tags applied | UI notifications |
Attribute Categories¶
Organization¶
+------------------+ +------------------+ +------------------+
| Hard Attributes | | Vital Attributes| | Recovery Rates |
| (Primary Stats) | | (Resources) | | |
+------------------+ +------------------+ +------------------+
| Ferocity | | Health/MaxHealth | | HealthRecoveryRate |
| Grace | | Stamina/MaxStamina| | StaminaRecoveryRate |
| Insight | | Resonance/MaxRes | | ResonanceRecoveryRate |
| Clarity | | MaxAdrenaline | | |
| Feral | +------------------+ +------------------+
| Dread |
+------------------+
+------------------+ +------------------+ +------------------+
| Soft Attributes | | Combat Attributes| | Armor/Resistance |
| (Secondary) | | | | |
+------------------+ +------------------+ +------------------+
| Resolve | | MovementSpeed | | Armor |
| Presence | | Poise/MaxPoise | | ArmorPenetration |
+------------------+ | PoiseRecoveryRate| | ResistanceFire |
| BaseDamage | | ResistanceCorruption |
+------------------+ | ResistanceElectric |
+------------------+
+------------------+ +------------------+ +------------------+
| Critical Strike | | Area Modifiers | | Cost Modifiers |
| | | | | |
+------------------+ +------------------+ +------------------+
| CritStrikeChance | | DamageDealtMult | | StaminaCostMult |
| CritStrikeDamage | | DamageTakenMult | | ResonanceCostMult|
+------------------+ +------------------+ +------------------+
+----------------------------+ +-----------------------------+
| Increased Damage Buckets | | Conditional Damage vs Status|
| (additive %, base 0) | | (additive %, base 0) |
+----------------------------+ +-----------------------------+
| IncreasedPhysicalDamage | | ConditionalDamageVsBleeding |
| IncreasedFireDamage | | ConditionalDamageVsIgnited |
| IncreasedCorruptionDamage | | ConditionalDamageVsPoisoned |
| IncreasedElectricDamage | | ConditionalDamageVsShocked |
| IncreasedDamageOverTime | +-----------------------------+
| IncreasedStatusDuration |
+----------------------------+
Attribute Purposes¶
| Category | Attributes | Role in Gameplay |
|---|---|---|
| Hard | Ferocity, Grace, Insight, Clarity, Feral, Dread | Core stats from character build |
| Vital | Health, Stamina, Resonance | Consumable resources |
| Recovery | *RecoveryRate variants | Passive regen speeds |
| Combat | MovementSpeed, Poise, BaseDamage | Combat pacing, NPC damage scaling (no AttackSpeed attribute — attack speed is weapon-local: weapon base rate + combo ramp) |
| Critical Strike | CriticalStrikeChance, CriticalStrikeDamage | Crit chance (%) and bonus damage multiplier |
| Defense | Armor, Resistances (Fire, Corruption, Electric) | Damage reduction |
| Area Modifiers | DamageDealtMultiplier, DamageTakenMultiplier | Dungeon area modifiers (base 1.0) |
| Cost Modifiers | StaminaCostMultiplier, ResonanceCostMultiplier | Ability cost scaling (base 1.0) |
| Increased Damage | IncreasedPhysical/Fire/Corruption/Electric, IncreasedDamageOverTime, IncreasedStatusDuration | Additive-summed % buckets folded into the per-type damage line — see Damage Execution |
| Conditional vs Status | ConditionalDamageVsBleeding/Ignited/Poisoned/Shocked | Flat % that joins the matching per-type Increased line only when the target carries the matching Status.* tag — see Conditional Modifiers |
| Meta | IncomingDamage, IncomingCritical | Damage pipeline (not replicated) |
Note — IncreasedStatusDuration is not a damage bucket. Although it lives in the same
Stats.Offensive.Increased.*family, the damage ExecCalc never reads it. It is consumed source-side byUOnHitAbility(viaDurationMultiplierAttribute) to extend the duration of status effects the wearer inflicts — distinct from the per-type Increased buckets the ExecCalc folds into the damage line. See Damage Execution.
Replication¶
All gameplay-relevant attributes use REPNOTIFY_Always to ensure UI updates even when the value is unchanged (e.g., clamped at max).
Tag-to-Attribute Mapping¶
The AttributeSet maintains a runtime map for dynamic lookups:
FGameplayTag -> FGameplayAttribute Getter
--------------------------------------------|--------------------------
Stats.Attributes.Hard.Ferocity -> GetFerocityAttribute
Stats.Attributes.Hard.Grace -> GetGraceAttribute
Stats.Resources.Health -> GetHealthAttribute
Stats.Resources.Stamina -> GetStaminaAttribute
Stats.Offensive.Increased.Fire -> GetIncreasedFireDamageAttribute
Stats.Offensive.ConditionalDamage.VsBleeding -> GetConditionalDamageVsBleedingAttribute
...
Why? Allows GameplayEffects to target attributes by tag rather than hardcoded references, enabling data-driven stat modifications. The Increased and Conditional buckets are resolved the same dynamic way — a rolled Stats.Offensive.* modifier never names a C++ attribute; the TagsToAttributes map turns its tag into the matching getter at apply time.
Character Class Configuration¶
Class Defaults¶
| Class | Description | Starting Focus |
|---|---|---|
| Acolyte | Religious scholar | Insight, Clarity |
| Artist | Creative combatant | Grace, Presence |
| Commoner | Everyman survivor | Balanced stats |
| Weaver | Magical artisan | Insight, Feral |
Each class defines:
- Primary attribute GE (base stats)
- Secondary attribute GE (derived stats)
- Starter Ability Kit (StartupKit)
- Starting item manifests
Ability granting is kit-based. The legacy flat "startup abilities array" was replaced by an Ability Kit (UEternalAbilitySet) referenced from the class row's StartupKit, granted server-side at possession. Abilities reach an ASC from three layers in order — class starter kit, preset kits, then item abilities — with input-slot collisions resolving last-granted-wins. See Ability Kits for the data model, the server-authority grant path, and the possession-race latch that grants each kit exactly once per session.
Dynamic GameplayEffect Pattern¶
Equipment, dungeon modifiers, and glyphs all apply stat modifications via runtime-created UGameplayEffect objects rather than Blueprint GE classes. This allows data-driven modifier definitions to target any attribute by tag without requiring a separate Blueprint asset per modifier.
How It Works¶
Modifier Source (Equipment, Dungeon, Glyph)
│
├─ Resolve FGameplayTag → FGameplayAttribute via TagsToAttributes map
├─ Create UGameplayEffect at runtime (NewObject on ASC)
├─ Set DurationPolicy = Infinite
├─ Add FGameplayModifierInfo with:
│ ├─ EModifierOperation::Flat → EGameplayModOp::Additive
│ └─ EModifierOperation::Increased → EGameplayModOp::Multiplicative (bias 1.0)
└─ ApplyGameplayEffectToSelf → ActiveEffectHandle for cleanup
Why Dynamic GEs?¶
| Concern | Blueprint GEs | Dynamic GEs |
|---|---|---|
| New modifier type | New Blueprint asset | New data row only |
| Attribute targeting | Hardcoded per asset | Resolved by tag at runtime |
| Magnitude | Fixed or SetByCaller | Set directly from rolled value |
| Asset count | One per modifier family | Zero — all created at runtime |
Systems Using This Pattern¶
| System | Component | Applies When |
|---|---|---|
| Equipment | FEquipmentFragment |
Item equipped |
| Dungeon | UDungeonModifierComponent |
Player enters dungeon |
| Glyph | UPlayerGlyphComponent |
Glyph socketed |
Helper: UEternalAbilitySystemLibrary::ApplyDynamicModifierEffect()
Data Flow Examples¶
Ability Activation¶
1. Player presses attack button
2. InputSubsystem -> AbilityInputTagHeld("InputTag.Attack.Primary")
3. ASC iterates ActivatableAbilities
4. Finds ability with matching DynamicAbilityTags
5. Calls AbilitySpecInputPressed + TryActivateAbility
6. Ability activates, plays montage, waits for events
Attribute Modification¶
1. GameplayEffect applies to target
2. Modifier evaluated (base value + modifiers)
3. PreAttributeChange() - validation/clamping
4. Value updated on AttributeSet
5. PostGameplayEffectExecute() - side effects
6. OnRep_* fires on clients for UI update
Source Reference¶
| Component | Location |
|---|---|
| EternalAbilitySystemComponent | Source/ProjectEternal/Public/AbilitySystem/EternalAbilitySystemComponent.h |
| EternalAttributeSet | Source/ProjectEternal/Public/AbilitySystem/EternalAttributeSet.h |
| CharacterClassInfo | Source/ProjectEternal/Public/AbilitySystem/Data/CharacterClassInfo.h |
| Tag-to-Attribute Map | EternalAttributeSet.cpp:Constructor |
| Dynamic GE Helper | Source/ProjectEternal/Public/Utils/EternalAbilitySystemLibrary.h |
| Modifier Operations | Source/ProjectEternal/Public/Inventory/Modifiers/ModifierDefinitions.h → EModifierOperation |
Related Systems¶
- Ability Classes - Ability hierarchy and types
- Ability Kits - Kit composition, grant order, and the possession-race latch
- Damage Execution - Damage calculation pipeline
- Resource Recovery - Health/Stamina/Resonance regeneration
- Character Framework - ASC ownership
Refactoring Considerations¶
| Issue | Current State | Recommendation |
|---|---|---|
| Large AttributeSet | 30+ attributes in one class | Split into focused sets (Vital, Combat, Poise, Resistance) |
EffectAssetTags delegate |
FEffectAssetTags (non-dynamic multicast), limited Blueprint use |
Convert to a BlueprintAssignable dynamic multicast delegate |
| Input tag iteration | Scans all abilities per input | Cache TMap<FGameplayTag, TArray<FGameplayAbilitySpecHandle>> |
Recent Changes¶
| Date | Change | Impact |
|---|---|---|
| 2026-06-17 | Theorycraft attribute wave | Added Increased Damage buckets (Physical/Fire/Corruption/Electric, DamageOverTime, StatusDuration) and Conditional Damage vs Status (Bleeding/Ignited/Poisoned/Shocked); all resolved via TagsToAttributes from Stats.Offensive.Increased.* / Stats.Offensive.ConditionalDamage.Vs* |
| 2026-02 | Dynamic GE pattern | Runtime-created GEs replace Blueprint GEs for equipment, dungeon, glyph modifiers |
| 2026-02 | EModifierOperation enum | Flat (GAS Additive) and Increased (GAS Multiplicative with bias 1.0) |
| 2026-02 | Critical strike attributes | CriticalStrikeChance, CriticalStrikeDamage, IncomingCritical meta attribute |
| 2026-02 | Area modifier attributes | DamageDealtMultiplier, DamageTakenMultiplier (base 1.0, used by dungeon modifiers) |
| 2026-02 | Cost modifier attributes | StaminaCostMultiplier, ResonanceCostMultiplier (base 1.0) |
| 2026-02 | Added BaseDamage attribute | NPC damage scaling via Balance System |
| 2025-Q1 | Trimmed to 3 resistances | Fire, Corruption, Electric |
| 2024-Q4 | Added Poise attribute system | New MaxPoise, PoiseRecoveryRate attributes |
| 2024-Q4 | Blueprint delegates for UI | OnAbilityGranted/Removed now BlueprintAssignable |
| 2024-Q3 | Tag-based input binding | Replaced enum-based InputID system |