// Copyright (c) 2026 Reignance Studios. MIT License, see LICENSE. #include "GKLAttributeSet.h" #include "GameplayEffectExtension.h" #include "GKLGameplayTags.h" #include "Net/UnrealNetwork.h" UGKLAttributeSet::UGKLAttributeSet() { InitHealth(100.f); InitMaxHealth(100.f); InitStamina(100.f); InitMaxStamina(100.f); InitMana(100.f); InitMaxMana(100.f); InitDamage(0.f); } void UGKLAttributeSet::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, Health, COND_None, REPNOTIFY_Always); DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always); DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, Stamina, COND_None, REPNOTIFY_Always); DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, MaxStamina, COND_None, REPNOTIFY_Always); DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, Mana, COND_None, REPNOTIFY_Always); DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, MaxMana, COND_None, REPNOTIFY_Always); } void UGKLAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { Super::PreAttributeChange(Attribute, NewValue); // Clamps the current value. The base value is clamped in PostGameplayEffectExecute. if (Attribute == GetHealthAttribute()) { NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth()); } else if (Attribute == GetStaminaAttribute()) { NewValue = FMath::Clamp(NewValue, 0.f, GetMaxStamina()); } else if (Attribute == GetManaAttribute()) { NewValue = FMath::Clamp(NewValue, 0.f, GetMaxMana()); } else if (Attribute == GetMaxHealthAttribute() || Attribute == GetMaxStaminaAttribute() || Attribute == GetMaxManaAttribute()) { NewValue = FMath::Max(NewValue, 1.f); } } void UGKLAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) { Super::PostGameplayEffectExecute(Data); const FGameplayAttribute& Attribute = Data.EvaluatedData.Attribute; if (Attribute == GetDamageAttribute()) { const float Amount = GetDamage(); SetDamage(0.f); if (Amount > 0.f) { SetHealth(FMath::Clamp(GetHealth() - Amount, 0.f, GetMaxHealth())); } } else if (Attribute == GetHealthAttribute()) { // Only write when the clamp changes something: setting a base value broadcasts a // change even when it is equal, so UI watchers would get every effect twice. const float Clamped = FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()); if (Clamped != GetHealth()) { SetHealth(Clamped); } } else if (Attribute == GetStaminaAttribute()) { const float Clamped = FMath::Clamp(GetStamina(), 0.f, GetMaxStamina()); if (Clamped != GetStamina()) { SetStamina(Clamped); } } else if (Attribute == GetManaAttribute()) { const float Clamped = FMath::Clamp(GetMana(), 0.f, GetMaxMana()); if (Clamped != GetMana()) { SetMana(Clamped); } } // PostGameplayEffectExecute runs on the server only, so the event activates the // ServerOnly Death ability exactly once. if (GetHealth() <= 0.f && !bOutOfHealth) { bOutOfHealth = true; FGameplayEventData Payload; Payload.EventTag = GKLTags::Event_Death; Payload.Instigator = Data.EffectSpec.GetContext().GetOriginalInstigator(); Payload.Target = GetOwningActor(); GetOwningAbilitySystemComponent()->HandleGameplayEvent(Payload.EventTag, &Payload); } } void UGKLAttributeSet::ResetToMax() { SetHealth(GetMaxHealth()); SetStamina(GetMaxStamina()); SetMana(GetMaxMana()); bOutOfHealth = false; } void UGKLAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue) { GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, Health, OldValue); } void UGKLAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue) { GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, MaxHealth, OldValue); } void UGKLAttributeSet::OnRep_Stamina(const FGameplayAttributeData& OldValue) { GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, Stamina, OldValue); } void UGKLAttributeSet::OnRep_MaxStamina(const FGameplayAttributeData& OldValue) { GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, MaxStamina, OldValue); } void UGKLAttributeSet::OnRep_Mana(const FGameplayAttributeData& OldValue) { GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, Mana, OldValue); } void UGKLAttributeSet::OnRep_MaxMana(const FGameplayAttributeData& OldValue) { GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, MaxMana, OldValue); }