Let's talk

GKLSampleAbilities.cpp

Source/GASKitLite/Private/Abilities/GKLSampleAbilities.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 "Abilities/GKLSampleAbilities.h"
4
5#include "AbilitySystemComponent.h"
6#include "AbilitySystemGlobals.h"
7#include "Engine/OverlapResult.h"
8#include "Engine/World.h"
9#include "GameFramework/Character.h"
10#include "GKLCharacter.h"
11#include "GKLEffects.h"
12#include "GKLGameplayTags.h"
13
14// ------------------------------------------------------------------ Jump
15
16UGKLAbility_Jump::UGKLAbility_Jump()
17{
18 AddAbilityTag(GKLTags::Ability_Jump);
19 StaminaCost = 10.f;
20}
21
22bool UGKLAbility_Jump::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
23 const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
24{
25 const ACharacter* Character = ActorInfo ? Cast<ACharacter>(ActorInfo->AvatarActor.Get()) : nullptr;
26 return Character && Character->CanJump() && Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags);
27}
28
29void UGKLAbility_Jump::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
30 const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
31{
32 if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
33 {
34 EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
35 return;
36 }
37 // The movement component predicts and replicates the jump itself.
38 if (ACharacter* Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get()))
39 {
40 Character->Jump();
41 }
42 EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
43}
44
45// ------------------------------------------------------------------ Heal
46
47UGKLAbility_Heal::UGKLAbility_Heal()
48{
49 AddAbilityTag(GKLTags::Ability_Heal);
50 ManaCost = 25.f;
51 CooldownDuration = 6.f;
52 CooldownTags.AddTag(GKLTags::Cooldown_Heal);
53}
54
55void UGKLAbility_Heal::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
56 const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
57{
58 // Commit first, before any latent work, so cost and cooldown are predicted.
59 if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
60 {
61 EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
62 return;
63 }
64 const FGameplayEffectSpecHandle Spec = MakeOutgoingGameplayEffectSpec(Handle, ActorInfo, ActivationInfo, UGKLHealEffect::StaticClass(), GetAbilityLevel(Handle, ActorInfo));
65 if (Spec.IsValid())
66 {
67 Spec.Data->SetSetByCallerMagnitude(GKLTags::Data_Healing, HealAmount);
68 ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, Spec);
69 }
70 EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
71}
72
73// ------------------------------------------------------------------ Strike
74
75UGKLAbility_Strike::UGKLAbility_Strike()
76{
77 AddAbilityTag(GKLTags::Ability_Strike);
78 StaminaCost = 15.f;
79 CooldownDuration = 0.8f;
80 CooldownTags.AddTag(GKLTags::Cooldown_Strike);
81}
82
83void UGKLAbility_Strike::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
84 const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
85{
86 if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
87 {
88 EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
89 return;
90 }
91
92 // Damage is server-authoritative: the client predicts the cost and cooldown, never the hit.
93 AActor* Avatar = ActorInfo->AvatarActor.Get();
94 UAbilitySystemComponent* SourceASC = ActorInfo->AbilitySystemComponent.Get();
95 if (HasAuthority(&ActivationInfo) && Avatar && SourceASC)
96 {
97 const FVector Center = Avatar->GetActorLocation() + Avatar->GetActorForwardVector() * Range;
98 FCollisionQueryParams Params(SCENE_QUERY_STAT(GKLStrike), false, Avatar);
99 TArray<FOverlapResult> Overlaps;
100 Avatar->GetWorld()->OverlapMultiByObjectType(Overlaps, Center, FQuat::Identity,
101 FCollisionObjectQueryParams(ECC_Pawn), FCollisionShape::MakeSphere(Radius), Params);
102
103 TSet<UAbilitySystemComponent*> Hit;
104 for (const FOverlapResult& Overlap : Overlaps)
105 {
106 UAbilitySystemComponent* TargetASC = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Overlap.GetActor());
107 if (!TargetASC || TargetASC == SourceASC || Hit.Contains(TargetASC))
108 {
109 continue;
110 }
111 Hit.Add(TargetASC);
112
113 const FGameplayEffectSpecHandle Spec = MakeOutgoingGameplayEffectSpec(Handle, ActorInfo, ActivationInfo, UGKLDamageEffect::StaticClass(), GetAbilityLevel(Handle, ActorInfo));
114 if (Spec.IsValid())
115 {
116 Spec.Data->SetSetByCallerMagnitude(GKLTags::Data_Damage, DamageAmount);
117 SourceASC->ApplyGameplayEffectSpecToTarget(*Spec.Data.Get(), TargetASC);
118 }
119 }
120 }
121 EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
122}
123
124// ------------------------------------------------------------------ Regen
125
126UGKLAbility_Regen::UGKLAbility_Regen()
127{
128 AddAbilityTag(GKLTags::Ability_Passive_Regen);
129 NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::ServerOnly;
130}
131
132void UGKLAbility_Regen::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
133{
134 Super::OnGiveAbility(ActorInfo, Spec);
135
136 // Passive: start as soon as the server grants it.
137 if (ActorInfo && ActorInfo->IsNetAuthority() && !Spec.IsActive())
138 {
139 ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle);
140 }
141}
142
143void UGKLAbility_Regen::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
144 const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
145{
146 if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
147 {
148 EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
149 return;
150 }
151 const FGameplayEffectSpecHandle Spec = MakeOutgoingGameplayEffectSpec(Handle, ActorInfo, ActivationInfo, UGKLRegenEffect::StaticClass(), GetAbilityLevel(Handle, ActorInfo));
152 RegenHandle = ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, Spec);
153 // Stays active until cancelled (Death cancels it).
154}
155
156void UGKLAbility_Regen::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
157 const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
158{
159 if (RegenHandle.IsValid() && ActorInfo && ActorInfo->AbilitySystemComponent.IsValid())
160 {
161 ActorInfo->AbilitySystemComponent->RemoveActiveGameplayEffect(RegenHandle);
162 RegenHandle.Invalidate();
163 }
164 Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
165}
166
167// ------------------------------------------------------------------ Death
168
169UGKLAbility_Death::UGKLAbility_Death()
170{
171 AddAbilityTag(GKLTags::Ability_Death);
172 NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::ServerOnly;
173
174 FAbilityTriggerData Trigger;
175 Trigger.TriggerTag = GKLTags::Event_Death;
176 Trigger.TriggerSource = EGameplayAbilityTriggerSource::GameplayEvent;
177 AbilityTriggers.Add(Trigger);
178}
179
180void UGKLAbility_Death::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
181 const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
182{
183 if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
184 {
185 EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
186 return;
187 }
188 UAbilitySystemComponent* ASC = ActorInfo->AbilitySystemComponent.Get();
189 ASC->CancelAllAbilities(this);
190
191 // Replicated, so clients see State.Dead too.
192 GKLTags::AddReplicatedLooseTag(ASC, GKLTags::State_Dead);
193
194 if (AGKLCharacter* Character = GetLiteCharacter())
195 {
196 Character->Die();
197 }
198 EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
199}

Still desyncing in packaged builds?

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