Let's talk

RSAbilitySystemLibrary.cpp

Source/RSGasHelpers/Private/RSAbilitySystemLibrary.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 "RSAbilitySystemLibrary.h"
4
5#include "AbilitySystemComponent.h"
6#include "AbilitySystemGlobals.h"
7#include "AbilitySystemInterface.h"
8#include "GameplayEffect.h"
9#include "GameFramework/Controller.h"
10#include "GameFramework/Pawn.h"
11#include "GameFramework/PlayerState.h"
12#include "Misc/EngineVersionComparison.h"
13
14namespace RSGasHelpers
15{
16 static UAbilitySystemComponent* FromActorOnly(const AActor* Actor)
17 {
18 // LookForComponent=false: only trust IAbilitySystemInterface here, the
19 // component search below would also find an ASC on a child actor.
20 return Actor ? UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Actor, false) : nullptr;
21 }
22
23 static const TCHAR* RoleName(ENetRole Role)
24 {
25 switch (Role)
26 {
27 case ROLE_Authority: return TEXT("Authority");
28 case ROLE_AutonomousProxy: return TEXT("AutonomousProxy");
29 case ROLE_SimulatedProxy: return TEXT("SimulatedProxy");
30 default: return TEXT("None");
31 }
32 }
33}
34
35UAbilitySystemComponent* URSAbilitySystemLibrary::FindAbilitySystemComponent(const AActor* Actor)
36{
37 if (!Actor)
38 {
39 return nullptr;
40 }
41
42 if (UAbilitySystemComponent* ASC = RSGasHelpers::FromActorOnly(Actor))
43 {
44 return ASC;
45 }
46
47 if (const APawn* Pawn = Cast<APawn>(Actor))
48 {
49 if (UAbilitySystemComponent* ASC = RSGasHelpers::FromActorOnly(Pawn->GetPlayerState()))
50 {
51 return ASC;
52 }
53 }
54
55 if (const AController* Controller = Cast<AController>(Actor))
56 {
57 if (UAbilitySystemComponent* ASC = RSGasHelpers::FromActorOnly(Controller->PlayerState))
58 {
59 return ASC;
60 }
61 }
62
63 // An ASC component on the actor itself wins over its owner's, so a projectile
64 // with its own ASC is not mistaken for the pawn that fired it.
65 if (UAbilitySystemComponent* ASC = Actor->FindComponentByClass<UAbilitySystemComponent>())
66 {
67 return ASC;
68 }
69
70 return RSGasHelpers::FromActorOnly(Actor->GetOwner());
71}
72
73ERSActivateResult URSAbilitySystemLibrary::TryActivateAbilityByTag(AActor* Actor, FGameplayTag AbilityTag, bool bAllowRemoteActivation)
74{
75 UAbilitySystemComponent* ASC = FindAbilitySystemComponent(Actor);
76 if (!ASC)
77 {
78 return ERSActivateResult::NoASC;
79 }
80
81 const FGameplayTagContainer Tags(AbilityTag);
82
83 // bOnlyAbilitiesThatSatisfyTagRequirements=false: we want "is it granted at all",
84 // so a blocked ability still counts as found.
85 TArray<FGameplayAbilitySpec*> Specs;
86 ASC->GetActivatableGameplayAbilitySpecsByAllMatchingTags(Tags, Specs, false);
87 if (Specs.Num() == 0)
88 {
89 return ERSActivateResult::NotGranted;
90 }
91
92 return ASC->TryActivateAbilitiesByTag(Tags, bAllowRemoteActivation)
93 ? ERSActivateResult::Activated
94 : ERSActivateResult::Blocked;
95}
96
97bool URSAbilitySystemLibrary::AddReplicatedLooseTag(AActor* Actor, FGameplayTag Tag)
98{
99 UAbilitySystemComponent* ASC = FindAbilitySystemComponent(Actor);
100 if (!ASC || !Tag.IsValid() || !ASC->IsOwnerActorAuthoritative())
101 {
102 return false;
103 }
104
105#if UE_VERSION_NEWER_THAN(5, 6, 99)
106 // 5.7 removed AddReplicatedLooseGameplayTag; the replication state does both jobs.
107 // TagOnly replicates presence, not count: clients see 1 however many times it was added.
108 ASC->AddLooseGameplayTag(Tag, 1, EGameplayTagReplicationState::TagOnly);
109#else
110 // The loose tag updates the server's tag counts right away; the replicated
111 // loose tag container carries it to clients, which apply it in their OnRep.
112 ASC->AddLooseGameplayTag(Tag);
113 ASC->AddReplicatedLooseGameplayTag(Tag);
114#endif
115 return true;
116}
117
118bool URSAbilitySystemLibrary::RemoveReplicatedLooseTag(AActor* Actor, FGameplayTag Tag)
119{
120 UAbilitySystemComponent* ASC = FindAbilitySystemComponent(Actor);
121 if (!ASC || !Tag.IsValid() || !ASC->IsOwnerActorAuthoritative())
122 {
123 return false;
124 }
125
126#if UE_VERSION_NEWER_THAN(5, 6, 99)
127 // The container overload, not RemoveLooseGameplayTag(Tag): in 5.7 and 5.8 the single-tag
128 // path drops AnyCountChange callbacks unless the count reaches 0, so a 2 -> 1 change
129 // would never reach WaitGameplayTagCount or RegisterGameplayTagEvent listeners.
130 ASC->RemoveLooseGameplayTags(FGameplayTagContainer(Tag), 1, EGameplayTagReplicationState::TagOnly);
131#else
132 ASC->RemoveLooseGameplayTag(Tag);
133 ASC->RemoveReplicatedLooseGameplayTag(Tag);
134#endif
135 return true;
136}
137
138FGameplayEffectSpecHandle URSAbilitySystemLibrary::MakeSpecWithSetByCaller(
139 UAbilitySystemComponent* ASC,
140 TSubclassOf<UGameplayEffect> EffectClass,
141 float Level,
142 const TMap<FGameplayTag, float>& SetByCallerMagnitudes)
143{
144 if (!ASC || !EffectClass)
145 {
146 return FGameplayEffectSpecHandle();
147 }
148
149 FGameplayEffectContextHandle Context = ASC->MakeEffectContext();
150 Context.AddSourceObject(ASC->GetAvatarActor());
151
152 FGameplayEffectSpecHandle Spec = ASC->MakeOutgoingSpec(EffectClass, Level, Context);
153 if (Spec.IsValid())
154 {
155 for (const TPair<FGameplayTag, float>& Pair : SetByCallerMagnitudes)
156 {
157 Spec.Data->SetSetByCallerMagnitude(Pair.Key, Pair.Value);
158 }
159 }
160 return Spec;
161}
162
163FString URSAbilitySystemLibrary::DumpAbilitySystemState(const AActor* Actor)
164{
165 UAbilitySystemComponent* ASC = FindAbilitySystemComponent(Actor);
166 if (!ASC)
167 {
168 return FString::Printf(TEXT("[RS GAS] %s: no ability system component"), *GetNameSafe(Actor));
169 }
170
171 const AActor* Owner = ASC->GetOwnerActor();
172 const AActor* Avatar = ASC->GetAvatarActor_Direct();
173
174 FString Out;
175 Out += FString::Printf(TEXT("[RS GAS] %s | role %s | owner %s | avatar %s\n"),
176 *ASC->GetName(),
177 Owner ? RSGasHelpers::RoleName(Owner->GetLocalRole()) : TEXT("None"),
178 *GetNameSafe(Owner),
179 *GetNameSafe(Avatar));
180
181 Out += FString::Printf(TEXT(" Tags: %s\n"), *ASC->GetOwnedGameplayTags().ToStringSimple());
182
183 Out += TEXT(" Abilities:\n");
184 for (const FGameplayAbilitySpec& Spec : ASC->GetActivatableAbilities())
185 {
186 Out += FString::Printf(TEXT(" %s lvl %d %s\n"),
187 *GetNameSafe(Spec.Ability),
188 Spec.Level,
189 Spec.IsActive() ? TEXT("[ACTIVE]") : TEXT(""));
190 }
191
192 Out += TEXT(" Effects:\n");
193 for (const FActiveGameplayEffectHandle& Handle : ASC->GetActiveEffects(FGameplayEffectQuery()))
194 {
195 if (const FActiveGameplayEffect* Effect = ASC->GetActiveGameplayEffect(Handle))
196 {
197 Out += FString::Printf(TEXT(" %s x%d\n"),
198 *GetNameSafe(Effect->Spec.Def),
199 Effect->Spec.GetStackCount());
200 }
201 }
202
203 Out += TEXT(" Attributes:\n");
204 TArray<FGameplayAttribute> Attributes;
205 ASC->GetAllAttributes(Attributes);
206 for (const FGameplayAttribute& Attribute : Attributes)
207 {
208 Out += FString::Printf(TEXT(" %s = %.2f\n"),
209 *Attribute.GetName(),
210 ASC->GetNumericAttribute(Attribute));
211 }
212
213 return Out;
214}

Still desyncing in packaged builds?

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