GAS Health Drops To -1 On Any Damage

I’m losing my mind.

I’ve been following along with this fantastic GAS tutorial series by Ali Elzoheiry on YouTube, tweaking it slightly here and there to fit my game. However, I’m coming across some strange functionality that I can’t figure out the cause of.

I’m trying to implement a death system, and the logic should be very simple. Basically, in my VitalityAttributeSet in PostAttributeChange, I check if Health is less than or equal to 0, and if it is, I activate a gameplay ability with tag GameplayAbility.Death. Then all the actual dying is handled elsewhere. Pretty straightforward, right?

The death event works perfectly, no problems there. The issue is that death is triggered on any damage at all. For my game, Health/MaxHealth default to 5. Using some debug logging to print New and Old Values from PostAttributeChange, on an attack against an enemy which deals 1 damage, I’m getting this: Old=4.00 New=-1.00 . However, when I use the console command AbilitySystem.DebugAbility Health during play, it shows the expected health values, namely starting at 5.000 and dropping to 4.000 after an attack, with the enemy dying nonetheless.

I cannot make any sense of these debug values for the life of me. The only possible explanation that I can think of (in my very limited knowledge of C++ and GAS) is that for some reason GAS is trying to initialize my Attribute Set after damage, despite me activating an attribute initialization Gameplay Ability on Begin Play, per the tutorial. So Health starts at 0 since it’s not initialized, drops to -1 when I hit the enemy, and then gets 5 added to it as an initialization value, coming out to a total of 4. This is probably wrong, but I don’t know what else to think.

Here’s my VitalityAttributeSet.cpp if it helps:

// Fill out your copyright notice in the Description page of Project Settings.


#include "VitalityAttributeSet.h"
#include "Net/UnrealNetwork.h"
#include "GameplayEffectExtension.h"

UVitalityAttributeSet::UVitalityAttributeSet()
{
    Health = 5.f;
    MaxHealth = 5.f;
    Stamina = 5.f;
    MaxStamina = 5.f;
}

void UVitalityAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME_CONDITION_NOTIFY(UVitalityAttributeSet, Health, COND_None, REPNOTIFY_Always);
    DOREPLIFETIME_CONDITION_NOTIFY(UVitalityAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
    DOREPLIFETIME_CONDITION_NOTIFY(UVitalityAttributeSet, Stamina, COND_None, REPNOTIFY_Always);
    DOREPLIFETIME_CONDITION_NOTIFY(UVitalityAttributeSet, MaxStamina, COND_None, REPNOTIFY_Always);
}


void UVitalityAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
    Super::PreAttributeChange(Attribute, NewValue);

    
// Clamp health and stamina before modifications
    
if (Attribute == GetHealthAttribute())
    {
       NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
    } else if (Attribute == GetStaminaAttribute())
    {
       NewValue = FMath::Clamp(NewValue, 0.f, GetMaxStamina());
    } 
}


void UVitalityAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
    Super::PostGameplayEffectExecute(Data);

    
// Clamp health and stamina after modifications
    
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
    {
       SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
    } else if (Data.EvaluatedData.Attribute == GetStaminaAttribute())
    {
       SetStamina(FMath::Clamp(GetStamina(), 0.f, GetMaxStamina()));
    }

    if (Data.EffectSpec.Def->GetAssetTags().HasTag(FGameplayTag::RequestGameplayTag("Effects.HitReaction")))
    {
       FGameplayTagContainer HitReactionTagContainer;
       HitReactionTagContainer.AddTag(FGameplayTag::RequestGameplayTag("GameplayAbility.HitReaction"));
       GetOwningAbilitySystemComponent()->TryActivateAbilitiesByTag(HitReactionTagContainer);
    }

}

void UVitalityAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
    Super::PostAttributeChange(Attribute, OldValue, NewValue);

    
// Log attribute changes
    
UE_LOG(LogTemp, Warning,
    TEXT("Attr=%s | Old=%.2f New=%.2f"),
    *Attribute.GetName(),
    OldValue,
    NewValue);

    
// Check for death
    
if (Attribute == GetHealthAttribute() && NewValue <= 0.0f)
    {
       FGameplayTagContainer DeathAbilityTagContainer;
       DeathAbilityTagContainer.AddTag(FGameplayTag::RequestGameplayTag("GameplayAbility.Death"));
       GetOwningAbilitySystemComponent()->TryActivateAbilitiesByTag(DeathAbilityTagContainer);
       
    }
    
}

This is my first real foray into C++, so debugging this on my own feels very out of my depth. Any help from C++ or GAS wizards would be much appreciated to alleviate my suffering. Thank you.