Let's talk

GKLAttributeSet.cpp

Source/GASKitLite/Private/GKLAttributeSet.cpp Raw Download ZIP

Click a line number to link it. Shift-click to select a range.

1// Copyright (c) 2026 Reignance Studios. MIT License, see LICENSE.
2
3#include "GKLAttributeSet.h"
4
5#include "GameplayEffectExtension.h"
6#include "GKLGameplayTags.h"
7#include "Net/UnrealNetwork.h"
8
9UGKLAttributeSet::UGKLAttributeSet()
10{
11 InitHealth(100.f);
12 InitMaxHealth(100.f);
13 InitStamina(100.f);
14 InitMaxStamina(100.f);
15 InitMana(100.f);
16 InitMaxMana(100.f);
17 InitDamage(0.f);
18}
19
20void UGKLAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
21{
22 Super::GetLifetimeReplicatedProps(OutLifetimeProps);
23
24 DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, Health, COND_None, REPNOTIFY_Always);
25 DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
26 DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, Stamina, COND_None, REPNOTIFY_Always);
27 DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, MaxStamina, COND_None, REPNOTIFY_Always);
28 DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, Mana, COND_None, REPNOTIFY_Always);
29 DOREPLIFETIME_CONDITION_NOTIFY(UGKLAttributeSet, MaxMana, COND_None, REPNOTIFY_Always);
30}
31
32void UGKLAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
33{
34 Super::PreAttributeChange(Attribute, NewValue);
35
36 // Clamps the current value. The base value is clamped in PostGameplayEffectExecute.
37 if (Attribute == GetHealthAttribute())
38 {
39 NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
40 }
41 else if (Attribute == GetStaminaAttribute())
42 {
43 NewValue = FMath::Clamp(NewValue, 0.f, GetMaxStamina());
44 }
45 else if (Attribute == GetManaAttribute())
46 {
47 NewValue = FMath::Clamp(NewValue, 0.f, GetMaxMana());
48 }
49 else if (Attribute == GetMaxHealthAttribute() || Attribute == GetMaxStaminaAttribute() || Attribute == GetMaxManaAttribute())
50 {
51 NewValue = FMath::Max(NewValue, 1.f);
52 }
53}
54
55void UGKLAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
56{
57 Super::PostGameplayEffectExecute(Data);
58
59 const FGameplayAttribute& Attribute = Data.EvaluatedData.Attribute;
60 if (Attribute == GetDamageAttribute())
61 {
62 const float Amount = GetDamage();
63 SetDamage(0.f);
64 if (Amount > 0.f)
65 {
66 SetHealth(FMath::Clamp(GetHealth() - Amount, 0.f, GetMaxHealth()));
67 }
68 }
69 else if (Attribute == GetHealthAttribute())
70 {
71 // Only write when the clamp changes something: setting a base value broadcasts a
72 // change even when it is equal, so UI watchers would get every effect twice.
73 const float Clamped = FMath::Clamp(GetHealth(), 0.f, GetMaxHealth());
74 if (Clamped != GetHealth())
75 {
76 SetHealth(Clamped);
77 }
78 }
79 else if (Attribute == GetStaminaAttribute())
80 {
81 const float Clamped = FMath::Clamp(GetStamina(), 0.f, GetMaxStamina());
82 if (Clamped != GetStamina())
83 {
84 SetStamina(Clamped);
85 }
86 }
87 else if (Attribute == GetManaAttribute())
88 {
89 const float Clamped = FMath::Clamp(GetMana(), 0.f, GetMaxMana());
90 if (Clamped != GetMana())
91 {
92 SetMana(Clamped);
93 }
94 }
95
96 // PostGameplayEffectExecute runs on the server only, so the event activates the
97 // ServerOnly Death ability exactly once.
98 if (GetHealth() <= 0.f && !bOutOfHealth)
99 {
100 bOutOfHealth = true;
101
102 FGameplayEventData Payload;
103 Payload.EventTag = GKLTags::Event_Death;
104 Payload.Instigator = Data.EffectSpec.GetContext().GetOriginalInstigator();
105 Payload.Target = GetOwningActor();
106 GetOwningAbilitySystemComponent()->HandleGameplayEvent(Payload.EventTag, &Payload);
107 }
108}
109
110void UGKLAttributeSet::ResetToMax()
111{
112 SetHealth(GetMaxHealth());
113 SetStamina(GetMaxStamina());
114 SetMana(GetMaxMana());
115 bOutOfHealth = false;
116}
117
118void UGKLAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
119{
120 GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, Health, OldValue);
121}
122
123void UGKLAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue)
124{
125 GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, MaxHealth, OldValue);
126}
127
128void UGKLAttributeSet::OnRep_Stamina(const FGameplayAttributeData& OldValue)
129{
130 GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, Stamina, OldValue);
131}
132
133void UGKLAttributeSet::OnRep_MaxStamina(const FGameplayAttributeData& OldValue)
134{
135 GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, MaxStamina, OldValue);
136}
137
138void UGKLAttributeSet::OnRep_Mana(const FGameplayAttributeData& OldValue)
139{
140 GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, Mana, OldValue);
141}
142
143void UGKLAttributeSet::OnRep_MaxMana(const FGameplayAttributeData& OldValue)
144{
145 GAMEPLAYATTRIBUTE_REPNOTIFY(UGKLAttributeSet, MaxMana, OldValue);
146}

Still desyncing in packaged builds?

Free code fixes one symptom. A Diagnostic finds the cause across your project in two days.